summaryrefslogtreecommitdiffstats
path: root/llhttp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-21 20:56:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-21 20:56:19 +0000
commit0b6210cd37b68b94252cb798598b12974a20e1c1 (patch)
treee371686554a877842d95aa94f100bee552ff2a8e /llhttp
parentInitial commit. (diff)
downloadnode-undici-upstream.tar.xz
node-undici-upstream.zip
Adding upstream version 5.28.2+dfsg1+~cs23.11.12.3.upstream/5.28.2+dfsg1+_cs23.11.12.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'llhttp')
-rw-r--r--llhttp/.dockerignore6
-rw-r--r--llhttp/.eslintrc.js31
-rw-r--r--llhttp/.github/workflows/aiohttp.yml61
-rw-r--r--llhttp/.github/workflows/ci.yaml117
-rw-r--r--llhttp/.gitignore6
-rw-r--r--llhttp/.npmrc1
-rw-r--r--llhttp/CMakeLists.txt117
-rw-r--r--llhttp/CNAME1
-rw-r--r--llhttp/CODE_OF_CONDUCT.md4
-rw-r--r--llhttp/Dockerfile13
-rw-r--r--llhttp/LICENSE-MIT22
-rw-r--r--llhttp/Makefile93
-rw-r--r--llhttp/README.md501
-rw-r--r--llhttp/_config.yml1
-rw-r--r--llhttp/bench/index.ts71
-rw-r--r--llhttp/bin/build_wasm.ts95
-rwxr-xr-xllhttp/bin/generate.ts47
-rw-r--r--llhttp/docs/releasing.md65
-rw-r--r--llhttp/examples/wasm.ts248
-rw-r--r--llhttp/images/http-loose-none.pngbin0 -> 3571702 bytes
-rw-r--r--llhttp/images/http-strict-none.pngbin0 -> 4166480 bytes
-rw-r--r--llhttp/libllhttp.pc.in10
-rw-r--r--llhttp/package-lock.json2995
-rw-r--r--llhttp/package.json60
-rw-r--r--llhttp/src/common.gypi46
-rw-r--r--llhttp/src/llhttp.gyp22
-rw-r--r--llhttp/src/llhttp.ts7
-rw-r--r--llhttp/src/llhttp/c-headers.ts106
-rw-r--r--llhttp/src/llhttp/constants.ts540
-rw-r--r--llhttp/src/llhttp/http.ts1299
-rw-r--r--llhttp/src/llhttp/url.ts220
-rw-r--r--llhttp/src/llhttp/utils.ts27
-rw-r--r--llhttp/src/native/api.c510
-rw-r--r--llhttp/src/native/api.h355
-rw-r--r--llhttp/src/native/http.c170
-rw-r--r--llhttp/test/fixtures/extra.c457
-rw-r--r--llhttp/test/fixtures/index.ts116
-rw-r--r--llhttp/test/fuzzers/fuzz_parser.c45
-rw-r--r--llhttp/test/md-test.ts269
-rw-r--r--llhttp/test/request/connection.md732
-rw-r--r--llhttp/test/request/content-length.md482
-rw-r--r--llhttp/test/request/finish.md69
-rw-r--r--llhttp/test/request/invalid.md607
-rw-r--r--llhttp/test/request/lenient-headers.md145
-rw-r--r--llhttp/test/request/lenient-version.md23
-rw-r--r--llhttp/test/request/method.md450
-rw-r--r--llhttp/test/request/pausing.md381
-rw-r--r--llhttp/test/request/pipelining.md66
-rw-r--r--llhttp/test/request/sample.md629
-rw-r--r--llhttp/test/request/transfer-encoding.md1187
-rw-r--r--llhttp/test/request/uri.md243
-rw-r--r--llhttp/test/response/connection.md647
-rw-r--r--llhttp/test/response/content-length.md158
-rw-r--r--llhttp/test/response/finish.md23
-rw-r--r--llhttp/test/response/invalid.md285
-rw-r--r--llhttp/test/response/lenient-version.md20
-rw-r--r--llhttp/test/response/pausing.md330
-rw-r--r--llhttp/test/response/pipelining.md60
-rw-r--r--llhttp/test/response/sample.md653
-rw-r--r--llhttp/test/response/transfer-encoding.md410
-rw-r--r--llhttp/test/url.md261
-rw-r--r--llhttp/tsconfig.json15
-rw-r--r--llhttp/tslint.json14
63 files changed, 16644 insertions, 0 deletions
diff --git a/llhttp/.dockerignore b/llhttp/.dockerignore
new file mode 100644
index 0000000..11b226d
--- /dev/null
+++ b/llhttp/.dockerignore
@@ -0,0 +1,6 @@
+*
+!package.json
+!package-lock.json
+!tsconfig.json
+!bin
+!src
diff --git a/llhttp/.eslintrc.js b/llhttp/.eslintrc.js
new file mode 100644
index 0000000..595cf53
--- /dev/null
+++ b/llhttp/.eslintrc.js
@@ -0,0 +1,31 @@
+module.exports = {
+ 'env': {
+ 'browser': false,
+ 'commonjs': true,
+ 'es6': true,
+ 'node': true
+ },
+ 'extends': 'eslint:recommended',
+ 'rules': {
+ 'max-len': [ 2, {
+ 'code': 80,
+ 'ignoreComments': true
+ } ],
+ 'indent': [
+ 'error',
+ 2
+ ],
+ 'linebreak-style': [
+ 'error',
+ 'unix'
+ ],
+ 'quotes': [
+ 'error',
+ 'single'
+ ],
+ 'semi': [
+ 'error',
+ 'always'
+ ]
+ }
+};
diff --git a/llhttp/.github/workflows/aiohttp.yml b/llhttp/.github/workflows/aiohttp.yml
new file mode 100644
index 0000000..8ae8eb3
--- /dev/null
+++ b/llhttp/.github/workflows/aiohttp.yml
@@ -0,0 +1,61 @@
+name: Aiohttp
+# If you don't understand the reason for a test failure, ping @Dreamsorcerer or open an issue in aio-libs/aiohttp.
+
+on:
+ push:
+ branches:
+ - 'main'
+ pull_request:
+ branches:
+ - 'main'
+
+jobs:
+ test:
+ permissions:
+ contents: read # to fetch code (actions/checkout)
+
+ name: Aiohttp regression tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout aiohttp
+ uses: actions/checkout@v4
+ with:
+ repository: aio-libs/aiohttp
+ - name: Checkout llhttp
+ uses: actions/checkout@v4
+ with:
+ path: vendor/llhttp
+ - name: Restore node_modules cache
+ uses: actions/cache@v3
+ with:
+ path: vendor/llhttp/.npm
+ key: ubuntu-latest-node-${{ hashFiles('vendor/llhttp/**/package-lock.json') }}
+ restore-keys: ubuntu-latest-node-
+ - name: Install llhttp dependencies
+ run: npm install --ignore-scripts
+ working-directory: vendor/llhttp
+ - name: Build llhttp
+ run: make
+ working-directory: vendor/llhttp
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.x
+ cache: 'pip'
+ cache-dependency-path: 'requirements/*.txt'
+ - name: Provision the dev env
+ run: >-
+ PATH="${HOME}/.local/bin:${PATH}"
+ make .develop
+ - name: Run tests
+ env:
+ COLOR: yes
+ run: >-
+ PATH="${HOME}/.local/bin:${PATH}"
+ pytest tests/test_http_parser.py tests/test_web_functional.py
+ - name: Run dev_mode tests
+ env:
+ COLOR: yes
+ run: >-
+ PATH="${HOME}/.local/bin:${PATH}"
+ python -X dev -m pytest -m dev_mode tests/test_http_parser.py tests/test_web_functional.py
diff --git a/llhttp/.github/workflows/ci.yaml b/llhttp/.github/workflows/ci.yaml
new file mode 100644
index 0000000..d1b3a65
--- /dev/null
+++ b/llhttp/.github/workflows/ci.yaml
@@ -0,0 +1,117 @@
+name: CI
+
+on: [push, pull_request]
+
+env:
+ CI: true
+
+jobs:
+ build:
+ name: Build libllhttp.a
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os:
+ - macos-latest
+ - ubuntu-latest
+ - windows-latest
+ steps:
+ - name: Install clang for Windows
+ if: runner.os == 'Windows'
+ run: |
+ iwr -useb get.scoop.sh -outfile 'install.ps1'
+ .\install.ps1 -RunAsAdmin
+ scoop install llvm --global
+
+ # Scoop modifies the PATH so we make the modified PATH global.
+ echo $env:PATH >> $env:GITHUB_PATH
+
+ - name: Fetch code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ # Skip macOS & Windows, cache there is slower
+ - name: Restore node_modules cache for Linux
+ uses: actions/cache@v3
+ if: runner.os == 'Linux'
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install dependencies
+ run: npm install --ignore-scripts
+
+ - name: Build libllhttp.a
+ shell: bash
+ run: |
+ make build/libllhttp.a
+
+ test:
+ name: Run tests
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os:
+ - macos-latest
+ - ubuntu-latest
+ - windows-latest
+ steps:
+ - name: Install clang for Windows
+ if: runner.os == 'Windows'
+ run: |
+ iwr -useb get.scoop.sh -outfile 'install.ps1'
+ .\install.ps1 -RunAsAdmin
+ scoop install llvm --global
+
+ # Scoop modifies the PATH so we make the modified PATH global.
+ echo $env:PATH >> $env:GITHUB_PATH
+
+ - name: Fetch code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ # Skip macOS & Windows, cache there is slower
+ - name: Restore node_modules cache for Linux
+ uses: actions/cache@v3
+ if: runner.os == 'Linux'
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install dependencies
+ run: npm install --ignore-scripts
+
+ # Custom script, because progress looks not good in CI
+ - name: Run tests
+ env:
+ CFLAGS: -O0
+ run: npx mocha --timeout 30000 -r ts-node/register/type-check test/*-test.ts
+
+ lint:
+ name: Run TSLint
+ runs-on: ubuntu-latest
+ steps:
+ - name: Fetch code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Restore node_modules cache
+ uses: actions/cache@v3
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install dependencies
+ run: npm install --ignore-scripts
+
+ - name: Run lint command
+ run: npm run lint
diff --git a/llhttp/.gitignore b/llhttp/.gitignore
new file mode 100644
index 0000000..c2e9902
--- /dev/null
+++ b/llhttp/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+npm-debug.log
+test/tmp/
+lib/
+build/
+release/
diff --git a/llhttp/.npmrc b/llhttp/.npmrc
new file mode 100644
index 0000000..cafe685
--- /dev/null
+++ b/llhttp/.npmrc
@@ -0,0 +1 @@
+package-lock=true
diff --git a/llhttp/CMakeLists.txt b/llhttp/CMakeLists.txt
new file mode 100644
index 0000000..97fa408
--- /dev/null
+++ b/llhttp/CMakeLists.txt
@@ -0,0 +1,117 @@
+cmake_minimum_required(VERSION 3.5.1)
+cmake_policy(SET CMP0069 NEW)
+
+project(llhttp VERSION _RELEASE_)
+include(GNUInstallDirs)
+
+set(CMAKE_C_STANDARD 99)
+
+# By default build in relwithdebinfo type, supports both lowercase and uppercase
+if(NOT CMAKE_CONFIGURATION_TYPES)
+ set(allowableBuildTypes DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${allowableBuildTypes}")
+ if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE RELWITHDEBINFO CACHE STRING "" FORCE)
+ else()
+ string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
+ if(NOT CMAKE_BUILD_TYPE IN_LIST allowableBuildTypes)
+ message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}")
+ endif()
+ endif()
+endif()
+
+#
+# Options
+#
+# Generic option
+option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so)" ON)
+option(BUILD_STATIC_LIBS "Build static libraries (.lib/.a)" OFF)
+
+# Source code
+set(LLHTTP_SOURCES
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/llhttp.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/http.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/api.c
+)
+
+set(LLHTTP_HEADERS
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/llhttp.h
+)
+
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc.in
+ ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc
+ @ONLY
+)
+
+function(config_library target)
+ target_sources(${target} PRIVATE ${LLHTTP_SOURCES} ${LLHTTP_HEADERS})
+
+ target_include_directories(${target} PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:include>
+ )
+
+ set_target_properties(${target} PROPERTIES
+ OUTPUT_NAME llhttp
+ VERSION ${PROJECT_VERSION}
+ SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
+ PUBLIC_HEADER ${LLHTTP_HEADERS}
+ )
+
+ install(TARGETS ${target}
+ EXPORT llhttp
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
+ )
+
+ install(FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
+ )
+
+ # This is required to work with FetchContent
+ install(EXPORT llhttp
+ FILE llhttp-config.cmake
+ NAMESPACE llhttp::
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/llhttp
+ )
+endfunction(config_library target)
+
+if(BUILD_SHARED_LIBS)
+ add_library(llhttp_shared SHARED
+ ${llhttp_src}
+ )
+ add_library(llhttp::llhttp ALIAS llhttp_shared)
+ config_library(llhttp_shared)
+endif()
+
+if(BUILD_STATIC_LIBS)
+ add_library(llhttp_static STATIC
+ ${llhttp_src}
+ )
+ if(BUILD_SHARED_LIBS)
+ add_library(llhttp::llhttp ALIAS llhttp_shared)
+ else()
+ add_library(llhttp::llhttp ALIAS llhttp_static)
+ endif()
+ config_library(llhttp_static)
+endif()
+
+# On windows with Visual Studio, add a debug postfix so that release
+# and debug libraries can coexist.
+if(MSVC)
+ set(CMAKE_DEBUG_POSTFIX "d")
+endif()
+
+# Print project configure summary
+message(STATUS "")
+message(STATUS "")
+message(STATUS "Project configure summary:")
+message(STATUS "")
+message(STATUS " CMake build type .................: ${CMAKE_BUILD_TYPE}")
+message(STATUS " Install prefix ...................: ${CMAKE_INSTALL_PREFIX}")
+message(STATUS " Build shared library .............: ${BUILD_SHARED_LIBS}")
+message(STATUS " Build static library .............: ${BUILD_STATIC_LIBS}")
+message(STATUS "")
diff --git a/llhttp/CNAME b/llhttp/CNAME
new file mode 100644
index 0000000..4c4e078
--- /dev/null
+++ b/llhttp/CNAME
@@ -0,0 +1 @@
+llhttp.org \ No newline at end of file
diff --git a/llhttp/CODE_OF_CONDUCT.md b/llhttp/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..8470ae4
--- /dev/null
+++ b/llhttp/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+# Code of Conduct
+
+* [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/main/CODE_OF_CONDUCT.md)
+* [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/main/Moderation-Policy.md)
diff --git a/llhttp/Dockerfile b/llhttp/Dockerfile
new file mode 100644
index 0000000..2b5bfae
--- /dev/null
+++ b/llhttp/Dockerfile
@@ -0,0 +1,13 @@
+FROM node:18-alpine
+ARG UID=1000
+ARG GID=1000
+
+RUN apk add -U clang lld wasi-sdk && mkdir /home/node/llhttp
+
+WORKDIR /home/node/llhttp
+
+COPY . .
+
+RUN npm ci
+
+USER node
diff --git a/llhttp/LICENSE-MIT b/llhttp/LICENSE-MIT
new file mode 100644
index 0000000..6c1512d
--- /dev/null
+++ b/llhttp/LICENSE-MIT
@@ -0,0 +1,22 @@
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2018.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/llhttp/Makefile b/llhttp/Makefile
new file mode 100644
index 0000000..d9c6d35
--- /dev/null
+++ b/llhttp/Makefile
@@ -0,0 +1,93 @@
+CLANG ?= clang
+CFLAGS ?=
+OS ?=
+
+CFLAGS += -Os -g3 -Wall -Wextra -Wno-unused-parameter
+ifneq ($(OS),Windows_NT)
+ # NOTE: clang on windows does not support fPIC
+ CFLAGS += -fPIC
+endif
+
+INCLUDES += -Ibuild/
+
+INSTALL ?= install
+PREFIX ?= /usr/local
+LIBDIR = $(PREFIX)/lib
+INCLUDEDIR = $(PREFIX)/include
+
+all: build/libllhttp.a build/libllhttp.so
+
+clean:
+ rm -rf release/
+ rm -rf build/
+
+build/libllhttp.so: build/c/llhttp.o build/native/api.o \
+ build/native/http.o
+ $(CLANG) -shared $^ -o $@
+
+build/libllhttp.a: build/c/llhttp.o build/native/api.o \
+ build/native/http.o
+ $(AR) rcs $@ build/c/llhttp.o build/native/api.o build/native/http.o
+
+build/c/llhttp.o: build/c/llhttp.c
+ $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+build/native/%.o: src/native/%.c build/llhttp.h src/native/api.h \
+ build/native
+ $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+build/llhttp.h: generate
+build/c/llhttp.c: generate
+
+build/native:
+ mkdir -p build/native
+
+release: clean generate
+ @echo "${RELEASE}" | grep -q -E ".+" || { echo "Please make sure the RELEASE argument is set."; exit 1; }
+ rm -rf release
+ mkdir -p release/src
+ mkdir -p release/include
+ cp -rf build/llhttp.h release/include/
+ cp -rf build/c/llhttp.c release/src/
+ cp -rf src/native/*.c release/src/
+ cp -rf src/llhttp.gyp release/
+ cp -rf src/common.gypi release/
+ sed s/_RELEASE_/$(RELEASE)/ CMakeLists.txt > release/CMakeLists.txt
+ cp -rf libllhttp.pc.in release/
+ cp -rf README.md release/
+ cp -rf LICENSE-MIT release/
+
+github-release:
+ @echo "${RELEASE_V}" | grep -q -E "^v" || { echo "Please make sure version starts with \"v\"."; exit 1; }
+ gh release create -d --generate-notes ${RELEASE_V}
+ @sleep 5
+ gh release view ${RELEASE_V} -t "{{.body}}" --json body > RELEASE_NOTES
+ gh release delete ${RELEASE_V} -y
+ gh release create -F RELEASE_NOTES -d --title ${RELEASE_V} --target release release/${RELEASE_V}
+ @sleep 5
+ rm -rf RELEASE_NOTES
+ open $$(gh release view release/${RELEASE_V} --json url -t "{{.url}}")
+
+postversion: release
+ git fetch origin
+ git push
+ git checkout release --
+ cp -rf release/* ./
+ rm -rf release
+ git add include src *.gyp *.gypi CMakeLists.txt README.md LICENSE-MIT libllhttp.pc.in
+ git commit -a -m "release: $(RELEASE)"
+ git tag "release/v$(RELEASE)"
+ git push && git push --tags
+ git checkout main
+
+generate:
+ npx ts-node bin/generate.ts
+
+install: build/libllhttp.a build/libllhttp.so
+ $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)
+ $(INSTALL) -d $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -C build/llhttp.h $(DESTDIR)$(INCLUDEDIR)/llhttp.h
+ $(INSTALL) -C build/libllhttp.a $(DESTDIR)$(LIBDIR)/libllhttp.a
+ $(INSTALL) build/libllhttp.so $(DESTDIR)$(LIBDIR)/libllhttp.so
+
+.PHONY: all generate clean release postversion github-release
diff --git a/llhttp/README.md b/llhttp/README.md
new file mode 100644
index 0000000..4960dbb
--- /dev/null
+++ b/llhttp/README.md
@@ -0,0 +1,501 @@
+# llhttp
+[![CI](https://github.com/nodejs/llhttp/workflows/CI/badge.svg)](https://github.com/nodejs/llhttp/actions?query=workflow%3ACI)
+
+Port of [http_parser][0] to [llparse][1].
+
+## Why?
+
+Let's face it, [http_parser][0] is practically unmaintainable. Even
+introduction of a single new method results in a significant code churn.
+
+This project aims to:
+
+* Make it maintainable
+* Verifiable
+* Improving benchmarks where possible
+
+More details in [Fedor Indutny's talk at JSConf EU 2019](https://youtu.be/x3k_5Mi66sY)
+
+## How?
+
+Over time, different approaches for improving [http_parser][0]'s code base
+were tried. However, all of them failed due to resulting significant performance
+degradation.
+
+This project is a port of [http_parser][0] to TypeScript. [llparse][1] is used
+to generate the output C source file, which could be compiled and
+linked with the embedder's program (like [Node.js][7]).
+
+## Performance
+
+So far llhttp outperforms http_parser:
+
+| | input size | bandwidth | reqs/sec | time |
+|:----------------|-----------:|-------------:|-----------:|--------:|
+| **llhttp** | 8192.00 mb | 1777.24 mb/s | 3583799.39 req/sec | 4.61 s |
+| **http_parser** | 8192.00 mb | 694.66 mb/s | 1406180.33 req/sec | 11.79 s |
+
+llhttp is faster by approximately **156%**.
+
+## Maintenance
+
+llhttp project has about 1400 lines of TypeScript code describing the parser
+itself and around 450 lines of C code and headers providing the helper methods.
+The whole [http_parser][0] is implemented in approximately 2500 lines of C, and
+436 lines of headers.
+
+All optimizations and multi-character matching in llhttp are generated
+automatically, and thus doesn't add any extra maintenance cost. On the contrary,
+most of http_parser's code is hand-optimized and unrolled. Instead describing
+"how" it should parse the HTTP requests/responses, a maintainer should
+implement the new features in [http_parser][0] cautiously, considering
+possible performance degradation and manually optimizing the new code.
+
+## Verification
+
+The state machine graph is encoded explicitly in llhttp. The [llparse][1]
+automatically checks the graph for absence of loops and correct reporting of the
+input ranges (spans) like header names and values. In the future, additional
+checks could be performed to get even stricter verification of the llhttp.
+
+## Usage
+
+```C
+#include "stdio.h"
+#include "llhttp.h"
+#include "string.h"
+
+int handle_on_message_complete(llhttp_t* parser) {
+ fprintf(stdout, "Message completed!\n");
+ return 0;
+}
+
+int main() {
+ llhttp_t parser;
+ llhttp_settings_t settings;
+
+ /*Initialize user callbacks and settings */
+ llhttp_settings_init(&settings);
+
+ /*Set user callback */
+ settings.on_message_complete = handle_on_message_complete;
+
+ /*Initialize the parser in HTTP_BOTH mode, meaning that it will select between
+ *HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
+ *input.
+ */
+ llhttp_init(&parser, HTTP_BOTH, &settings);
+
+ /*Parse request! */
+ const char* request = "GET / HTTP/1.1\r\n\r\n";
+ int request_len = strlen(request);
+
+ enum llhttp_errno err = llhttp_execute(&parser, request, request_len);
+ if (err == HPE_OK) {
+ fprintf(stdout, "Successfully parsed!\n");
+ } else {
+ fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason);
+ }
+}
+```
+For more information on API usage, please refer to [src/native/api.h](https://github.com/nodejs/llhttp/blob/main/src/native/api.h).
+
+## API
+
+### llhttp_settings_t
+
+The settings object contains a list of callbacks that the parser will invoke.
+
+The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_PAUSED` (pause the parser):
+
+* `on_message_begin`: Invoked when a new request/response starts.
+* `on_message_complete`: Invoked when a request/response has been completedly parsed.
+* `on_url_complete`: Invoked after the URL has been parsed.
+* `on_method_complete`: Invoked after the HTTP method has been parsed.
+* `on_version_complete`: Invoked after the HTTP version has been parsed.
+* `on_status_complete`: Invoked after the status code has been parsed.
+* `on_header_field_complete`: Invoked after a header name has been parsed.
+* `on_header_value_complete`: Invoked after a header value has been parsed.
+* `on_chunk_header`: Invoked after a new chunk is started. The current chunk length is stored in `parser->content_length`.
+* `on_chunk_extension_name_complete`: Invoked after a chunk extension name is started.
+* `on_chunk_extension_value_complete`: Invoked after a chunk extension value is started.
+* `on_chunk_complete`: Invoked after a new chunk is received.
+* `on_reset`: Invoked after `on_message_complete` and before `on_message_begin` when a new message
+ is received on the same parser. This is not invoked for the first message of the parser.
+
+The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_USER` (error from the callback):
+
+* `on_url`: Invoked when another character of the URL is received.
+* `on_status`: Invoked when another character of the status is received.
+* `on_method`: Invoked when another character of the method is received.
+ When parser is created with `HTTP_BOTH` and the input is a response, this also invoked for the sequence `HTTP/`
+ of the first message.
+* `on_version`: Invoked when another character of the version is received.
+* `on_header_field`: Invoked when another character of a header name is received.
+* `on_header_value`: Invoked when another character of a header value is received.
+* `on_chunk_extension_name`: Invoked when another character of a chunk extension name is received.
+* `on_chunk_extension_value`: Invoked when another character of a extension value is received.
+
+The callback `on_headers_complete`, invoked when headers are completed, can return:
+
+* `0`: Proceed normally.
+* `1`: Assume that request/response has no body, and proceed to parsing the next message.
+* `2`: Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE`.
+* `-1`: Error
+* `HPE_PAUSED`: Pause the parser.
+
+### `void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)`
+
+Initialize the parser with specific type and user settings.
+
+### `uint8_t llhttp_get_type(llhttp_t* parser)`
+
+Returns the type of the parser.
+
+### `uint8_t llhttp_get_http_major(llhttp_t* parser)`
+
+Returns the major version of the HTTP protocol of the current request/response.
+
+### `uint8_t llhttp_get_http_minor(llhttp_t* parser)`
+
+Returns the minor version of the HTTP protocol of the current request/response.
+
+### `uint8_t llhttp_get_method(llhttp_t* parser)`
+
+Returns the method of the current request.
+
+### `int llhttp_get_status_code(llhttp_t* parser)`
+
+Returns the method of the current response.
+
+### `uint8_t llhttp_get_upgrade(llhttp_t* parser)`
+
+Returns `1` if request includes the `Connection: upgrade` header.
+
+### `void llhttp_reset(llhttp_t* parser)`
+
+Reset an already initialized parser back to the start state, preserving the
+existing parser type, callback settings, user data, and lenient flags.
+
+### `void llhttp_settings_init(llhttp_settings_t* settings)`
+
+Initialize the settings object.
+
+### `llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)`
+
+Parse full or partial request/response, invoking user callbacks along the way.
+
+If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing interrupts,
+and such errno is returned from `llhttp_execute()`. If `HPE_PAUSED` was used as a errno,
+the execution can be resumed with `llhttp_resume()` call.
+
+In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` is returned
+after fully parsing the request/response. If the user wishes to continue parsing,
+they need to invoke `llhttp_resume_after_upgrade()`.
+
+**if this function ever returns a non-pause type error, it will continue to return
+the same error upon each successive call up until `llhttp_init()` is called.**
+
+### `llhttp_errno_t llhttp_finish(llhttp_t* parser)`
+
+This method should be called when the other side has no further bytes to
+send (e.g. shutdown of readable side of the TCP connection.)
+
+Requests without `Content-Length` and other messages might require treating
+all incoming bytes as the part of the body, up to the last byte of the
+connection.
+
+This method will invoke `on_message_complete()` callback if the
+request was terminated safely. Otherwise a error code would be returned.
+
+
+### `int llhttp_message_needs_eof(const llhttp_t* parser)`
+
+Returns `1` if the incoming message is parsed until the last byte, and has to be completed by calling `llhttp_finish()` on EOF.
+
+### `int llhttp_should_keep_alive(const llhttp_t* parser)`
+
+Returns `1` if there might be any other messages following the last that was
+successfully parsed.
+
+### `void llhttp_pause(llhttp_t* parser)`
+
+Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set
+appropriate error reason.
+
+**Do not call this from user callbacks! User callbacks must return
+`HPE_PAUSED` if pausing is required.**
+
+### `void llhttp_resume(llhttp_t* parser)`
+
+Might be called to resume the execution after the pause in user's callback.
+
+See `llhttp_execute()` above for details.
+
+**Call this only if `llhttp_execute()` returns `HPE_PAUSED`.**
+
+### `void llhttp_resume_after_upgrade(llhttp_t* parser)`
+
+Might be called to resume the execution after the pause in user's callback.
+See `llhttp_execute()` above for details.
+
+**Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`**
+
+### `llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)`
+
+Returns the latest error.
+
+### `const char* llhttp_get_error_reason(const llhttp_t* parser)`
+
+Returns the verbal explanation of the latest returned error.
+
+**User callback should set error reason when returning the error. See
+`llhttp_set_error_reason()` for details.**
+
+### `void llhttp_set_error_reason(llhttp_t* parser, const char* reason)`
+
+Assign verbal description to the returned error. Must be called in user
+callbacks right before returning the errno.
+
+**`HPE_USER` error code might be useful in user callbacks.**
+
+### `const char* llhttp_get_error_pos(const llhttp_t* parser)`
+
+Returns the pointer to the last parsed byte before the returned error. The
+pointer is relative to the `data` argument of `llhttp_execute()`.
+
+**This method might be useful for counting the number of parsed bytes.**
+
+### `const char* llhttp_errno_name(llhttp_errno_t err)`
+
+Returns textual name of error code.
+
+### `const char* llhttp_method_name(llhttp_method_t method)`
+
+Returns textual name of HTTP method.
+
+### `const char* llhttp_status_name(llhttp_status_t status)`
+
+Returns textual name of HTTP status.
+
+### `void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient header value parsing (disabled by default).
+Lenient parsing disables header value token checks, extending llhttp's
+protocol support to highly non-compliant clients/server.
+
+No `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when
+lenient parsing is "on".
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of conflicting `Transfer-Encoding` and
+`Content-Length` headers (disabled by default).
+
+Normally `llhttp` would error when `Transfer-Encoding` is present in
+conjunction with `Content-Length`.
+
+This error is important to prevent HTTP request smuggling, but may be less desirable
+for small number of cases involving legacy servers.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of `Connection: close` and HTTP/1.0
+requests responses.
+
+Normally `llhttp` would error the HTTP request/response
+after the request/response with `Connection: close` and `Content-Length`.
+
+This is important to prevent cache poisoning attacks,
+but might interact badly with outdated and insecure clients.
+
+With this flag the extra request/response will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of `Transfer-Encoding` header.
+
+Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value
+and another value after it (either in a single header or in multiple
+headers whose value are internally joined using `, `).
+
+This is mandated by the spec to reliably determine request body size and thus
+avoid request smuggling.
+
+With this flag the extra value will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_version(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of HTTP version.
+
+Normally `llhttp` would error when the HTTP version in the request or status line
+is not `0.9`, `1.0`, `1.1` or `2.0`.
+With this flag the extra value will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will allow unsupported HTTP versions. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of additional data received after a message ends
+and keep-alive is disabled.
+
+Normally `llhttp` would error when additional unexpected data is received if the message
+contains the `Connection` header with `close` value.
+With this flag the extra data will discarded without throwing an error.
+
+**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of incomplete CRLF sequences.
+
+Normally `llhttp` would error when a CR is not followed by LF when terminating the
+request line, the status line, the headers or a chunk header.
+With this flag only a CR is required to terminate such sections.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of line separators.
+
+Normally `llhttp` would error when a LF is not preceded by CR when terminating the
+request line, the status line, the headers, a chunk header or a chunk data.
+With this flag only a LF is required to terminate such sections.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of chunks not separated via CRLF.
+
+Normally `llhttp` would error when after a chunk data a CRLF is missing before
+starting a new chunk.
+With this flag the new chunk can start immediately after the previous one.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of spaces after chunk size.
+
+Normally `llhttp` would error when after a chunk size is followed by one or more spaces are present instead of a CRLF or `;`.
+With this flag this check is disabled.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+## Build Instructions
+
+Make sure you have [Node.js](https://nodejs.org/), npm and npx installed. Then under project directory run:
+
+```sh
+npm install
+make
+```
+
+---
+
+### Bindings to other languages
+
+* Lua: [MunifTanjim/llhttp.lua][11]
+* Python: [pallas/pyllhttp][8]
+* Ruby: [metabahn/llhttp][9]
+* Rust: [JackLiar/rust-llhttp][10]
+
+### Using with CMake
+
+If you want to use this library in a CMake project as a shared library, you can use the snippet below.
+
+```
+FetchContent_Declare(llhttp
+ URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz")
+
+FetchContent_MakeAvailable(llhttp)
+
+# Link with the llhttp_shared target
+target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_shared ${PROJECT_NAME})
+```
+
+If you want to use this library in a CMake project as a static library, you can set some cache variables first.
+
+```
+FetchContent_Declare(llhttp
+ URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz")
+
+set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
+set(BUILD_STATIC_LIBS ON CACHE INTERNAL "")
+FetchContent_MakeAvailable(llhttp)
+
+# Link with the llhttp_static target
+target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_static ${PROJECT_NAME})
+```
+
+_Note that using the git repo directly (e.g., via a git repo url and tag) will not work with FetchContent_Declare because [CMakeLists.txt](./CMakeLists.txt) requires string replacements (e.g., `_RELEASE_`) before it will build._
+
+## Building on Windows
+
+### Installation
+
+* `choco install git`
+* `choco install node`
+* `choco install llvm` (or install the `C++ Clang tools for Windows` optional package from the Visual Studio 2019 installer)
+* `choco install make` (or if you have MinGW, it comes bundled)
+
+1. Ensure that `Clang` and `make` are in your system path.
+2. Using Git Bash, clone the repo to your preferred location.
+3. Cd into the cloned directory and run `npm install`
+5. Run `make`
+6. Your `repo/build` directory should now have `libllhttp.a` and `libllhttp.so` static and dynamic libraries.
+7. When building your executable, you can link to these libraries. Make sure to set the build folder as an include path when building so you can reference the declarations in `repo/build/llhttp.h`.
+
+### A simple example on linking with the library:
+
+Assuming you have an executable `main.cpp` in your current working directory, you would run: `clang++ -Os -g3 -Wall -Wextra -Wno-unused-parameter -I/path/to/llhttp/build main.cpp /path/to/llhttp/build/libllhttp.a -o main.exe`.
+
+If you are getting `unresolved external symbol` linker errors you are likely attempting to build `llhttp.c` without linking it with object files from `api.c` and `http.c`.
+
+#### LICENSE
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2018.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+[0]: https://github.com/nodejs/http-parser
+[1]: https://github.com/nodejs/llparse
+[2]: https://en.wikipedia.org/wiki/Register_allocation#Spilling
+[3]: https://en.wikipedia.org/wiki/Tail_call
+[4]: https://llvm.org/docs/LangRef.html
+[5]: https://llvm.org/docs/LangRef.html#call-instruction
+[6]: https://clang.llvm.org/
+[7]: https://github.com/nodejs/node
+[8]: https://github.com/pallas/pyllhttp
+[9]: https://github.com/metabahn/llhttp
+[10]: https://github.com/JackLiar/rust-llhttp
+[11]: https://github.com/MunifTanjim/llhttp.lua
diff --git a/llhttp/_config.yml b/llhttp/_config.yml
new file mode 100644
index 0000000..1885487
--- /dev/null
+++ b/llhttp/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-midnight \ No newline at end of file
diff --git a/llhttp/bench/index.ts b/llhttp/bench/index.ts
new file mode 100644
index 0000000..b3ff2e1
--- /dev/null
+++ b/llhttp/bench/index.ts
@@ -0,0 +1,71 @@
+import * as assert from "assert";
+import { spawnSync } from "child_process";
+import { existsSync } from "fs";
+import { resolve } from "path";
+
+function request(tpl: TemplateStringsArray): string {
+ return tpl.raw[0].replace(/^\s+/gm, '').replace(/\n/gm, '').replace(/\\r/gm, '\r').replace(/\\n/gm, '\n')
+}
+
+const urlExecutable = resolve(__dirname, "../test/tmp/url-url-c");
+const httpExecutable = resolve(__dirname, "../test/tmp/http-request-c");
+
+const httpRequests: Record<string, string> = {
+ "seanmonstar/httparse": request`
+ GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n
+ Host: www.kittyhell.com\r\n
+ User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
+ Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n
+ Accept-Encoding: gzip,deflate\r\n
+ Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n
+ Keep-Alive: 115\r\n
+ Connection: keep-alive\r\n
+ Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n\r\n
+ `,
+ "nodejs/http-parser": request`
+ POST /joyent/http-parser HTTP/1.1\r\n
+ Host: github.com\r\n
+ DNT: 1\r\n
+ Accept-Encoding: gzip, deflate, sdch\r\n
+ Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)
+ AppleWebKit/537.36 (KHTML, like Gecko)
+ Chrome/39.0.2171.65 Safari/537.36\r\n
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,
+ image/webp,*/*;q=0.8\r\n
+ Referer: https://github.com/joyent/http-parser\r\n
+ Connection: keep-alive\r\n
+ Transfer-Encoding: chunked\r\n
+ Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n
+ `
+}
+const urlRequest = "http://example.com/path/to/file?query=value#fragment";
+
+if (!existsSync(urlExecutable) || !existsSync(urlExecutable)) {
+ console.error(
+ "\x1b[31m\x1b[1mPlease run npm test in order to create required executables."
+ );
+ process.exit(1);
+}
+
+if (process.argv[2] === "loop") {
+ const reqName = process.argv[3];
+ const request = httpRequests[reqName]!;
+
+ assert(request, `Unknown request name: "${reqName}"`);
+ spawnSync(httpExecutable, ["loop", request], { stdio: "inherit" });
+ process.exit(0);
+}
+
+if (!process.argv[2] || process.argv[2] === "url") {
+ console.log("url (C)");
+ spawnSync(urlExecutable, ["bench", urlRequest], { stdio: "inherit" });
+}
+
+if (!process.argv[2] || process.argv[2] === "http") {
+ for (const [name, request] of Object.entries(httpRequests)) {
+ console.log('http: "%s" (C)', name);
+ spawnSync(httpExecutable, ["bench", request], { stdio: "inherit" });
+ }
+}
diff --git a/llhttp/bin/build_wasm.ts b/llhttp/bin/build_wasm.ts
new file mode 100644
index 0000000..a885703
--- /dev/null
+++ b/llhttp/bin/build_wasm.ts
@@ -0,0 +1,95 @@
+import { execSync } from 'child_process';
+import { copyFileSync, mkdirSync } from 'fs';
+import { join, resolve } from 'path';
+
+let platform = process.env.WASM_PLATFORM ?? '';
+const WASM_OUT = resolve(__dirname, '../build/wasm');
+const WASM_SRC = resolve(__dirname, '../');
+
+if (!platform && process.argv[2]) {
+ platform = execSync('docker info -f "{{.OSType}}/{{.Architecture}}"').toString().trim();
+}
+
+if (process.argv[2] === '--prebuild') {
+ const cmd = `docker build --platform=${platform.toString().trim()} -t llhttp_wasm_builder .`;
+
+ /* tslint:disable-next-line no-console */
+ console.log(`> ${cmd}\n\n`);
+ execSync(cmd, { stdio: 'inherit' });
+
+ process.exit(0);
+}
+
+if (process.argv[2] === '--setup') {
+ try {
+ mkdirSync(join(WASM_SRC, 'build'));
+ process.exit(0);
+ } catch (error) {
+ if (error.code !== 'EEXIST') {
+ throw error;
+ }
+ process.exit(0);
+ }
+}
+
+if (process.argv[2] === '--docker') {
+ let cmd = `docker run --rm -it --platform=${platform.toString().trim()}`;
+ // Try to avoid root permission problems on compiled assets
+ // when running on linux.
+ // It will work flawessly if uid === gid === 1000
+ // there will be some warnings otherwise.
+ if (process.platform === 'linux') {
+ cmd += ` --user ${process.getuid()}:${process.getegid()}`;
+ }
+ cmd += ` --mount type=bind,source=${WASM_SRC}/build,target=/home/node/llhttp/build llhttp_wasm_builder npm run wasm`;
+
+ /* tslint:disable-next-line no-console */
+ console.log(`> ${cmd}\n\n`);
+ execSync(cmd, { cwd: WASM_SRC, stdio: 'inherit' });
+ process.exit(0);
+}
+
+try {
+ mkdirSync(WASM_OUT);
+} catch (error) {
+ if (error.code !== 'EEXIST') {
+ throw error;
+ }
+}
+
+// Build ts
+execSync('npm run build', { cwd: WASM_SRC, stdio: 'inherit' });
+
+// Build wasm binary
+execSync(
+ `clang \
+ --sysroot=/usr/share/wasi-sysroot \
+ -target wasm32-unknown-wasi \
+ -Ofast \
+ -fno-exceptions \
+ -fvisibility=hidden \
+ -mexec-model=reactor \
+ -Wl,-error-limit=0 \
+ -Wl,-O3 \
+ -Wl,--lto-O3 \
+ -Wl,--strip-all \
+ -Wl,--allow-undefined \
+ -Wl,--export-dynamic \
+ -Wl,--export-table \
+ -Wl,--export=malloc \
+ -Wl,--export=free \
+ -Wl,--no-entry \
+ ${join(WASM_SRC, 'build', 'c')}/*.c \
+ ${join(WASM_SRC, 'src', 'native')}/*.c \
+ -I${join(WASM_SRC, 'build')} \
+ -o ${join(WASM_OUT, 'llhttp.wasm')}`,
+ { stdio: 'inherit' },
+);
+
+// Copy constants for `.js` and `.ts` users.
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.js'), join(WASM_OUT, 'constants.js'));
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.js.map'), join(WASM_OUT, 'constants.js.map'));
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.d.ts'), join(WASM_OUT, 'constants.d.ts'));
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.js'), join(WASM_OUT, 'utils.js'));
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.js.map'), join(WASM_OUT, 'utils.js.map'));
+copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.d.ts'), join(WASM_OUT, 'utils.d.ts'));
diff --git a/llhttp/bin/generate.ts b/llhttp/bin/generate.ts
new file mode 100755
index 0000000..edb7f49
--- /dev/null
+++ b/llhttp/bin/generate.ts
@@ -0,0 +1,47 @@
+#!/usr/bin/env -S npx ts-node
+
+import { mkdirSync, readFileSync, writeFileSync } from 'fs';
+import { LLParse } from 'llparse';
+import { dirname, resolve } from 'path';
+import { parse } from 'semver';
+import { CHeaders, HTTP } from '../src/llhttp';
+
+const C_FILE = resolve(__dirname, '../build/c/llhttp.c');
+const HEADER_FILE = resolve(__dirname, '../build/llhttp.h');
+
+const pkg = JSON.parse(
+ readFileSync(resolve(__dirname, '..', 'package.json')).toString(),
+);
+const version = parse(pkg.version)!;
+const llparse = new LLParse('llhttp__internal');
+
+const cHeaders = new CHeaders();
+const nativeHeaders = readFileSync(resolve(__dirname, '../src/native/api.h'));
+const generated = llparse.build(new HTTP(llparse).build().entry, {
+ c: {
+ header: 'llhttp',
+ },
+ debug: process.env.LLPARSE_DEBUG ? 'llhttp__debug' : undefined,
+ headerGuard: 'INCLUDE_LLHTTP_ITSELF_H_',
+});
+
+const headers = `
+#ifndef INCLUDE_LLHTTP_H_
+#define INCLUDE_LLHTTP_H_
+
+#define LLHTTP_VERSION_MAJOR ${version.major}
+#define LLHTTP_VERSION_MINOR ${version.minor}
+#define LLHTTP_VERSION_PATCH ${version.patch}
+
+${generated.header}
+
+${cHeaders.build()}
+
+${nativeHeaders}
+
+#endif /* INCLUDE_LLHTTP_H_ */
+`;
+
+mkdirSync(dirname(C_FILE), { recursive: true });
+writeFileSync(HEADER_FILE, headers);
+writeFileSync(C_FILE, generated.c);
diff --git a/llhttp/docs/releasing.md b/llhttp/docs/releasing.md
new file mode 100644
index 0000000..f83e0f7
--- /dev/null
+++ b/llhttp/docs/releasing.md
@@ -0,0 +1,65 @@
+# How to release a new version of llhttp
+
+## What does releasing involves?
+
+These are the required steps to release a new version of llhttp:
+
+1. Increase the version number.
+2. Build it locally.
+3. Create a new build and push it to GitHub.
+4. Create a new release on GitHub release.
+
+> Do not try to execute the commands in the Makefile manually. This is really error-prone!
+
+## Which commands to run?
+
+First of all, make sure you have [GitHub CLI](https://cli.github.com) installed and configured. While this is not strictly necessary, it will make your life way easier.
+
+As a preliminary check, run the build command and execute the test suite locally:
+
+```
+npm run build
+npm test
+```
+
+If all goes good, you are ready to go!
+
+To release a new version of llhttp, first increase the version using `npm`Ā and make sure it also execute the `postversion` script. Unless you have some very specific setup, this should happen automatically, which means the following command will suffice:
+
+```
+npm version [major|minor|patch]
+```
+
+The command will increase the version and then will create a new release branch on GitHub.
+
+> Even thought there is a package on NPM, it is not updated anymore. NEVER RUN `npm publish`!
+
+It's now time to create the release on GitHub. If you DON'T have GitHub CLI available, skip to the next section, otherwise run the following command:
+
+```
+npm run github-release
+```
+
+This command will create a draft release on GitHub and then show it in your browser so you can review and publish it.
+
+Congratulation, you are all set!
+
+## Create a GitHub release without GitHub CLI
+
+> From now on, `$VERSION` will be the new version you are trying to create, including the leading letter, for instance `v6.0.9`.
+
+If you don't want to or can't use GitHub CLI, you can still create the release on GitHub following this procedure.
+
+1. Go on GitHub and start creating a new release which targets tag `$VERSION`. Generate the notes using the `Generate release notes` button.
+
+2. At the bottom of the generated notes, make sure the previous and current version in the notes are correct.
+
+ The last line should be something like this: `**Full Changelog**: https://github.com/nodejs/llhttp/compare/v6.0.8...v6.0.9`
+
+ In this case it says we are creating release `v6.0.9`Ā and we are showing the changes between `v6.0.8` and `v6.0.9`.
+
+3. Change the target of the release to point to tag `release/$VERSION`.
+
+4. Review and then publish the release.
+
+Congratulation, you are all set! \ No newline at end of file
diff --git a/llhttp/examples/wasm.ts b/llhttp/examples/wasm.ts
new file mode 100644
index 0000000..995fed8
--- /dev/null
+++ b/llhttp/examples/wasm.ts
@@ -0,0 +1,248 @@
+/**
+ * A minimal Parser that mimicks a small fraction of the Node.js parser
+ * API.
+ * To run:
+ * - `npm run build-wasm`
+ * - `npx ts-node examples/wasm.ts`
+ */
+import { readFileSync } from 'fs';
+import { resolve } from 'path';
+import * as constants from '../build/wasm/constants';
+
+const bin = readFileSync(resolve(__dirname, '../build/wasm/llhttp.wasm'));
+const mod = new WebAssembly.Module(bin);
+
+const REQUEST = constants.TYPE.REQUEST;
+const RESPONSE = constants.TYPE.RESPONSE;
+const kOnMessageBegin = 0;
+const kOnHeaders = 1;
+const kOnHeadersComplete = 2;
+const kOnBody = 3;
+const kOnMessageComplete = 4;
+const kOnExecute = 5;
+
+const kPtr = Symbol('kPtr');
+const kUrl = Symbol('kUrl');
+const kStatusMessage = Symbol('kStatusMessage');
+const kHeadersFields = Symbol('kHeadersFields');
+const kHeadersValues = Symbol('kHeadersValues');
+const kBody = Symbol('kBody');
+const kReset = Symbol('kReset');
+const kCheckErr = Symbol('kCheckErr');
+
+const cstr = (ptr: number, len: number): string =>
+ Buffer.from(memory.buffer, ptr, len).toString();
+
+const wasm_on_message_begin = (p: number) => {
+ const i = instMap.get(p);
+ i[kReset]();
+ return i[kOnMessageBegin]();
+};
+
+const wasm_on_url = (p: number, at: number, length: number) => {
+ instMap.get(p)[kUrl] = cstr(at, length);
+ return 0;
+};
+
+const wasm_on_status = (p: number, at: number, length: number) => {
+ instMap.get(p)[kStatusMessage] = cstr(at, length);
+ return 0;
+};
+
+const wasm_on_header_field = (p: number, at: number, length: number) => {
+ const i= instMap.get(p)
+ i[kHeadersFields].push(cstr(at, length));
+ return 0;
+};
+
+const wasm_on_header_value = (p: number, at: number, length: number) => {
+ const i = instMap.get(p);
+ i[kHeadersValues].push(cstr(at, length));
+ return 0;
+};
+
+const wasm_on_headers_complete = (p: number) => {
+ const i = instMap.get(p);
+ const type = get_type(p);
+ const versionMajor = get_version_major(p);
+ const versionMinor = get_version_minor(p);
+ const rawHeaders = [];
+ let method;
+ let url;
+ let statusCode;
+ let statusMessage;
+ const upgrade = get_upgrade(p);
+ const shouldKeepAlive = should_keep_alive(p);
+
+ for (let c = 0; c < i[kHeadersFields].length; c++) {
+ rawHeaders.push(i[kHeadersFields][c], i[kHeadersValues][c])
+ }
+
+ if (type === HTTPParser.REQUEST) {
+ method = constants.METHODS[get_method(p)];
+ url = i[kUrl];
+ } else if (type === HTTPParser.RESPONSE) {
+ statusCode = get_status_code(p);
+ statusMessage = i[kStatusMessage];
+ }
+ return i[kOnHeadersComplete](versionMajor, versionMinor, rawHeaders, method,
+url, statusCode, statusMessage, upgrade, shouldKeepAlive);
+};
+
+const wasm_on_body = (p: number, at: number, length: number) => {
+ const i = instMap.get(p);
+ const body = Buffer.from(memory.buffer, at, length);
+ return i[kOnBody](body);
+};
+
+const wasm_on_message_complete = (p: number) => {
+ return instMap.get(p)[kOnMessageComplete]();
+};
+
+const instMap = new Map();
+
+const inst = new WebAssembly.Instance(mod, {
+ env: {
+ wasm_on_message_begin,
+ wasm_on_url,
+ wasm_on_status,
+ wasm_on_header_field,
+ wasm_on_header_value,
+ wasm_on_headers_complete,
+ wasm_on_body,
+ wasm_on_message_complete,
+ },
+});
+
+const memory = inst.exports.memory as any;
+const alloc = inst.exports.llhttp_alloc as CallableFunction;
+const malloc = inst.exports.malloc as CallableFunction;
+const execute = inst.exports.llhttp_execute as CallableFunction;
+const get_type = inst.exports.llhttp_get_type as CallableFunction;
+const get_upgrade = inst.exports.llhttp_get_upgrade as CallableFunction;
+const should_keep_alive = inst.exports.llhttp_should_keep_alive as CallableFunction;
+const get_method = inst.exports.llhttp_get_method as CallableFunction;
+const get_status_code = inst.exports.llhttp_get_status_code as CallableFunction;
+const get_version_minor = inst.exports.llhttp_get_http_minor as CallableFunction;
+const get_version_major = inst.exports.llhttp_get_http_major as CallableFunction;
+const get_error_reason = inst.exports.llhttp_get_error_reason as CallableFunction;
+const free = inst.exports.free as CallableFunction;
+const initialize = inst.exports._initialize as CallableFunction;
+
+initialize(); // wasi reactor
+
+class HTTPParser {
+ static REQUEST = REQUEST;
+ static RESPONSE = RESPONSE;
+ static kOnMessageBegin = kOnMessageBegin;
+ static kOnHeaders = kOnHeaders;
+ static kOnHeadersComplete = kOnHeadersComplete;
+ static kOnBody = kOnBody;
+ static kOnMessageComplete = kOnMessageComplete;
+ static kOnExecute = kOnExecute;
+
+ [kPtr]: number;
+ [kUrl]: string;
+ [kStatusMessage]: null|string;
+ [kHeadersFields]: []|[string];
+ [kHeadersValues]: []|[string];
+ [kBody]: null|Buffer;
+
+ constructor(type: constants.TYPE) {
+ this[kPtr] = alloc(constants.TYPE[type]);
+ instMap.set(this[kPtr], this);
+
+ this[kUrl] = '';
+ this[kStatusMessage] = null;
+ this[kHeadersFields] = [];
+ this[kHeadersValues] = [];
+ this[kBody] = null;
+ }
+
+ [kReset]() {
+ this[kUrl] = '';
+ this[kStatusMessage] = null;
+ this[kHeadersFields] = [];
+ this[kHeadersValues] = [];
+ this[kBody] = null;
+ }
+
+ [kOnMessageBegin]() {
+ return 0;
+ }
+
+ [kOnHeaders](rawHeaders: [string]) {}
+
+ [kOnHeadersComplete](versionMajor: number, versionMinor: number, rawHeaders: [string], method: string,
+ url: string, statusCode: number, statusMessage: string, upgrade: boolean, shouldKeepAlive: boolean) {
+ return 0;
+ }
+
+ [kOnBody](body: Buffer) {
+ this[kBody] = body;
+ return 0;
+ }
+
+ [kOnMessageComplete]() {
+ return 0;
+ }
+
+ destroy() {
+ instMap.delete(this[kPtr]);
+ free(this[kPtr]);
+ }
+
+ execute(data: Buffer) {
+ const ptr = malloc(data.byteLength);
+ const u8 = new Uint8Array(memory.buffer);
+ u8.set(data, ptr);
+ const ret = execute(this[kPtr], ptr, data.length);
+ free(ptr);
+ this[kCheckErr](ret);
+ return ret;
+ }
+
+ [kCheckErr](n: number) {
+ if (n === constants.ERROR.OK) {
+ return;
+ }
+ const ptr = get_error_reason(this[kPtr]);
+ const u8 = new Uint8Array(memory.buffer);
+ const len = u8.indexOf(0, ptr) - ptr;
+ throw new Error(cstr(ptr, len));
+ }
+}
+
+
+{
+ const p = new HTTPParser(HTTPParser.REQUEST);
+
+ p.execute(Buffer.from([
+ 'POST /owo HTTP/1.1',
+ 'X: Y',
+ 'Content-Length: 9',
+ '',
+ 'uh, meow?',
+ '',
+ ].join('\r\n')));
+
+ console.log(p);
+
+ p.destroy();
+}
+
+{
+ const p = new HTTPParser(HTTPParser.RESPONSE);
+
+ p.execute(Buffer.from([
+ 'HTTP/1.1 200 OK',
+ 'X: Y',
+ 'Content-Length: 9',
+ '',
+ 'uh, meow?'
+ ].join('\r\n')));
+
+ console.log(p);
+
+ p.destroy();
+}
diff --git a/llhttp/images/http-loose-none.png b/llhttp/images/http-loose-none.png
new file mode 100644
index 0000000..3187765
--- /dev/null
+++ b/llhttp/images/http-loose-none.png
Binary files differ
diff --git a/llhttp/images/http-strict-none.png b/llhttp/images/http-strict-none.png
new file mode 100644
index 0000000..8f2aacf
--- /dev/null
+++ b/llhttp/images/http-strict-none.png
Binary files differ
diff --git a/llhttp/libllhttp.pc.in b/llhttp/libllhttp.pc.in
new file mode 100644
index 0000000..67d280a
--- /dev/null
+++ b/llhttp/libllhttp.pc.in
@@ -0,0 +1,10 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@
+includedir=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_INCLUDEDIR@
+
+Name: libllhttp
+Description: Node.js llhttp Library
+Version: @PROJECT_VERSION@
+Libs: -L${libdir} -lllhttp
+Cflags: -I${includedir} \ No newline at end of file
diff --git a/llhttp/package-lock.json b/llhttp/package-lock.json
new file mode 100644
index 0000000..a49ed36
--- /dev/null
+++ b/llhttp/package-lock.json
@@ -0,0 +1,2995 @@
+{
+ "name": "llhttp",
+ "version": "9.1.3",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "version": "9.1.3",
+ "license": "MIT",
+ "dependencies": {
+ "@types/semver": "^5.5.0",
+ "llparse": "^7.1.1",
+ "semver": "^5.7.1"
+ },
+ "devDependencies": {
+ "@types/mocha": "^5.2.7",
+ "@types/node": "^10.17.52",
+ "javascript-stringify": "^2.0.1",
+ "llparse-dot": "^1.0.1",
+ "llparse-test-fixture": "^5.0.1",
+ "mdgator": "^1.1.2",
+ "mocha": "^10.2.0",
+ "ts-node": "^7.0.1",
+ "tslint": "^5.20.1",
+ "typescript": "^3.9.9"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
+ "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
+ "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.14.0",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
+ "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-0.0.4.tgz",
+ "integrity": "sha512-FWR7QB7EqBRq1s9BMk0ccOSOuRLfVEWYpHQYpFPaXtCoqN6dJx2ttdsdQbUxLLnAlKpYeVjveGGhQ3583TTa7g==",
+ "dev": true
+ },
+ "node_modules/@types/mocha": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
+ "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "10.17.59",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.59.tgz",
+ "integrity": "sha512-7Uc8IRrL8yZz5ti45RaFxpbU8TxlzdC3HvxV+hOWo1EyLsuKv/w7y0n+TwZzwL3vdx3oZ2k3ubxPq131hNtXyg==",
+ "dev": true
+ },
+ "node_modules/@types/semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ=="
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/binary-search": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
+ "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "node_modules/builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
+ "dev": true
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esm": {
+ "version": "3.2.25",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/javascript-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
+ "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
+ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
+ "dev": true,
+ "dependencies": {
+ "uc.micro": "^1.0.1"
+ }
+ },
+ "node_modules/llparse": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/llparse/-/llparse-7.1.1.tgz",
+ "integrity": "sha512-lBxN5O6sKq6KSOaRFIGczoVpO/U/37mHhjJioQbPuiXdfZmwzP1zC3txV9xx778TRNFENzeCM0Uoo+mE1rfJOA==",
+ "dependencies": {
+ "debug": "^4.2.0",
+ "llparse-frontend": "^3.0.0"
+ }
+ },
+ "node_modules/llparse-builder": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz",
+ "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==",
+ "dependencies": {
+ "@types/debug": "4.1.5 ",
+ "binary-search": "^1.3.6",
+ "debug": "^4.2.0"
+ }
+ },
+ "node_modules/llparse-dot": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/llparse-dot/-/llparse-dot-1.0.1.tgz",
+ "integrity": "sha512-3e271C2LuDWBzhxaCUDzjpufamoEBuTYQz83QyMixI/i99BntCEk6ngHWOhhDb0XdtNNh6qAfRmXyjgNP+Nxpw==",
+ "dev": true,
+ "dependencies": {
+ "llparse-builder": "^1.0.0"
+ }
+ },
+ "node_modules/llparse-frontend": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-3.0.0.tgz",
+ "integrity": "sha512-G/o0Po2C+G5OtP8MJeQDjDf5qwDxcO7K6x4r6jqGsJwxk7yblbJnRqpmye7G/lZ8dD0Hv5neY4/KB5BhDmEc9Q==",
+ "dependencies": {
+ "debug": "^3.2.6",
+ "llparse-builder": "^1.5.2"
+ }
+ },
+ "node_modules/llparse-frontend/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/llparse-test-fixture": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.0.2.tgz",
+ "integrity": "sha512-61KI5J/b5uyRktD0y1EezleEW6UfaxhHkn1adLKNVemRZzklE+SpLakr251qo04kb9jN/ytk8lllgK+yFOj4cQ==",
+ "dev": true,
+ "dependencies": {
+ "esm": "^3.2.25",
+ "llparse": "^7.0.0",
+ "yargs": "^15.4.1"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "node_modules/markdown-it": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
+ "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "entities": "~1.1.1",
+ "linkify-it": "^2.0.0",
+ "mdurl": "^1.0.1",
+ "uc.micro": "^1.0.5"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.js"
+ }
+ },
+ "node_modules/mdgator": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/mdgator/-/mdgator-1.1.2.tgz",
+ "integrity": "sha512-S2GvsLIznUQ2McXfpe6BCD+IqhnRuHcBO7krqnvnsHgDpjjO1mLhr0vZtVa5ca4WZET037g3G+94DznpicKkOA==",
+ "dev": true,
+ "dependencies": {
+ "@types/markdown-it": "0.0.4",
+ "markdown-it": "^8.4.1"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
+ "dev": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+ "dev": true
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/mocha/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/mocha/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/mocha/node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "node_modules/resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/supports-color/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+ "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^1.0.0",
+ "buffer-from": "^1.1.0",
+ "diff": "^3.1.0",
+ "make-error": "^1.1.1",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.5.6",
+ "yn": "^2.0.0"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tslint": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz",
+ "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^4.0.1",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.29.0"
+ },
+ "bin": {
+ "tslint": "bin/tslint"
+ },
+ "engines": {
+ "node": ">=4.8.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev"
+ }
+ },
+ "node_modules/tslint/node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "3.9.9",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
+ "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "dev": true
+ },
+ "node_modules/which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
+ "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.12.13"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
+ "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
+ "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.14.0",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@types/debug": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
+ "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
+ },
+ "@types/markdown-it": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-0.0.4.tgz",
+ "integrity": "sha512-FWR7QB7EqBRq1s9BMk0ccOSOuRLfVEWYpHQYpFPaXtCoqN6dJx2ttdsdQbUxLLnAlKpYeVjveGGhQ3583TTa7g==",
+ "dev": true
+ },
+ "@types/mocha": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
+ "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "10.17.59",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.59.tgz",
+ "integrity": "sha512-7Uc8IRrL8yZz5ti45RaFxpbU8TxlzdC3HvxV+hOWo1EyLsuKv/w7y0n+TwZzwL3vdx3oZ2k3ubxPq131hNtXyg==",
+ "dev": true
+ },
+ "@types/semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ=="
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "binary-search": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
+ "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
+ "dev": true
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esm": {
+ "version": "3.2.25",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-core-module": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true
+ },
+ "is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true
+ },
+ "javascript-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
+ "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "linkify-it": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
+ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
+ "dev": true,
+ "requires": {
+ "uc.micro": "^1.0.1"
+ }
+ },
+ "llparse": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/llparse/-/llparse-7.1.1.tgz",
+ "integrity": "sha512-lBxN5O6sKq6KSOaRFIGczoVpO/U/37mHhjJioQbPuiXdfZmwzP1zC3txV9xx778TRNFENzeCM0Uoo+mE1rfJOA==",
+ "requires": {
+ "debug": "^4.2.0",
+ "llparse-frontend": "^3.0.0"
+ }
+ },
+ "llparse-builder": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.5.2.tgz",
+ "integrity": "sha512-i862UNC3YUEdlfK/NUCJxlKjtWjgAI9AJXDRgjcfRHfwFt4Sf8eFPTRsc91/2R9MBZ0kyFdfhi8SVhMsZf1gNQ==",
+ "requires": {
+ "@types/debug": "4.1.5 ",
+ "binary-search": "^1.3.6",
+ "debug": "^4.2.0"
+ }
+ },
+ "llparse-dot": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/llparse-dot/-/llparse-dot-1.0.1.tgz",
+ "integrity": "sha512-3e271C2LuDWBzhxaCUDzjpufamoEBuTYQz83QyMixI/i99BntCEk6ngHWOhhDb0XdtNNh6qAfRmXyjgNP+Nxpw==",
+ "dev": true,
+ "requires": {
+ "llparse-builder": "^1.0.0"
+ }
+ },
+ "llparse-frontend": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/llparse-frontend/-/llparse-frontend-3.0.0.tgz",
+ "integrity": "sha512-G/o0Po2C+G5OtP8MJeQDjDf5qwDxcO7K6x4r6jqGsJwxk7yblbJnRqpmye7G/lZ8dD0Hv5neY4/KB5BhDmEc9Q==",
+ "requires": {
+ "debug": "^3.2.6",
+ "llparse-builder": "^1.5.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "llparse-test-fixture": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/llparse-test-fixture/-/llparse-test-fixture-5.0.2.tgz",
+ "integrity": "sha512-61KI5J/b5uyRktD0y1EezleEW6UfaxhHkn1adLKNVemRZzklE+SpLakr251qo04kb9jN/ytk8lllgK+yFOj4cQ==",
+ "dev": true,
+ "requires": {
+ "esm": "^3.2.25",
+ "llparse": "^7.0.0",
+ "yargs": "^15.4.1"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "markdown-it": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
+ "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "entities": "~1.1.1",
+ "linkify-it": "^2.0.0",
+ "mdurl": "^1.0.1",
+ "uc.micro": "^1.0.5"
+ }
+ },
+ "mdgator": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/mdgator/-/mdgator-1.1.2.tgz",
+ "integrity": "sha512-S2GvsLIznUQ2McXfpe6BCD+IqhnRuHcBO7krqnvnsHgDpjjO1mLhr0vZtVa5ca4WZET037g3G+94DznpicKkOA==",
+ "dev": true,
+ "requires": {
+ "@types/markdown-it": "0.0.4",
+ "markdown-it": "^8.4.1"
+ }
+ },
+ "mdurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ },
+ "dependencies": {
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ }
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
+ },
+ "serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ }
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "ts-node": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+ "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.0",
+ "buffer-from": "^1.1.0",
+ "diff": "^3.1.0",
+ "make-error": "^1.1.1",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.5.6",
+ "yn": "^2.0.0"
+ }
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "tslint": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz",
+ "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^4.0.1",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.29.0"
+ },
+ "dependencies": {
+ "diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true
+ }
+ }
+ },
+ "tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "typescript": {
+ "version": "3.9.9",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
+ "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
+ "dev": true
+ },
+ "uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "dev": true
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
+ },
+ "yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true
+ },
+ "decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true
+ }
+ }
+ },
+ "yn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+ "dev": true
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
+ }
+ }
+}
diff --git a/llhttp/package.json b/llhttp/package.json
new file mode 100644
index 0000000..be715b8
--- /dev/null
+++ b/llhttp/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "llhttp",
+ "version": "9.1.3",
+ "description": "HTTP parser in LLVM IR",
+ "main": "lib/llhttp.js",
+ "types": "lib/llhttp.d.ts",
+ "files": [
+ "lib",
+ "src"
+ ],
+ "scripts": {
+ "bench": "ts-node bench/",
+ "build": "ts-node bin/generate.ts",
+ "build-ts": "tsc",
+ "prebuild-wasm": "npm run wasm -- --prebuild && npm run wasm -- --setup",
+ "build-wasm": "npm run wasm -- --docker",
+ "wasm": "ts-node bin/build_wasm.ts",
+ "clean": "rm -rf lib && rm -rf test/tmp",
+ "prepare": "npm run clean && npm run build-ts",
+ "lint": "tslint -c tslint.json bin/*.ts src/*.ts src/**/*.ts test/*.ts test/**/*.ts",
+ "lint-fix": "tslint --fix -c tslint.json bin/*.ts src/*.ts src/**/*.ts test/*.ts test/**/*.ts",
+ "mocha": "mocha --timeout=10000 -r ts-node/register/type-check --reporter progress test/*-test.ts",
+ "test": "npm run mocha && npm run lint",
+ "postversion": "RELEASE=`node -e \"process.stdout.write(require('./package').version)\"` make -B postversion",
+ "github-release": "RELEASE_V=`node -e \"process.stdout.write('v' + require('./package').version)\"` make github-release"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/nodejs/llhttp.git"
+ },
+ "keywords": [
+ "http",
+ "llvm",
+ "ir",
+ "llparse"
+ ],
+ "author": "Fedor Indutny <fedor@indutny.com> (http://darksi.de/)",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/nodejs/llhttp/issues"
+ },
+ "homepage": "https://github.com/nodejs/llhttp#readme",
+ "devDependencies": {
+ "@types/mocha": "^5.2.7",
+ "@types/node": "^10.17.52",
+ "javascript-stringify": "^2.0.1",
+ "llparse-dot": "^1.0.1",
+ "llparse-test-fixture": "^5.0.1",
+ "mdgator": "^1.1.2",
+ "mocha": "^10.2.0",
+ "ts-node": "^7.0.1",
+ "tslint": "^5.20.1",
+ "typescript": "^3.9.9"
+ },
+ "dependencies": {
+ "@types/semver": "^5.5.0",
+ "llparse": "^7.1.1",
+ "semver": "^5.7.1"
+ }
+}
diff --git a/llhttp/src/common.gypi b/llhttp/src/common.gypi
new file mode 100644
index 0000000..ef7549f
--- /dev/null
+++ b/llhttp/src/common.gypi
@@ -0,0 +1,46 @@
+{
+ 'target_defaults': {
+ 'default_configuration': 'Debug',
+ 'configurations': {
+ # TODO: hoist these out and put them somewhere common, because
+ # RuntimeLibrary MUST MATCH across the entire project
+ 'Debug': {
+ 'defines': [ 'DEBUG', '_DEBUG' ],
+ 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 1, # static debug
+ },
+ },
+ },
+ 'Release': {
+ 'defines': [ 'NDEBUG' ],
+ 'cflags': [ '-Wall', '-Wextra', '-O3' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 0, # static release
+ },
+ },
+ }
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # Compile as C++. llhttp.c is actually C99, but C++ is
+ # close enough in this case.
+ 'CompileAs': 2,
+ },
+ 'VCLibrarianTool': {
+ },
+ 'VCLinkerTool': {
+ 'GenerateDebugInformation': 'true',
+ },
+ },
+ 'conditions': [
+ ['OS == "win"', {
+ 'defines': [
+ 'WIN32'
+ ],
+ }]
+ ],
+ },
+}
diff --git a/llhttp/src/llhttp.gyp b/llhttp/src/llhttp.gyp
new file mode 100644
index 0000000..c7b8800
--- /dev/null
+++ b/llhttp/src/llhttp.gyp
@@ -0,0 +1,22 @@
+{
+ 'variables': {
+ 'llhttp_sources': [
+ 'src/llhttp.c',
+ 'src/api.c',
+ 'src/http.c',
+ ]
+ },
+ 'targets': [
+ {
+ 'target_name': 'llhttp',
+ 'type': 'static_library',
+ 'include_dirs': [ '.', 'include' ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [ 'include' ],
+ },
+ 'sources': [
+ '<@(llhttp_sources)',
+ ],
+ },
+ ]
+}
diff --git a/llhttp/src/llhttp.ts b/llhttp/src/llhttp.ts
new file mode 100644
index 0000000..ba36b01
--- /dev/null
+++ b/llhttp/src/llhttp.ts
@@ -0,0 +1,7 @@
+import * as constants from './llhttp/constants';
+
+export { constants };
+
+export { HTTP } from './llhttp/http';
+export { URL } from './llhttp/url';
+export { CHeaders } from './llhttp/c-headers';
diff --git a/llhttp/src/llhttp/c-headers.ts b/llhttp/src/llhttp/c-headers.ts
new file mode 100644
index 0000000..fad66de
--- /dev/null
+++ b/llhttp/src/llhttp/c-headers.ts
@@ -0,0 +1,106 @@
+import * as constants from './constants';
+import { enumToMap, IEnumMap } from './utils';
+
+type Encoding = 'none' | 'hex';
+
+export class CHeaders {
+ public build(): string {
+ let res = '';
+
+ res += '#ifndef LLLLHTTP_C_HEADERS_\n';
+ res += '#define LLLLHTTP_C_HEADERS_\n';
+
+ res += '#ifdef __cplusplus\n';
+ res += 'extern "C" {\n';
+ res += '#endif\n';
+
+ res += '\n';
+
+ const errorMap = enumToMap(constants.ERROR);
+ const methodMap = enumToMap(constants.METHODS);
+ const httpMethodMap = enumToMap(constants.METHODS, constants.METHODS_HTTP, [
+ constants.METHODS.PRI,
+ ]);
+ const rtspMethodMap = enumToMap(constants.METHODS, constants.METHODS_RTSP);
+ const statusMap = enumToMap(constants.STATUSES, constants.STATUSES_HTTP);
+
+ res += this.buildEnum('llhttp_errno', 'HPE', errorMap);
+ res += '\n';
+ res += this.buildEnum('llhttp_flags', 'F', enumToMap(constants.FLAGS),
+ 'hex');
+ res += '\n';
+ res += this.buildEnum('llhttp_lenient_flags', 'LENIENT',
+ enumToMap(constants.LENIENT_FLAGS), 'hex');
+ res += '\n';
+ res += this.buildEnum('llhttp_type', 'HTTP',
+ enumToMap(constants.TYPE));
+ res += '\n';
+ res += this.buildEnum('llhttp_finish', 'HTTP_FINISH',
+ enumToMap(constants.FINISH));
+ res += '\n';
+ res += this.buildEnum('llhttp_method', 'HTTP', methodMap);
+ res += '\n';
+ res += this.buildEnum('llhttp_status', 'HTTP_STATUS', statusMap);
+
+ res += '\n';
+
+ res += this.buildMap('HTTP_ERRNO', errorMap);
+ res += '\n';
+ res += this.buildMap('HTTP_METHOD', httpMethodMap);
+ res += '\n';
+ res += this.buildMap('RTSP_METHOD', rtspMethodMap);
+ res += '\n';
+ res += this.buildMap('HTTP_ALL_METHOD', methodMap);
+ res += '\n';
+ res += this.buildMap('HTTP_STATUS', statusMap);
+
+ res += '\n';
+
+ res += '#ifdef __cplusplus\n';
+ res += '} /* extern "C" */\n';
+ res += '#endif\n';
+ res += '#endif /* LLLLHTTP_C_HEADERS_ */\n';
+
+ return res;
+ }
+
+ private buildEnum(name: string, prefix: string, map: IEnumMap,
+ encoding: Encoding = 'none'): string {
+ let res = '';
+
+ res += `enum ${name} {\n`;
+ const keys = Object.keys(map);
+ const keysLength = keys.length;
+ for (let i = 0; i < keysLength; i++) {
+ const key = keys[i];
+ const isLast = i === keysLength - 1;
+
+ let value: number | string = map[key];
+
+ if (encoding === 'hex') {
+ value = `0x${value.toString(16)}`;
+ }
+
+ res += ` ${prefix}_${key.replace(/-/g, '')} = ${value}`;
+ if (!isLast) {
+ res += ',\n';
+ }
+ }
+ res += '\n};\n';
+ res += `typedef enum ${name} ${name}_t;\n`;
+
+ return res;
+ }
+
+ private buildMap(name: string, map: IEnumMap): string {
+ let res = '';
+
+ res += `#define ${name}_MAP(XX) \\\n`;
+ for (const [key, value] of Object.entries(map)) {
+ res += ` XX(${value!}, ${key.replace(/-/g, '')}, ${key}) \\\n`;
+ }
+ res += '\n';
+
+ return res;
+ }
+}
diff --git a/llhttp/src/llhttp/constants.ts b/llhttp/src/llhttp/constants.ts
new file mode 100644
index 0000000..00fc523
--- /dev/null
+++ b/llhttp/src/llhttp/constants.ts
@@ -0,0 +1,540 @@
+import { enumToMap, IEnumMap } from './utils';
+
+// C headers
+
+export enum ERROR {
+ OK = 0,
+ INTERNAL = 1,
+ STRICT = 2,
+ CR_EXPECTED = 25,
+ LF_EXPECTED = 3,
+ UNEXPECTED_CONTENT_LENGTH = 4,
+ UNEXPECTED_SPACE = 30,
+ CLOSED_CONNECTION = 5,
+ INVALID_METHOD = 6,
+ INVALID_URL = 7,
+ INVALID_CONSTANT = 8,
+ INVALID_VERSION = 9,
+ INVALID_HEADER_TOKEN = 10,
+ INVALID_CONTENT_LENGTH = 11,
+ INVALID_CHUNK_SIZE = 12,
+ INVALID_STATUS = 13,
+ INVALID_EOF_STATE = 14,
+ INVALID_TRANSFER_ENCODING = 15,
+
+ CB_MESSAGE_BEGIN = 16,
+ CB_HEADERS_COMPLETE = 17,
+ CB_MESSAGE_COMPLETE = 18,
+ CB_CHUNK_HEADER = 19,
+ CB_CHUNK_COMPLETE = 20,
+
+ PAUSED = 21,
+ PAUSED_UPGRADE = 22,
+ PAUSED_H2_UPGRADE = 23,
+
+ USER = 24,
+
+ CB_URL_COMPLETE = 26,
+ CB_STATUS_COMPLETE = 27,
+ CB_METHOD_COMPLETE = 32,
+ CB_VERSION_COMPLETE = 33,
+ CB_HEADER_FIELD_COMPLETE = 28,
+ CB_HEADER_VALUE_COMPLETE = 29,
+ CB_CHUNK_EXTENSION_NAME_COMPLETE = 34,
+ CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35,
+ CB_RESET = 31,
+}
+
+export enum TYPE {
+ BOTH = 0, // default
+ REQUEST = 1,
+ RESPONSE = 2,
+}
+
+export enum FLAGS {
+ CONNECTION_KEEP_ALIVE = 1 << 0,
+ CONNECTION_CLOSE = 1 << 1,
+ CONNECTION_UPGRADE = 1 << 2,
+ CHUNKED = 1 << 3,
+ UPGRADE = 1 << 4,
+ CONTENT_LENGTH = 1 << 5,
+ SKIPBODY = 1 << 6,
+ TRAILING = 1 << 7,
+ // 1 << 8 is unused
+ TRANSFER_ENCODING = 1 << 9,
+}
+
+export enum LENIENT_FLAGS {
+ HEADERS = 1 << 0,
+ CHUNKED_LENGTH = 1 << 1,
+ KEEP_ALIVE = 1 << 2,
+ TRANSFER_ENCODING = 1 << 3,
+ VERSION = 1 << 4,
+ DATA_AFTER_CLOSE = 1 << 5,
+ OPTIONAL_LF_AFTER_CR = 1 << 6,
+ OPTIONAL_CRLF_AFTER_CHUNK = 1 << 7,
+ OPTIONAL_CR_BEFORE_LF = 1 << 8,
+ SPACES_AFTER_CHUNK_SIZE = 1 << 9,
+}
+
+export enum METHODS {
+ DELETE = 0,
+ GET = 1,
+ HEAD = 2,
+ POST = 3,
+ PUT = 4,
+ /* pathological */
+ CONNECT = 5,
+ OPTIONS = 6,
+ TRACE = 7,
+ /* WebDAV */
+ COPY = 8,
+ LOCK = 9,
+ MKCOL = 10,
+ MOVE = 11,
+ PROPFIND = 12,
+ PROPPATCH = 13,
+ SEARCH = 14,
+ UNLOCK = 15,
+ BIND = 16,
+ REBIND = 17,
+ UNBIND = 18,
+ ACL = 19,
+ /* subversion */
+ REPORT = 20,
+ MKACTIVITY = 21,
+ CHECKOUT = 22,
+ MERGE = 23,
+ /* upnp */
+ 'M-SEARCH' = 24,
+ NOTIFY = 25,
+ SUBSCRIBE = 26,
+ UNSUBSCRIBE = 27,
+ /* RFC-5789 */
+ PATCH = 28,
+ PURGE = 29,
+ /* CalDAV */
+ MKCALENDAR = 30,
+ /* RFC-2068, section 19.6.1.2 */
+ LINK = 31,
+ UNLINK = 32,
+ /* icecast */
+ SOURCE = 33,
+ /* RFC-7540, section 11.6 */
+ PRI = 34,
+ /* RFC-2326 RTSP */
+ DESCRIBE = 35,
+ ANNOUNCE = 36,
+ SETUP = 37,
+ PLAY = 38,
+ PAUSE = 39,
+ TEARDOWN = 40,
+ GET_PARAMETER = 41,
+ SET_PARAMETER = 42,
+ REDIRECT = 43,
+ RECORD = 44,
+ /* RAOP */
+ FLUSH = 45,
+}
+
+export const METHODS_HTTP = [
+ METHODS.DELETE,
+ METHODS.GET,
+ METHODS.HEAD,
+ METHODS.POST,
+ METHODS.PUT,
+ METHODS.CONNECT,
+ METHODS.OPTIONS,
+ METHODS.TRACE,
+ METHODS.COPY,
+ METHODS.LOCK,
+ METHODS.MKCOL,
+ METHODS.MOVE,
+ METHODS.PROPFIND,
+ METHODS.PROPPATCH,
+ METHODS.SEARCH,
+ METHODS.UNLOCK,
+ METHODS.BIND,
+ METHODS.REBIND,
+ METHODS.UNBIND,
+ METHODS.ACL,
+ METHODS.REPORT,
+ METHODS.MKACTIVITY,
+ METHODS.CHECKOUT,
+ METHODS.MERGE,
+ METHODS['M-SEARCH'],
+ METHODS.NOTIFY,
+ METHODS.SUBSCRIBE,
+ METHODS.UNSUBSCRIBE,
+ METHODS.PATCH,
+ METHODS.PURGE,
+ METHODS.MKCALENDAR,
+ METHODS.LINK,
+ METHODS.UNLINK,
+ METHODS.PRI,
+
+ // TODO(indutny): should we allow it with HTTP?
+ METHODS.SOURCE,
+];
+
+export const METHODS_ICE = [
+ METHODS.SOURCE,
+];
+
+export const METHODS_RTSP = [
+ METHODS.OPTIONS,
+ METHODS.DESCRIBE,
+ METHODS.ANNOUNCE,
+ METHODS.SETUP,
+ METHODS.PLAY,
+ METHODS.PAUSE,
+ METHODS.TEARDOWN,
+ METHODS.GET_PARAMETER,
+ METHODS.SET_PARAMETER,
+ METHODS.REDIRECT,
+ METHODS.RECORD,
+ METHODS.FLUSH,
+
+ // For AirPlay
+ METHODS.GET,
+ METHODS.POST,
+];
+
+export const METHOD_MAP = enumToMap(METHODS);
+export const H_METHOD_MAP: IEnumMap = {};
+
+for (const key of Object.keys(METHOD_MAP)) {
+ if (/^H/.test(key)) {
+ H_METHOD_MAP[key] = METHOD_MAP[key];
+ }
+}
+
+export enum STATUSES {
+ CONTINUE = 100,
+ SWITCHING_PROTOCOLS = 101,
+ PROCESSING = 102,
+ EARLY_HINTS = 103,
+ RESPONSE_IS_STALE = 110, // Unofficial
+ REVALIDATION_FAILED = 111, // Unofficial
+ DISCONNECTED_OPERATION = 112, // Unofficial
+ HEURISTIC_EXPIRATION = 113, // Unofficial
+ MISCELLANEOUS_WARNING = 199, // Unofficial
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NON_AUTHORITATIVE_INFORMATION = 203,
+ NO_CONTENT = 204,
+ RESET_CONTENT = 205,
+ PARTIAL_CONTENT = 206,
+ MULTI_STATUS = 207,
+ ALREADY_REPORTED = 208,
+ TRANSFORMATION_APPLIED = 214, // Unofficial
+ IM_USED = 226,
+ MISCELLANEOUS_PERSISTENT_WARNING = 299, // Unofficial
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ FOUND = 302,
+ SEE_OTHER = 303,
+ NOT_MODIFIED = 304,
+ USE_PROXY = 305,
+ SWITCH_PROXY = 306, // No longer used
+ TEMPORARY_REDIRECT = 307,
+ PERMANENT_REDIRECT = 308,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ PAYMENT_REQUIRED = 402,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ METHOD_NOT_ALLOWED = 405,
+ NOT_ACCEPTABLE = 406,
+ PROXY_AUTHENTICATION_REQUIRED = 407,
+ REQUEST_TIMEOUT = 408,
+ CONFLICT = 409,
+ GONE = 410,
+ LENGTH_REQUIRED = 411,
+ PRECONDITION_FAILED = 412,
+ PAYLOAD_TOO_LARGE = 413,
+ URI_TOO_LONG = 414,
+ UNSUPPORTED_MEDIA_TYPE = 415,
+ RANGE_NOT_SATISFIABLE = 416,
+ EXPECTATION_FAILED = 417,
+ IM_A_TEAPOT = 418,
+ PAGE_EXPIRED = 419, // Unofficial
+ ENHANCE_YOUR_CALM = 420, // Unofficial
+ MISDIRECTED_REQUEST = 421,
+ UNPROCESSABLE_ENTITY = 422,
+ LOCKED = 423,
+ FAILED_DEPENDENCY = 424,
+ TOO_EARLY = 425,
+ UPGRADE_REQUIRED = 426,
+ PRECONDITION_REQUIRED = 428,
+ TOO_MANY_REQUESTS = 429,
+ REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, // Unofficial
+ REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ LOGIN_TIMEOUT = 440, // Unofficial
+ NO_RESPONSE = 444, // Unofficial
+ RETRY_WITH = 449, // Unofficial
+ BLOCKED_BY_PARENTAL_CONTROL = 450, // Unofficial
+ UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+ CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, // Unofficial
+ INVALID_X_FORWARDED_FOR = 463, // Unofficial
+ REQUEST_HEADER_TOO_LARGE = 494, // Unofficial
+ SSL_CERTIFICATE_ERROR = 495, // Unofficial
+ SSL_CERTIFICATE_REQUIRED = 496, // Unofficial
+ HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, // Unofficial
+ INVALID_TOKEN = 498, // Unofficial
+ CLIENT_CLOSED_REQUEST = 499, // Unofficial
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503,
+ GATEWAY_TIMEOUT = 504,
+ HTTP_VERSION_NOT_SUPPORTED = 505,
+ VARIANT_ALSO_NEGOTIATES = 506,
+ INSUFFICIENT_STORAGE = 507,
+ LOOP_DETECTED = 508,
+ BANDWIDTH_LIMIT_EXCEEDED = 509,
+ NOT_EXTENDED = 510,
+ NETWORK_AUTHENTICATION_REQUIRED = 511,
+ WEB_SERVER_UNKNOWN_ERROR = 520, // Unofficial
+ WEB_SERVER_IS_DOWN = 521, // Unofficial
+ CONNECTION_TIMEOUT = 522, // Unofficial
+ ORIGIN_IS_UNREACHABLE = 523, // Unofficial
+ TIMEOUT_OCCURED = 524, // Unofficial
+ SSL_HANDSHAKE_FAILED = 525, // Unofficial
+ INVALID_SSL_CERTIFICATE = 526, // Unofficial
+ RAILGUN_ERROR = 527, // Unofficial
+ SITE_IS_OVERLOADED = 529, // Unofficial
+ SITE_IS_FROZEN = 530, // Unofficial
+ IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, // Unofficial
+ NETWORK_READ_TIMEOUT = 598, // Unofficial
+ NETWORK_CONNECT_TIMEOUT = 599, // Unofficial
+}
+
+export const STATUSES_HTTP = [
+ STATUSES.CONTINUE,
+ STATUSES.SWITCHING_PROTOCOLS,
+ STATUSES.PROCESSING,
+ STATUSES.EARLY_HINTS,
+ STATUSES.RESPONSE_IS_STALE,
+ STATUSES.REVALIDATION_FAILED,
+ STATUSES.DISCONNECTED_OPERATION,
+ STATUSES.HEURISTIC_EXPIRATION,
+ STATUSES.MISCELLANEOUS_WARNING,
+ STATUSES.OK,
+ STATUSES.CREATED,
+ STATUSES.ACCEPTED,
+ STATUSES.NON_AUTHORITATIVE_INFORMATION,
+ STATUSES.NO_CONTENT,
+ STATUSES.RESET_CONTENT,
+ STATUSES.PARTIAL_CONTENT,
+ STATUSES.MULTI_STATUS,
+ STATUSES.ALREADY_REPORTED,
+ STATUSES.TRANSFORMATION_APPLIED,
+ STATUSES.IM_USED,
+ STATUSES.MISCELLANEOUS_PERSISTENT_WARNING,
+ STATUSES.MULTIPLE_CHOICES,
+ STATUSES.MOVED_PERMANENTLY,
+ STATUSES.FOUND,
+ STATUSES.SEE_OTHER,
+ STATUSES.NOT_MODIFIED,
+ STATUSES.USE_PROXY,
+ STATUSES.SWITCH_PROXY,
+ STATUSES.TEMPORARY_REDIRECT,
+ STATUSES.PERMANENT_REDIRECT,
+ STATUSES.BAD_REQUEST,
+ STATUSES.UNAUTHORIZED,
+ STATUSES.PAYMENT_REQUIRED,
+ STATUSES.FORBIDDEN,
+ STATUSES.NOT_FOUND,
+ STATUSES.METHOD_NOT_ALLOWED,
+ STATUSES.NOT_ACCEPTABLE,
+ STATUSES.PROXY_AUTHENTICATION_REQUIRED,
+ STATUSES.REQUEST_TIMEOUT,
+ STATUSES.CONFLICT,
+ STATUSES.GONE,
+ STATUSES.LENGTH_REQUIRED,
+ STATUSES.PRECONDITION_FAILED,
+ STATUSES.PAYLOAD_TOO_LARGE,
+ STATUSES.URI_TOO_LONG,
+ STATUSES.UNSUPPORTED_MEDIA_TYPE,
+ STATUSES.RANGE_NOT_SATISFIABLE,
+ STATUSES.EXPECTATION_FAILED,
+ STATUSES.IM_A_TEAPOT,
+ STATUSES.PAGE_EXPIRED,
+ STATUSES.ENHANCE_YOUR_CALM,
+ STATUSES.MISDIRECTED_REQUEST,
+ STATUSES.UNPROCESSABLE_ENTITY,
+ STATUSES.LOCKED,
+ STATUSES.FAILED_DEPENDENCY,
+ STATUSES.TOO_EARLY,
+ STATUSES.UPGRADE_REQUIRED,
+ STATUSES.PRECONDITION_REQUIRED,
+ STATUSES.TOO_MANY_REQUESTS,
+ STATUSES.REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL,
+ STATUSES.REQUEST_HEADER_FIELDS_TOO_LARGE,
+ STATUSES.LOGIN_TIMEOUT,
+ STATUSES.NO_RESPONSE,
+ STATUSES.RETRY_WITH,
+ STATUSES.BLOCKED_BY_PARENTAL_CONTROL,
+ STATUSES.UNAVAILABLE_FOR_LEGAL_REASONS,
+ STATUSES.CLIENT_CLOSED_LOAD_BALANCED_REQUEST,
+ STATUSES.INVALID_X_FORWARDED_FOR,
+ STATUSES.REQUEST_HEADER_TOO_LARGE,
+ STATUSES.SSL_CERTIFICATE_ERROR,
+ STATUSES.SSL_CERTIFICATE_REQUIRED,
+ STATUSES.HTTP_REQUEST_SENT_TO_HTTPS_PORT,
+ STATUSES.INVALID_TOKEN,
+ STATUSES.CLIENT_CLOSED_REQUEST,
+ STATUSES.INTERNAL_SERVER_ERROR,
+ STATUSES.NOT_IMPLEMENTED,
+ STATUSES.BAD_GATEWAY,
+ STATUSES.SERVICE_UNAVAILABLE,
+ STATUSES.GATEWAY_TIMEOUT,
+ STATUSES.HTTP_VERSION_NOT_SUPPORTED,
+ STATUSES.VARIANT_ALSO_NEGOTIATES,
+ STATUSES.INSUFFICIENT_STORAGE,
+ STATUSES.LOOP_DETECTED,
+ STATUSES.BANDWIDTH_LIMIT_EXCEEDED,
+ STATUSES.NOT_EXTENDED,
+ STATUSES.NETWORK_AUTHENTICATION_REQUIRED,
+ STATUSES.WEB_SERVER_UNKNOWN_ERROR,
+ STATUSES.WEB_SERVER_IS_DOWN,
+ STATUSES.CONNECTION_TIMEOUT,
+ STATUSES.ORIGIN_IS_UNREACHABLE,
+ STATUSES.TIMEOUT_OCCURED,
+ STATUSES.SSL_HANDSHAKE_FAILED,
+ STATUSES.INVALID_SSL_CERTIFICATE,
+ STATUSES.RAILGUN_ERROR,
+ STATUSES.SITE_IS_OVERLOADED,
+ STATUSES.SITE_IS_FROZEN,
+ STATUSES.IDENTITY_PROVIDER_AUTHENTICATION_ERROR,
+ STATUSES.NETWORK_READ_TIMEOUT,
+ STATUSES.NETWORK_CONNECT_TIMEOUT,
+];
+
+export enum FINISH {
+ SAFE = 0,
+ SAFE_WITH_CB = 1,
+ UNSAFE = 2,
+}
+
+// Internal
+
+export type CharList = Array<string | number>;
+
+export const ALPHA: CharList = [];
+
+for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) {
+ // Upper case
+ ALPHA.push(String.fromCharCode(i));
+
+ // Lower case
+ ALPHA.push(String.fromCharCode(i + 0x20));
+}
+
+export const NUM_MAP = {
+ 0: 0, 1: 1, 2: 2, 3: 3, 4: 4,
+ 5: 5, 6: 6, 7: 7, 8: 8, 9: 9,
+};
+
+export const HEX_MAP = {
+ 0: 0, 1: 1, 2: 2, 3: 3, 4: 4,
+ 5: 5, 6: 6, 7: 7, 8: 8, 9: 9,
+ A: 0XA, B: 0XB, C: 0XC, D: 0XD, E: 0XE, F: 0XF,
+ a: 0xa, b: 0xb, c: 0xc, d: 0xd, e: 0xe, f: 0xf,
+};
+
+export const NUM: CharList = [
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+];
+
+export const ALPHANUM: CharList = ALPHA.concat(NUM);
+export const MARK: CharList = [ '-', '_', '.', '!', '~', '*', '\'', '(', ')' ];
+export const USERINFO_CHARS: CharList = ALPHANUM
+ .concat(MARK)
+ .concat([ '%', ';', ':', '&', '=', '+', '$', ',' ]);
+
+// TODO(indutny): use RFC
+export const URL_CHAR: CharList = ([
+ '!', '"', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ ':', ';', '<', '=', '>',
+ '@', '[', '\\', ']', '^', '_',
+ '`',
+ '{', '|', '}', '~',
+] as CharList).concat(ALPHANUM);
+
+export const HEX: CharList = NUM.concat(
+ [ 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' ]);
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+export const TOKEN: CharList = ([
+ '!', '#', '$', '%', '&', '\'',
+ '*', '+', '-', '.',
+ '^', '_', '`',
+ '|', '~',
+] as CharList).concat(ALPHANUM);
+
+/*
+ * Verify that a char is a valid visible (printable) US-ASCII
+ * character or %x80-FF
+ */
+export const HEADER_CHARS: CharList = [ '\t' ];
+for (let i = 32; i <= 255; i++) {
+ if (i !== 127) {
+ HEADER_CHARS.push(i);
+ }
+}
+
+// ',' = \x44
+export const CONNECTION_TOKEN_CHARS: CharList =
+ HEADER_CHARS.filter((c: string | number) => c !== 44);
+
+export const QUOTED_STRING: CharList = [ '\t', ' ' ];
+for (let i = 0x21; i <= 0xff; i++) {
+ if (i !== 0x22 && i !== 0x5c) { // All characters in ASCII except \ and "
+ QUOTED_STRING.push(i);
+ }
+}
+
+export const HTAB_SP_VCHAR_OBS_TEXT: CharList = [ '\t', ' ' ];
+
+// VCHAR: https://tools.ietf.org/html/rfc5234#appendix-B.1
+for (let i = 0x21; i <= 0x7E; i++) {
+ HTAB_SP_VCHAR_OBS_TEXT.push(i);
+}
+// OBS_TEXT: https://datatracker.ietf.org/doc/html/rfc9110#name-collected-abnf
+for (let i = 0x80; i <= 0xff; i++) {
+ HTAB_SP_VCHAR_OBS_TEXT.push(i);
+}
+
+export const MAJOR = NUM_MAP;
+export const MINOR = MAJOR;
+
+export enum HEADER_STATE {
+ GENERAL = 0,
+ CONNECTION = 1,
+ CONTENT_LENGTH = 2,
+ TRANSFER_ENCODING = 3,
+ UPGRADE = 4,
+
+ CONNECTION_KEEP_ALIVE = 5,
+ CONNECTION_CLOSE = 6,
+ CONNECTION_UPGRADE = 7,
+ TRANSFER_ENCODING_CHUNKED = 8,
+}
+
+export const SPECIAL_HEADERS = {
+ 'connection': HEADER_STATE.CONNECTION,
+ 'content-length': HEADER_STATE.CONTENT_LENGTH,
+ 'proxy-connection': HEADER_STATE.CONNECTION,
+ 'transfer-encoding': HEADER_STATE.TRANSFER_ENCODING,
+ 'upgrade': HEADER_STATE.UPGRADE,
+};
diff --git a/llhttp/src/llhttp/http.ts b/llhttp/src/llhttp/http.ts
new file mode 100644
index 0000000..6a201ff
--- /dev/null
+++ b/llhttp/src/llhttp/http.ts
@@ -0,0 +1,1299 @@
+import * as assert from 'assert';
+import { LLParse, source } from 'llparse';
+
+import Match = source.node.Match;
+import Node = source.node.Node;
+
+import {
+ CharList,
+ CONNECTION_TOKEN_CHARS, ERROR, FINISH, FLAGS, H_METHOD_MAP, HEADER_CHARS,
+ HEADER_STATE, HEX_MAP, HTAB_SP_VCHAR_OBS_TEXT,
+ LENIENT_FLAGS,
+ MAJOR, METHOD_MAP, METHODS, METHODS_HTTP, METHODS_ICE, METHODS_RTSP,
+ MINOR, NUM_MAP, QUOTED_STRING, SPECIAL_HEADERS,
+ TOKEN, TYPE,
+} from './constants';
+import { URL } from './url';
+
+type MaybeNode = string | Match | Node;
+
+const NODES: ReadonlyArray<string> = [
+ 'start',
+ 'after_start',
+ 'start_req',
+ 'after_start_req',
+ 'start_res',
+ 'start_req_or_res',
+
+ 'req_or_res_method',
+
+ 'res_http_major',
+ 'res_http_dot',
+ 'res_http_minor',
+ 'res_http_end',
+ 'res_after_version',
+ 'res_status_code_digit_1',
+ 'res_status_code_digit_2',
+ 'res_status_code_digit_3',
+ 'res_status_code_otherwise',
+ 'res_status_start',
+ 'res_status',
+ 'res_line_almost_done',
+
+ 'req_first_space_before_url',
+ 'req_spaces_before_url',
+ 'req_http_start',
+ 'req_http_version',
+ 'req_http_major',
+ 'req_http_dot',
+ 'req_http_minor',
+ 'req_http_end',
+ 'req_http_complete',
+ 'req_http_complete_crlf',
+
+ 'req_pri_upgrade',
+
+ 'headers_start',
+ 'header_field_start',
+ 'header_field',
+ 'header_field_colon',
+ 'header_field_colon_discard_ws',
+ 'header_field_general',
+ 'header_field_general_otherwise',
+ 'header_value_discard_ws',
+ 'header_value_discard_ws_almost_done',
+ 'header_value_discard_lws',
+ 'header_value_start',
+ 'header_value',
+ 'header_value_otherwise',
+ 'header_value_lenient',
+ 'header_value_lenient_failed',
+ 'header_value_lws',
+ 'header_value_te_chunked',
+ 'header_value_te_chunked_last',
+ 'header_value_te_token',
+ 'header_value_te_token_ows',
+ 'header_value_content_length_once',
+ 'header_value_content_length',
+ 'header_value_content_length_ws',
+ 'header_value_connection',
+ 'header_value_connection_ws',
+ 'header_value_connection_token',
+ 'header_value_almost_done',
+
+ 'headers_almost_done',
+ 'headers_done',
+
+ 'chunk_size_start',
+ 'chunk_size_digit',
+ 'chunk_size',
+ 'chunk_size_otherwise',
+ 'chunk_size_almost_done',
+ 'chunk_size_almost_done_lf',
+ 'chunk_extensions',
+ 'chunk_extension_name',
+ 'chunk_extension_value',
+ 'chunk_extension_quoted_value',
+ 'chunk_extension_quoted_value_quoted_pair',
+ 'chunk_extension_quoted_value_done',
+ 'chunk_data',
+ 'chunk_data_almost_done',
+ 'chunk_complete',
+ 'body_identity',
+ 'body_identity_eof',
+
+ 'message_done',
+
+ 'eof',
+ 'cleanup',
+ 'closed',
+ 'restart',
+];
+
+interface ISpanMap {
+ readonly status: source.Span;
+ readonly method: source.Span;
+ readonly version: source.Span;
+ readonly headerField: source.Span;
+ readonly headerValue: source.Span;
+ readonly chunkExtensionName: source.Span;
+ readonly chunkExtensionValue: source.Span;
+ readonly body: source.Span;
+}
+
+interface ICallbackMap {
+ readonly onMessageBegin: source.code.Code;
+ readonly onUrlComplete: source.code.Code;
+ readonly onMethodComplete: source.code.Code;
+ readonly onVersionComplete: source.code.Code;
+ readonly onStatusComplete: source.code.Code;
+ readonly beforeHeadersComplete: source.code.Code;
+ readonly onHeaderFieldComplete: source.code.Code;
+ readonly onHeaderValueComplete: source.code.Code;
+ readonly onHeadersComplete: source.code.Code;
+ readonly afterHeadersComplete: source.code.Code;
+ readonly onChunkHeader: source.code.Code;
+ readonly onChunkExtensionName: source.code.Code;
+ readonly onChunkExtensionValue: source.code.Code;
+ readonly onChunkComplete: source.code.Code;
+ readonly onMessageComplete: source.code.Code;
+ readonly afterMessageComplete: source.code.Code;
+ readonly onReset: source.code.Code;
+}
+
+interface IMulTargets {
+ readonly overflow: string | Node;
+ readonly success: string | Node;
+}
+
+interface IMulOptions {
+ readonly base: number;
+ readonly max?: number;
+ readonly signed: boolean;
+}
+
+interface IIsEqualTargets {
+ readonly equal: string | Node;
+ readonly notEqual: string | Node;
+}
+
+export interface IHTTPResult {
+ readonly entry: Node;
+}
+
+export class HTTP {
+ private readonly url: URL;
+ private readonly TOKEN: CharList;
+ private readonly span: ISpanMap;
+ private readonly callback: ICallbackMap;
+ private readonly nodes: Map<string, Match> = new Map();
+
+ constructor(private readonly llparse: LLParse) {
+ const p = llparse;
+
+ this.url = new URL(p);
+ this.TOKEN = TOKEN;
+
+ this.span = {
+ body: p.span(p.code.span('llhttp__on_body')),
+ chunkExtensionName: p.span(p.code.span('llhttp__on_chunk_extension_name')),
+ chunkExtensionValue: p.span(p.code.span('llhttp__on_chunk_extension_value')),
+ headerField: p.span(p.code.span('llhttp__on_header_field')),
+ headerValue: p.span(p.code.span('llhttp__on_header_value')),
+ method: p.span(p.code.span('llhttp__on_method')),
+ status: p.span(p.code.span('llhttp__on_status')),
+ version: p.span(p.code.span('llhttp__on_version')),
+ };
+
+ /* tslint:disable:object-literal-sort-keys */
+ this.callback = {
+ // User callbacks
+ onUrlComplete: p.code.match('llhttp__on_url_complete'),
+ onStatusComplete: p.code.match('llhttp__on_status_complete'),
+ onMethodComplete: p.code.match('llhttp__on_method_complete'),
+ onVersionComplete: p.code.match('llhttp__on_version_complete'),
+ onHeaderFieldComplete: p.code.match('llhttp__on_header_field_complete'),
+ onHeaderValueComplete: p.code.match('llhttp__on_header_value_complete'),
+ onHeadersComplete: p.code.match('llhttp__on_headers_complete'),
+ onMessageBegin: p.code.match('llhttp__on_message_begin'),
+ onMessageComplete: p.code.match('llhttp__on_message_complete'),
+ onChunkHeader: p.code.match('llhttp__on_chunk_header'),
+ onChunkExtensionName: p.code.match('llhttp__on_chunk_extension_name_complete'),
+ onChunkExtensionValue: p.code.match('llhttp__on_chunk_extension_value_complete'),
+ onChunkComplete: p.code.match('llhttp__on_chunk_complete'),
+ onReset: p.code.match('llhttp__on_reset'),
+
+ // Internal callbacks `src/http.c`
+ beforeHeadersComplete:
+ p.code.match('llhttp__before_headers_complete'),
+ afterHeadersComplete: p.code.match('llhttp__after_headers_complete'),
+ afterMessageComplete: p.code.match('llhttp__after_message_complete'),
+ };
+ /* tslint:enable:object-literal-sort-keys */
+
+ for (const name of NODES) {
+ this.nodes.set(name, p.node(name) as Match);
+ }
+ }
+
+ public build(): IHTTPResult {
+ const p = this.llparse;
+
+ p.property('i64', 'content_length');
+ p.property('i8', 'type');
+ p.property('i8', 'method');
+ p.property('i8', 'http_major');
+ p.property('i8', 'http_minor');
+ p.property('i8', 'header_state');
+ p.property('i16', 'lenient_flags');
+ p.property('i8', 'upgrade');
+ p.property('i8', 'finish');
+ p.property('i16', 'flags');
+ p.property('i16', 'status_code');
+ p.property('i8', 'initial_message_completed');
+
+ // Verify defaults
+ assert.strictEqual(FINISH.SAFE, 0);
+ assert.strictEqual(TYPE.BOTH, 0);
+
+ // Shared settings (to be used in C wrapper)
+ p.property('ptr', 'settings');
+
+ this.buildLine();
+ this.buildHeaders();
+
+ return {
+ entry: this.node('start'),
+ };
+ }
+
+ private buildLine(): void {
+ const p = this.llparse;
+ const span = this.span;
+ const n = (name: string): Match => this.node<Match>(name);
+
+ const url = this.url.build();
+
+ const switchType = this.load('type', {
+ [TYPE.REQUEST]: n('start_req'),
+ [TYPE.RESPONSE]: n('start_res'),
+ }, n('start_req_or_res'));
+
+ n('start')
+ .match([ '\r', '\n' ], n('start'))
+ .otherwise(
+ this.load('initial_message_completed', {
+ 1: this.invokePausable('on_reset', ERROR.CB_RESET, n('after_start')),
+ }, n('after_start')),
+ );
+
+ n('after_start').otherwise(
+ this.update(
+ 'finish',
+ FINISH.UNSAFE,
+ this.invokePausable('on_message_begin', ERROR.CB_MESSAGE_BEGIN, switchType),
+ ),
+ );
+
+ n('start_req_or_res')
+ .peek('H', this.span.method.start(n('req_or_res_method')))
+ .otherwise(this.update('type', TYPE.REQUEST, 'start_req'));
+
+ n('req_or_res_method')
+ .select(H_METHOD_MAP, this.store('method',
+ this.update('type', TYPE.REQUEST, this.span.method.end(
+ this.invokePausable('on_method_complete', ERROR.CB_METHOD_COMPLETE, n('req_first_space_before_url')),
+ )),
+ ))
+ .match('HTTP/', this.span.method.end(this.update('type', TYPE.RESPONSE,
+ this.span.version.start(n('res_http_major')))))
+ .otherwise(p.error(ERROR.INVALID_CONSTANT, 'Invalid word encountered'));
+
+ const checkVersion = (destination: string): Node => {
+ const node = n(destination);
+ const errorNode = this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Invalid HTTP version'));
+
+ return this.testLenientFlags(LENIENT_FLAGS.VERSION,
+ {
+ 1: node,
+ },
+ this.load('http_major', {
+ 0: this.load('http_minor', {
+ 9: node,
+ }, errorNode),
+ 1: this.load('http_minor', {
+ 0: node,
+ 1: node,
+ }, errorNode),
+ 2: this.load('http_minor', {
+ 0: node,
+ }, errorNode),
+ }, errorNode),
+ );
+ };
+
+ const checkIfAllowLFWithoutCR = (success: Node, failure: Node) => {
+ return this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_CR_BEFORE_LF, { 1: success }, failure);
+ };
+
+ // Response
+ n('start_res')
+ .match('HTTP/', span.version.start(n('res_http_major')))
+ .otherwise(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/'));
+
+ n('res_http_major')
+ .select(MAJOR, this.store('http_major', 'res_http_dot'))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Invalid major version')));
+
+ n('res_http_dot')
+ .match('.', n('res_http_minor'))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Expected dot')));
+
+ n('res_http_minor')
+ .select(MINOR, this.store('http_minor', checkVersion('res_http_end')))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Invalid minor version')));
+
+ n('res_http_end')
+ .otherwise(this.span.version.end(
+ this.invokePausable('on_version_complete', ERROR.CB_VERSION_COMPLETE, 'res_after_version'),
+ ));
+
+ n('res_after_version')
+ .match(' ', this.update('status_code', 0, 'res_status_code_digit_1'))
+ .otherwise(p.error(ERROR.INVALID_VERSION,
+ 'Expected space after version'));
+
+ n('res_status_code_digit_1')
+ .select(NUM_MAP, this.mulAdd('status_code', {
+ overflow: p.error(ERROR.INVALID_STATUS, 'Invalid status code'),
+ success: 'res_status_code_digit_2',
+ }))
+ .otherwise(p.error(ERROR.INVALID_STATUS, 'Invalid status code'));
+
+ n('res_status_code_digit_2')
+ .select(NUM_MAP, this.mulAdd('status_code', {
+ overflow: p.error(ERROR.INVALID_STATUS, 'Invalid status code'),
+ success: 'res_status_code_digit_3',
+ }))
+ .otherwise(p.error(ERROR.INVALID_STATUS, 'Invalid status code'));
+
+ n('res_status_code_digit_3')
+ .select(NUM_MAP, this.mulAdd('status_code', {
+ overflow: p.error(ERROR.INVALID_STATUS, 'Invalid status code'),
+ success: 'res_status_code_otherwise',
+ }))
+ .otherwise(p.error(ERROR.INVALID_STATUS, 'Invalid status code'));
+
+ const onStatusComplete = this.invokePausable(
+ 'on_status_complete', ERROR.CB_STATUS_COMPLETE, n('headers_start'),
+ );
+
+ n('res_status_code_otherwise')
+ .match(' ', n('res_status_start'))
+ .match('\r', n('res_line_almost_done'))
+ .match(
+ '\n',
+ checkIfAllowLFWithoutCR(
+ onStatusComplete,
+ p.error(ERROR.INVALID_STATUS, 'Invalid response status'),
+ ),
+ )
+ .otherwise(p.error(ERROR.INVALID_STATUS, 'Invalid response status'));
+
+ n('res_status_start')
+ .otherwise(span.status.start(n('res_status')));
+
+ n('res_status')
+ .peek('\r', span.status.end().skipTo(n('res_line_almost_done')))
+ .peek(
+ '\n',
+ span.status.end().skipTo(
+ checkIfAllowLFWithoutCR(
+ onStatusComplete,
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after response line'),
+ ),
+ ),
+ )
+ .skipTo(n('res_status'));
+
+ n('res_line_almost_done')
+ .match(['\r', '\n'], onStatusComplete)
+ .otherwise(this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_LF_AFTER_CR, {
+ 1: onStatusComplete,
+ }, p.error(ERROR.STRICT, 'Expected LF after CR')));
+
+ // Request
+ n('start_req').otherwise(this.span.method.start(n('after_start_req')));
+
+ n('after_start_req')
+ .select(METHOD_MAP, this.store('method', this.span.method.end(
+ this.invokePausable('on_method_complete', ERROR.CB_METHOD_COMPLETE, n('req_first_space_before_url'),
+ ))))
+ .otherwise(p.error(ERROR.INVALID_METHOD, 'Invalid method encountered'));
+
+ n('req_first_space_before_url')
+ .match(' ', n('req_spaces_before_url'))
+ .otherwise(p.error(ERROR.INVALID_METHOD, 'Expected space after method'));
+
+ n('req_spaces_before_url')
+ .match(' ', n('req_spaces_before_url'))
+ .otherwise(this.isEqual('method', METHODS.CONNECT, {
+ equal: url.entry.connect,
+ notEqual: url.entry.normal,
+ }));
+
+ const onUrlCompleteHTTP = this.invokePausable(
+ 'on_url_complete', ERROR.CB_URL_COMPLETE, n('req_http_start'),
+ );
+
+ url.exit.toHTTP
+ .otherwise(onUrlCompleteHTTP);
+
+ const onUrlCompleteHTTP09 = this.invokePausable(
+ 'on_url_complete', ERROR.CB_URL_COMPLETE, n('headers_start'),
+ );
+
+ url.exit.toHTTP09
+ .otherwise(
+ this.update('http_major', 0,
+ this.update('http_minor', 9, onUrlCompleteHTTP09)),
+ );
+
+ const checkMethod = (methods: METHODS[], error: string): Node => {
+ const success = n('req_http_version');
+ const failure = p.error(ERROR.INVALID_CONSTANT, error);
+
+ const map: { [key: number]: Node } = {};
+ for (const method of methods) {
+ map[method] = success;
+ }
+
+ return this.load('method', map, failure);
+ };
+
+ n('req_http_start')
+ .match('HTTP/', checkMethod(METHODS_HTTP,
+ 'Invalid method for HTTP/x.x request'))
+ .match('RTSP/', checkMethod(METHODS_RTSP,
+ 'Invalid method for RTSP/x.x request'))
+ .match('ICE/', checkMethod(METHODS_ICE,
+ 'Expected SOURCE method for ICE/x.x request'))
+ .match(' ', n('req_http_start'))
+ .otherwise(p.error(ERROR.INVALID_CONSTANT, 'Expected HTTP/'));
+
+ n('req_http_version').otherwise(span.version.start(n('req_http_major')));
+
+ n('req_http_major')
+ .select(MAJOR, this.store('http_major', 'req_http_dot'))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Invalid major version')));
+
+ n('req_http_dot')
+ .match('.', n('req_http_minor'))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Expected dot')));
+
+ n('req_http_minor')
+ .select(MINOR, this.store('http_minor', checkVersion('req_http_end')))
+ .otherwise(this.span.version.end(p.error(ERROR.INVALID_VERSION, 'Invalid minor version')));
+
+ n('req_http_end').otherwise(
+ span.version.end(
+ this.invokePausable(
+ 'on_version_complete',
+ ERROR.CB_VERSION_COMPLETE,
+ this.load('method', {
+ [METHODS.PRI]: n('req_pri_upgrade'),
+ }, n('req_http_complete')),
+ ),
+ ),
+ );
+
+ n('req_http_complete')
+ .match('\r', n('req_http_complete_crlf'))
+ .match(
+ '\n',
+ checkIfAllowLFWithoutCR(
+ n('req_http_complete_crlf'),
+ p.error(ERROR.INVALID_VERSION, 'Expected CRLF after version'),
+ ),
+ )
+ .otherwise(p.error(ERROR.INVALID_VERSION, 'Expected CRLF after version'));
+
+ n('req_http_complete_crlf')
+ .match('\n', n('headers_start'))
+ .otherwise(this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_LF_AFTER_CR, {
+ 1: n('headers_start'),
+ }, p.error(ERROR.STRICT, 'Expected CRLF after version')));
+
+ n('req_pri_upgrade')
+ .match('\r\n\r\nSM\r\n\r\n',
+ p.error(ERROR.PAUSED_H2_UPGRADE, 'Pause on PRI/Upgrade'))
+ .otherwise(
+ p.error(ERROR.INVALID_VERSION, 'Expected HTTP/2 Connection Preface'));
+ }
+
+ private buildHeaders(): void {
+ this.buildHeaderField();
+ this.buildHeaderValue();
+ }
+
+ private buildHeaderField(): void {
+ const p = this.llparse;
+ const span = this.span;
+ const n = (name: string): Match => this.node<Match>(name);
+
+ const onInvalidHeaderFieldChar =
+ p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header field char');
+
+ n('headers_start')
+ .match(' ',
+ this.testLenientFlags(LENIENT_FLAGS.HEADERS, {
+ 1: n('header_field_start'),
+ }, p.error(ERROR.UNEXPECTED_SPACE, 'Unexpected space after start line')),
+ )
+ .otherwise(n('header_field_start'));
+
+ n('header_field_start')
+ .match('\r', n('headers_almost_done'))
+ .match('\n',
+ this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_CR_BEFORE_LF, {
+ 1: this.testFlags(FLAGS.TRAILING, {
+ 1: this.invokePausable('on_chunk_complete',
+ ERROR.CB_CHUNK_COMPLETE, 'message_done'),
+ }).otherwise(this.headersCompleted()),
+ }, onInvalidHeaderFieldChar),
+ )
+ .peek(':', p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header token'))
+ .otherwise(span.headerField.start(n('header_field')));
+
+ n('header_field')
+ .transform(p.transform.toLower())
+ // Match headers that need special treatment
+ .select(SPECIAL_HEADERS, this.store('header_state', 'header_field_colon'))
+ .otherwise(this.resetHeaderState('header_field_general'));
+
+ /* https://www.rfc-editor.org/rfc/rfc7230.html#section-3.3.3, paragraph 3.
+ *
+ * If a message is received with both a Transfer-Encoding and a
+ * Content-Length header field, the Transfer-Encoding overrides the
+ * Content-Length. Such a message might indicate an attempt to
+ * perform request smuggling (Section 9.5) or response splitting
+ * (Section 9.4) and **ought to be handled as an error**. A sender MUST
+ * remove the received Content-Length field prior to forwarding such
+ * a message downstream.
+ *
+ * Since llhttp 9, we go for the stricter approach and treat this as an error.
+ */
+ const checkInvalidTransferEncoding = (otherwise: Node) => {
+ return this.testFlags(FLAGS.CONTENT_LENGTH, {
+ 1: this.testLenientFlags(LENIENT_FLAGS.CHUNKED_LENGTH, {
+ 0: p.error(ERROR.INVALID_TRANSFER_ENCODING, "Transfer-Encoding can't be present with Content-Length"),
+ }).otherwise(otherwise),
+ }).otherwise(otherwise);
+ };
+
+ const checkInvalidContentLength = (otherwise: Node) => {
+ return this.testFlags(FLAGS.TRANSFER_ENCODING, {
+ 1: this.testLenientFlags(LENIENT_FLAGS.CHUNKED_LENGTH, {
+ 0: p.error(ERROR.INVALID_CONTENT_LENGTH, "Content-Length can't be present with Transfer-Encoding"),
+ }).otherwise(otherwise),
+ }).otherwise(otherwise);
+ };
+
+ const onHeaderFieldComplete = this.invokePausable(
+ 'on_header_field_complete', ERROR.CB_HEADER_FIELD_COMPLETE,
+ this.load('header_state', {
+ [HEADER_STATE.TRANSFER_ENCODING]: checkInvalidTransferEncoding(n('header_value_discard_ws')),
+ [HEADER_STATE.CONTENT_LENGTH]: checkInvalidContentLength(n('header_value_discard_ws')),
+ }, 'header_value_discard_ws'),
+ );
+
+ const checkLenientFlagsOnColon =
+ this.testLenientFlags(LENIENT_FLAGS.HEADERS, {
+ 1: n('header_field_colon_discard_ws'),
+ }, span.headerField.end().skipTo(onInvalidHeaderFieldChar));
+
+ n('header_field_colon')
+ // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
+ // Whitespace character is not allowed between the header field-name
+ // and colon. If the next token matches whitespace then throw an error.
+ //
+ // Add a check for the lenient flag. If the lenient flag is set, the
+ // whitespace token is allowed to support legacy code not following
+ // http specs.
+ .peek(' ', checkLenientFlagsOnColon)
+ .peek(':', span.headerField.end().skipTo(onHeaderFieldComplete))
+ // Fallback to general header, there're additional characters:
+ // `Connection-Duration` instead of `Connection` and so on.
+ .otherwise(this.resetHeaderState('header_field_general'));
+
+ n('header_field_colon_discard_ws')
+ .match(' ', n('header_field_colon_discard_ws'))
+ .otherwise(n('header_field_colon'));
+
+ n('header_field_general')
+ .match(this.TOKEN, n('header_field_general'))
+ .otherwise(n('header_field_general_otherwise'));
+
+ // Just a performance optimization, split the node so that the fast case
+ // remains in `header_field_general`
+ n('header_field_general_otherwise')
+ .peek(':', span.headerField.end().skipTo(onHeaderFieldComplete))
+ .otherwise(p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header token'));
+ }
+
+ private buildHeaderValue(): void {
+ const p = this.llparse;
+ const span = this.span;
+ const callback = this.callback;
+ const n = (name: string): Match => this.node<Match>(name);
+
+ const fallback = this.resetHeaderState('header_value');
+
+ n('header_value_discard_ws')
+ .match([ ' ', '\t' ], n('header_value_discard_ws'))
+ .match('\r', n('header_value_discard_ws_almost_done'))
+ .match('\n', this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_CR_BEFORE_LF, {
+ 1: n('header_value_discard_lws'),
+ }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char')))
+ .otherwise(span.headerValue.start(n('header_value_start')));
+
+ n('header_value_discard_ws_almost_done')
+ .match('\n', n('header_value_discard_lws'))
+ .otherwise(
+ this.testLenientFlags(LENIENT_FLAGS.HEADERS, {
+ 1: n('header_value_discard_lws'),
+ }, p.error(ERROR.STRICT, 'Expected LF after CR')),
+ );
+
+ const onHeaderValueComplete = this.invokePausable(
+ 'on_header_value_complete', ERROR.CB_HEADER_VALUE_COMPLETE, n('header_field_start'),
+ );
+
+ const emptyContentLengthError = p.error(
+ ERROR.INVALID_CONTENT_LENGTH, 'Empty Content-Length');
+ const checkContentLengthEmptiness = this.load('header_state', {
+ [HEADER_STATE.CONTENT_LENGTH]: emptyContentLengthError,
+ }, this.setHeaderFlags(
+ this.emptySpan(span.headerValue, onHeaderValueComplete)));
+
+ n('header_value_discard_lws')
+ .match([ ' ', '\t' ], this.testLenientFlags(LENIENT_FLAGS.HEADERS, {
+ 1: n('header_value_discard_ws'),
+ }, p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char')))
+ .otherwise(checkContentLengthEmptiness);
+
+ // Multiple `Transfer-Encoding` headers should be treated as one, but with
+ // values separate by a comma.
+ //
+ // See: https://tools.ietf.org/html/rfc7230#section-3.2.2
+ const toTransferEncoding = this.unsetFlag(
+ FLAGS.CHUNKED,
+ 'header_value_te_chunked');
+
+ // Once chunked has been selected, no other encoding is possible in requests
+ // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1
+ const forbidAfterChunkedInRequest = (otherwise: Node) => {
+ return this.load('type', {
+ [TYPE.REQUEST]: this.testLenientFlags(LENIENT_FLAGS.TRANSFER_ENCODING, {
+ 0: span.headerValue.end().skipTo(
+ p.error(ERROR.INVALID_TRANSFER_ENCODING, 'Invalid `Transfer-Encoding` header value'),
+ ),
+ }).otherwise(otherwise),
+ }, otherwise);
+ };
+
+ n('header_value_start')
+ .otherwise(this.load('header_state', {
+ [HEADER_STATE.UPGRADE]: this.setFlag(FLAGS.UPGRADE, fallback),
+ [HEADER_STATE.TRANSFER_ENCODING]: this.testFlags(
+ FLAGS.CHUNKED,
+ {
+ 1: forbidAfterChunkedInRequest(this.setFlag(FLAGS.TRANSFER_ENCODING, toTransferEncoding)),
+ },
+ this.setFlag(FLAGS.TRANSFER_ENCODING, toTransferEncoding)),
+ [HEADER_STATE.CONTENT_LENGTH]: n('header_value_content_length_once'),
+ [HEADER_STATE.CONNECTION]: n('header_value_connection'),
+ }, 'header_value'));
+
+ //
+ // Transfer-Encoding
+ //
+
+ n('header_value_te_chunked')
+ .transform(p.transform.toLowerUnsafe())
+ .match(
+ 'chunked',
+ n('header_value_te_chunked_last'),
+ )
+ .otherwise(n('header_value_te_token'));
+
+ n('header_value_te_chunked_last')
+ .match(' ', n('header_value_te_chunked_last'))
+ .peek([ '\r', '\n' ], this.update('header_state',
+ HEADER_STATE.TRANSFER_ENCODING_CHUNKED,
+ 'header_value_otherwise'))
+ .peek(',', forbidAfterChunkedInRequest(n('header_value_te_chunked')))
+ .otherwise(n('header_value_te_token'));
+
+ n('header_value_te_token')
+ .match(',', n('header_value_te_token_ows'))
+ .match(CONNECTION_TOKEN_CHARS, n('header_value_te_token'))
+ .otherwise(fallback);
+
+ n('header_value_te_token_ows')
+ .match([ ' ', '\t' ], n('header_value_te_token_ows'))
+ .otherwise(n('header_value_te_chunked'));
+
+ //
+ // Content-Length
+ //
+
+ const invalidContentLength = (reason: string): Node => {
+ // End span for easier testing
+ // TODO(indutny): minimize code size
+ return span.headerValue.end()
+ .otherwise(p.error(ERROR.INVALID_CONTENT_LENGTH, reason));
+ };
+
+ n('header_value_content_length_once')
+ .otherwise(this.testFlags(FLAGS.CONTENT_LENGTH, {
+ 0: n('header_value_content_length'),
+ }, p.error(ERROR.UNEXPECTED_CONTENT_LENGTH, 'Duplicate Content-Length')));
+
+ n('header_value_content_length')
+ .select(NUM_MAP, this.mulAdd('content_length', {
+ overflow: invalidContentLength('Content-Length overflow'),
+ success: 'header_value_content_length',
+ }))
+ .otherwise(n('header_value_content_length_ws'));
+
+ n('header_value_content_length_ws')
+ .match(' ', n('header_value_content_length_ws'))
+ .peek([ '\r', '\n' ],
+ this.setFlag(FLAGS.CONTENT_LENGTH, 'header_value_otherwise'))
+ .otherwise(invalidContentLength('Invalid character in Content-Length'));
+
+ //
+ // Connection
+ //
+
+ n('header_value_connection')
+ .transform(p.transform.toLower())
+ // TODO(indutny): extra node for token back-edge?
+ // Skip lws
+ .match([ ' ', '\t' ], n('header_value_connection'))
+ .match(
+ 'close',
+ this.update('header_state', HEADER_STATE.CONNECTION_CLOSE,
+ 'header_value_connection_ws'),
+ )
+ .match(
+ 'upgrade',
+ this.update('header_state', HEADER_STATE.CONNECTION_UPGRADE,
+ 'header_value_connection_ws'),
+ )
+ .match(
+ 'keep-alive',
+ this.update('header_state', HEADER_STATE.CONNECTION_KEEP_ALIVE,
+ 'header_value_connection_ws'),
+ )
+ .otherwise(n('header_value_connection_token'));
+
+ n('header_value_connection_ws')
+ .match(',', this.setHeaderFlags('header_value_connection'))
+ .match(' ', n('header_value_connection_ws'))
+ .peek([ '\r', '\n' ], n('header_value_otherwise'))
+ .otherwise(this.resetHeaderState('header_value_connection_token'));
+
+ n('header_value_connection_token')
+ .match(',', n('header_value_connection'))
+ .match(CONNECTION_TOKEN_CHARS,
+ n('header_value_connection_token'))
+ .otherwise(n('header_value_otherwise'));
+
+ // Split for performance reasons
+ n('header_value')
+ .match(HEADER_CHARS, n('header_value'))
+ .otherwise(n('header_value_otherwise'));
+
+ const checkIfAllowLFWithoutCR = (success: Node, failure: Node) => {
+ return this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_CR_BEFORE_LF, { 1: success }, failure);
+ };
+
+ const checkLenient = this.testLenientFlags(LENIENT_FLAGS.HEADERS, {
+ 1: n('header_value_lenient'),
+ }, span.headerValue.end(p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header value char')));
+
+ n('header_value_otherwise')
+ .peek('\r', span.headerValue.end().skipTo(n('header_value_almost_done')))
+ .peek(
+ '\n',
+ span.headerValue.end(
+ checkIfAllowLFWithoutCR(
+ n('header_value_almost_done'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after header value'),
+ ),
+ ),
+ )
+ .otherwise(checkLenient);
+
+ n('header_value_lenient')
+ .peek('\r', span.headerValue.end().skipTo(n('header_value_almost_done')))
+ .peek('\n', span.headerValue.end(n('header_value_almost_done')))
+ .skipTo(n('header_value_lenient'));
+
+ n('header_value_almost_done')
+ .match('\n', n('header_value_lws'))
+ .otherwise(p.error(ERROR.LF_EXPECTED,
+ 'Missing expected LF after header value'));
+
+ n('header_value_lws')
+ .peek([ ' ', '\t' ],
+ this.load('header_state', {
+ [HEADER_STATE.TRANSFER_ENCODING_CHUNKED]:
+ this.resetHeaderState(span.headerValue.start(n('header_value_start'))),
+ }, span.headerValue.start(n('header_value_start'))))
+ .otherwise(this.setHeaderFlags(onHeaderValueComplete));
+
+ const checkTrailing = this.testFlags(FLAGS.TRAILING, {
+ 1: this.invokePausable('on_chunk_complete',
+ ERROR.CB_CHUNK_COMPLETE, 'message_done'),
+ }).otherwise(this.headersCompleted());
+
+ n('headers_almost_done')
+ .match('\n', checkTrailing)
+ .otherwise(
+ this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_LF_AFTER_CR, {
+ 1: checkTrailing,
+ }, p.error(ERROR.STRICT, 'Expected LF after headers')));
+
+ const upgradePause = p.pause(ERROR.PAUSED_UPGRADE,
+ 'Pause on CONNECT/Upgrade');
+
+ const afterHeadersComplete = p.invoke(callback.afterHeadersComplete, {
+ 1: this.invokePausable('on_message_complete',
+ ERROR.CB_MESSAGE_COMPLETE, upgradePause),
+ 2: n('chunk_size_start'),
+ 3: n('body_identity'),
+ 4: n('body_identity_eof'),
+
+ // non-chunked `Transfer-Encoding` for request, see `src/native/http.c`
+ 5: p.error(ERROR.INVALID_TRANSFER_ENCODING,
+ 'Request has invalid `Transfer-Encoding`'),
+ });
+
+ n('headers_done')
+ .otherwise(afterHeadersComplete);
+
+ upgradePause
+ .otherwise(n('cleanup'));
+
+ afterHeadersComplete
+ .otherwise(this.invokePausable('on_message_complete',
+ ERROR.CB_MESSAGE_COMPLETE, 'cleanup'));
+
+ n('body_identity')
+ .otherwise(span.body.start()
+ .otherwise(p.consume('content_length').otherwise(
+ span.body.end(n('message_done')))));
+
+ n('body_identity_eof')
+ .otherwise(
+ this.update('finish', FINISH.SAFE_WITH_CB, span.body.start(n('eof'))));
+
+ // Just read everything until EOF
+ n('eof')
+ .skipTo(n('eof'));
+
+ n('chunk_size_start')
+ .otherwise(this.update('content_length', 0, 'chunk_size_digit'));
+
+ const addContentLength = this.mulAdd('content_length', {
+ overflow: p.error(ERROR.INVALID_CHUNK_SIZE, 'Chunk size overflow'),
+ success: 'chunk_size',
+ }, { signed: false, base: 0x10 });
+
+ n('chunk_size_digit')
+ .select(HEX_MAP, addContentLength)
+ .otherwise(p.error(ERROR.INVALID_CHUNK_SIZE,
+ 'Invalid character in chunk size'));
+
+ n('chunk_size')
+ .select(HEX_MAP, addContentLength)
+ .otherwise(n('chunk_size_otherwise'));
+
+ n('chunk_size_otherwise')
+ .match(
+ [ ' ', '\t' ],
+ this.testLenientFlags(
+ LENIENT_FLAGS.SPACES_AFTER_CHUNK_SIZE,
+ {
+ 1: n('chunk_size_otherwise'),
+ },
+ p.error(ERROR.INVALID_CHUNK_SIZE, 'Invalid character in chunk size'),
+ ),
+ )
+ .match('\r', n('chunk_size_almost_done'))
+ .match(
+ '\n',
+ checkIfAllowLFWithoutCR(
+ n('chunk_size_almost_done'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after chunk size'),
+ ),
+ )
+ .match(';', n('chunk_extensions'))
+ .otherwise(p.error(ERROR.INVALID_CHUNK_SIZE,
+ 'Invalid character in chunk size'));
+
+ const onChunkExtensionNameCompleted = (destination: Node) => {
+ return this.invokePausable(
+ 'on_chunk_extension_name', ERROR.CB_CHUNK_EXTENSION_NAME_COMPLETE, destination);
+ };
+
+ const onChunkExtensionValueCompleted = (destination: Node) => {
+ return this.invokePausable(
+ 'on_chunk_extension_value', ERROR.CB_CHUNK_EXTENSION_VALUE_COMPLETE, destination);
+ };
+
+ n('chunk_extensions')
+ .match(' ', p.error(ERROR.STRICT, 'Invalid character in chunk extensions'))
+ .match('\r', p.error(ERROR.STRICT, 'Invalid character in chunk extensions'))
+ .otherwise(this.span.chunkExtensionName.start(n('chunk_extension_name')));
+
+ n('chunk_extension_name')
+ .match(TOKEN, n('chunk_extension_name'))
+ .peek('=', this.span.chunkExtensionName.end().skipTo(
+ this.span.chunkExtensionValue.start(
+ onChunkExtensionNameCompleted(n('chunk_extension_value')),
+ ),
+ ))
+ .peek(';', this.span.chunkExtensionName.end().skipTo(
+ onChunkExtensionNameCompleted(n('chunk_extensions')),
+ ))
+ .peek('\r', this.span.chunkExtensionName.end().skipTo(
+ onChunkExtensionNameCompleted(n('chunk_size_almost_done')),
+ ))
+ .peek('\n', this.span.chunkExtensionName.end(
+ onChunkExtensionNameCompleted(
+ checkIfAllowLFWithoutCR(
+ n('chunk_size_almost_done'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after chunk extension name'),
+ ),
+ ),
+ ))
+ .otherwise(this.span.chunkExtensionName.end().skipTo(
+ p.error(ERROR.STRICT, 'Invalid character in chunk extensions name'),
+ ));
+
+ n('chunk_extension_value')
+ .match('"', n('chunk_extension_quoted_value'))
+ .match(TOKEN, n('chunk_extension_value'))
+ .peek(';', this.span.chunkExtensionValue.end().skipTo(
+ onChunkExtensionValueCompleted(n('chunk_extensions')),
+ ))
+ .peek('\r', this.span.chunkExtensionValue.end().skipTo(
+ onChunkExtensionValueCompleted(n('chunk_size_almost_done')),
+ ))
+ .peek('\n', this.span.chunkExtensionValue.end(
+ onChunkExtensionValueCompleted(
+ checkIfAllowLFWithoutCR(
+ n('chunk_size_almost_done'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after chunk extension value'),
+ ),
+ ),
+ ))
+ .otherwise(this.span.chunkExtensionValue.end().skipTo(
+ p.error(ERROR.STRICT, 'Invalid character in chunk extensions value'),
+ ));
+
+ n('chunk_extension_quoted_value')
+ .match(QUOTED_STRING, n('chunk_extension_quoted_value'))
+ .match('"', this.span.chunkExtensionValue.end(
+ onChunkExtensionValueCompleted(n('chunk_extension_quoted_value_done')),
+ ))
+ .match('\\', n('chunk_extension_quoted_value_quoted_pair'))
+ .otherwise(this.span.chunkExtensionValue.end().skipTo(
+ p.error(ERROR.STRICT, 'Invalid character in chunk extensions quoted value'),
+ ));
+
+ n('chunk_extension_quoted_value_quoted_pair')
+ .match(HTAB_SP_VCHAR_OBS_TEXT, n('chunk_extension_quoted_value'))
+ .otherwise(this.span.chunkExtensionValue.end().skipTo(
+ p.error(ERROR.STRICT, 'Invalid quoted-pair in chunk extensions quoted value'),
+ ));
+
+ n('chunk_extension_quoted_value_done')
+ .match(';', n('chunk_extensions'))
+ .match('\r', n('chunk_size_almost_done'))
+ .peek(
+ '\n',
+ checkIfAllowLFWithoutCR(
+ n('chunk_size_almost_done'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after chunk extension value'),
+ ),
+ )
+ .otherwise(p.error(ERROR.STRICT,
+ 'Invalid character in chunk extensions quote value'));
+
+ n('chunk_size_almost_done')
+ .match('\n', n('chunk_size_almost_done_lf'))
+ .otherwise(
+ this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_LF_AFTER_CR, {
+ 1: n('chunk_size_almost_done_lf'),
+ }).otherwise(p.error(ERROR.STRICT, 'Expected LF after chunk size')),
+ );
+
+ const toChunk = this.isEqual('content_length', 0, {
+ equal: this.setFlag(FLAGS.TRAILING, 'header_field_start'),
+ notEqual: 'chunk_data',
+ });
+
+ n('chunk_size_almost_done_lf')
+ .otherwise(this.invokePausable('on_chunk_header',
+ ERROR.CB_CHUNK_HEADER, toChunk));
+
+ n('chunk_data')
+ .otherwise(span.body.start()
+ .otherwise(p.consume('content_length').otherwise(
+ span.body.end(n('chunk_data_almost_done')))));
+
+ n('chunk_data_almost_done')
+ .match('\r\n', n('chunk_complete'))
+ .match(
+ '\n',
+ checkIfAllowLFWithoutCR(
+ n('chunk_complete'),
+ p.error(ERROR.CR_EXPECTED, 'Missing expected CR after chunk data'),
+ ),
+ )
+ .otherwise(
+ this.testLenientFlags(LENIENT_FLAGS.OPTIONAL_CRLF_AFTER_CHUNK, {
+ 1: n('chunk_complete'),
+ }).otherwise(p.error(ERROR.STRICT, 'Expected LF after chunk data')),
+ );
+
+ n('chunk_complete')
+ .otherwise(this.invokePausable('on_chunk_complete',
+ ERROR.CB_CHUNK_COMPLETE, 'chunk_size_start'));
+
+ const upgradeAfterDone = this.isEqual('upgrade', 1, {
+ // Exit, the rest of the message is in a different protocol.
+ equal: upgradePause,
+
+ // Restart
+ notEqual: 'cleanup',
+ });
+
+ n('message_done')
+ .otherwise(this.invokePausable('on_message_complete',
+ ERROR.CB_MESSAGE_COMPLETE, upgradeAfterDone));
+
+ const lenientClose = this.testLenientFlags(LENIENT_FLAGS.KEEP_ALIVE, {
+ 1: n('restart'),
+ }, n('closed'));
+
+ // Check if we'd like to keep-alive
+ n('cleanup')
+ .otherwise(p.invoke(callback.afterMessageComplete, {
+ 1: this.update('content_length', 0, n('restart')),
+ }, this.update('finish', FINISH.SAFE, lenientClose)));
+
+ const lenientDiscardAfterClose = this.testLenientFlags(LENIENT_FLAGS.DATA_AFTER_CLOSE, {
+ 1: n('closed'),
+ }, p.error(ERROR.CLOSED_CONNECTION, 'Data after `Connection: close`'));
+
+ n('closed')
+ .match([ '\r', '\n' ], n('closed'))
+ .skipTo(lenientDiscardAfterClose);
+
+ n('restart')
+ .otherwise(
+ this.update('initial_message_completed', 1, this.update('finish', FINISH.SAFE, n('start')),
+ ));
+ }
+
+ private headersCompleted(): Node {
+ const p = this.llparse;
+ const callback = this.callback;
+ const n = (name: string): Match => this.node<Match>(name);
+
+ // Set `upgrade` if needed
+ const beforeHeadersComplete = p.invoke(callback.beforeHeadersComplete);
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of receiving a response to a HEAD
+ * request.
+ *
+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
+ */
+ const onHeadersComplete = p.invoke(callback.onHeadersComplete, {
+ 0: n('headers_done'),
+ 1: this.setFlag(FLAGS.SKIPBODY, 'headers_done'),
+ 2: this.update('upgrade', 1,
+ this.setFlag(FLAGS.SKIPBODY, 'headers_done')),
+ [ERROR.PAUSED]: this.pause('Paused by on_headers_complete',
+ 'headers_done'),
+ }, p.error(ERROR.CB_HEADERS_COMPLETE, 'User callback error'));
+
+ beforeHeadersComplete.otherwise(onHeadersComplete);
+
+ return beforeHeadersComplete;
+ }
+
+ private node<T extends Node>(name: string | T): T {
+ if (name instanceof Node) {
+ return name;
+ }
+
+ assert(this.nodes.has(name), `Unknown node with name "${name}"`);
+ return this.nodes.get(name)! as any;
+ }
+
+ private load(field: string, map: { [key: number]: Node },
+ next?: string | Node): Node {
+ const p = this.llparse;
+
+ const res = p.invoke(p.code.load(field), map);
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private store(field: string, next?: string | Node): Node {
+ const p = this.llparse;
+
+ const res = p.invoke(p.code.store(field));
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private update(field: string, value: number, next?: string | Node): Node {
+ const p = this.llparse;
+
+ const res = p.invoke(p.code.update(field, value));
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private resetHeaderState(next: string | Node): Node {
+ return this.update('header_state', HEADER_STATE.GENERAL, next);
+ }
+
+ private emptySpan(span: source.Span, next: string | Node): Node {
+ return span.start(span.end(this.node(next)));
+ }
+
+ private unsetFlag(flag: FLAGS, next: string | Node): Node {
+ const p = this.llparse;
+ return p.invoke(p.code.and('flags', ~flag), this.node(next));
+ }
+
+ private setFlag(flag: FLAGS, next: string | Node): Node {
+ const p = this.llparse;
+ return p.invoke(p.code.or('flags', flag), this.node(next));
+ }
+
+ private testFlags(flag: FLAGS, map: { [key: number]: Node },
+ next?: string | Node): Node {
+ const p = this.llparse;
+ const res = p.invoke(p.code.test('flags', flag), map);
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private testLenientFlags(flag: LENIENT_FLAGS, map: { [key: number]: Node },
+ next?: string | Node): Node {
+ const p = this.llparse;
+ const res = p.invoke(p.code.test('lenient_flags', flag), map);
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private setHeaderFlags(next: string | Node): Node {
+ const HS = HEADER_STATE;
+ const F = FLAGS;
+
+ const toConnection =
+ this.update('header_state', HEADER_STATE.CONNECTION, next);
+
+ return this.load('header_state', {
+ [HS.CONNECTION_KEEP_ALIVE]:
+ this.setFlag(F.CONNECTION_KEEP_ALIVE, toConnection),
+ [HS.CONNECTION_CLOSE]: this.setFlag(F.CONNECTION_CLOSE, toConnection),
+ [HS.CONNECTION_UPGRADE]: this.setFlag(F.CONNECTION_UPGRADE, toConnection),
+ [HS.TRANSFER_ENCODING_CHUNKED]: this.setFlag(F.CHUNKED, next),
+ }, this.node(next));
+ }
+
+ private mulAdd(field: string, targets: IMulTargets,
+ options: IMulOptions = { base: 10, signed: false }): Node {
+ const p = this.llparse;
+
+ return p.invoke(p.code.mulAdd(field, options), {
+ 1: this.node(targets.overflow),
+ }, this.node(targets.success));
+ }
+
+ private isEqual(field: string, value: number, map: IIsEqualTargets) {
+ const p = this.llparse;
+ return p.invoke(p.code.isEqual(field, value), {
+ 0: this.node(map.notEqual),
+ }, this.node(map.equal));
+ }
+
+ private pause(msg: string, next?: string | Node) {
+ const p = this.llparse;
+ const res = p.pause(ERROR.PAUSED, msg);
+ if (next !== undefined) {
+ res.otherwise(this.node(next));
+ }
+ return res;
+ }
+
+ private invokePausable(name: string, errorCode: ERROR, next: string | Node)
+ : Node {
+ let cb;
+
+ switch (name) {
+ case 'on_message_begin':
+ cb = this.callback.onMessageBegin;
+ break;
+ case 'on_url_complete':
+ cb = this.callback.onUrlComplete;
+ break;
+ case 'on_status_complete':
+ cb = this.callback.onStatusComplete;
+ break;
+ case 'on_method_complete':
+ cb = this.callback.onMethodComplete;
+ break;
+ case 'on_version_complete':
+ cb = this.callback.onVersionComplete;
+ break;
+ case 'on_header_field_complete':
+ cb = this.callback.onHeaderFieldComplete;
+ break;
+ case 'on_header_value_complete':
+ cb = this.callback.onHeaderValueComplete;
+ break;
+ case 'on_message_complete':
+ cb = this.callback.onMessageComplete;
+ break;
+ case 'on_chunk_header':
+ cb = this.callback.onChunkHeader;
+ break;
+ case 'on_chunk_extension_name':
+ cb = this.callback.onChunkExtensionName;
+ break;
+ case 'on_chunk_extension_value':
+ cb = this.callback.onChunkExtensionValue;
+ break;
+ case 'on_chunk_complete':
+ cb = this.callback.onChunkComplete;
+ break;
+ case 'on_reset':
+ cb = this.callback.onReset;
+ break;
+ default:
+ throw new Error('Unknown callback: ' + name);
+ }
+
+ const p = this.llparse;
+ return p.invoke(cb, {
+ 0: this.node(next),
+ [ERROR.PAUSED]: this.pause(`${name} pause`, next),
+ }, p.error(errorCode, `\`${name}\` callback error`));
+ }
+}
diff --git a/llhttp/src/llhttp/url.ts b/llhttp/src/llhttp/url.ts
new file mode 100644
index 0000000..c5fced9
--- /dev/null
+++ b/llhttp/src/llhttp/url.ts
@@ -0,0 +1,220 @@
+import { LLParse, source } from 'llparse';
+
+import Match = source.node.Match;
+import Node = source.node.Node;
+
+import {
+ ALPHA,
+ CharList,
+ ERROR,
+ URL_CHAR,
+ USERINFO_CHARS,
+} from './constants';
+
+type SpanName = 'schema' | 'host' | 'path' | 'query' | 'fragment' | 'url';
+
+export interface IURLResult {
+ readonly entry: {
+ readonly normal: Node;
+ readonly connect: Node;
+ };
+ readonly exit: {
+ readonly toHTTP: Node;
+ readonly toHTTP09: Node;
+ };
+}
+
+type SpanTable = Map<SpanName, source.Span>;
+
+export class URL {
+ private readonly spanTable: SpanTable = new Map();
+ private readonly errorInvalid: Node;
+ private readonly URL_CHAR: CharList;
+
+ constructor(private readonly llparse: LLParse, separateSpans: boolean = false) {
+ const p = this.llparse;
+
+ this.errorInvalid = p.error(ERROR.INVALID_URL, 'Invalid characters in url');
+
+ this.URL_CHAR = URL_CHAR;
+
+ const table = this.spanTable;
+ if (separateSpans) {
+ table.set('schema', p.span(p.code.span('llhttp__on_url_schema')));
+ table.set('host', p.span(p.code.span('llhttp__on_url_host')));
+ table.set('path', p.span(p.code.span('llhttp__on_url_path')));
+ table.set('query', p.span(p.code.span('llhttp__on_url_query')));
+ table.set('fragment',
+ p.span(p.code.span('llhttp__on_url_fragment')));
+ } else {
+ table.set('url', p.span(p.code.span('llhttp__on_url')));
+ }
+ }
+
+ public build(): IURLResult {
+ const p = this.llparse;
+
+ const entry = {
+ connect: this.node('entry_connect'),
+ normal: this.node('entry_normal'),
+ };
+
+ const start = this.node('start');
+ const path = this.node('path');
+ const queryOrFragment = this.node('query_or_fragment');
+ const schema = this.node('schema');
+ const schemaDelim = this.node('schema_delim');
+ const server = this.node('server');
+ const queryStart = this.node('query_start');
+ const query = this.node('query');
+ const fragment = this.node('fragment');
+ const serverWithAt = this.node('server_with_at');
+
+ entry.normal
+ .otherwise(this.spanStart('url', start));
+
+ entry.connect
+ .otherwise(this.spanStart('url', this.spanStart('host', server)));
+
+ start
+ .peek([ '/', '*' ], this.spanStart('path').skipTo(path))
+ .peek(ALPHA, this.spanStart('schema', schema))
+ .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected start char in url'));
+
+ schema
+ .match(ALPHA, schema)
+ .peek(':', this.spanEnd('schema').skipTo(schemaDelim))
+ .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url schema'));
+
+ schemaDelim
+ .match('//', this.spanStart('host', server))
+ .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url schema'));
+
+ for (const node of [server, serverWithAt]) {
+ node
+ .peek('/', this.spanEnd('host', this.spanStart('path').skipTo(path)))
+ .match('?', this.spanEnd('host', this.spanStart('query', query)))
+ .match(USERINFO_CHARS, server)
+ .match([ '[', ']' ], server)
+ .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url server'));
+
+ if (node !== serverWithAt) {
+ node.match('@', serverWithAt);
+ }
+ }
+
+ serverWithAt
+ .match('@', p.error(ERROR.INVALID_URL, 'Double @ in url'));
+
+ path
+ .match(this.URL_CHAR, path)
+ .otherwise(this.spanEnd('path', queryOrFragment));
+
+ // Performance optimization, split `path` so that the fast case remains
+ // there
+ queryOrFragment
+ .match('?', this.spanStart('query', query))
+ .match('#', this.spanStart('fragment', fragment))
+ .otherwise(p.error(ERROR.INVALID_URL, 'Invalid char in url path'));
+
+ query
+ .match(this.URL_CHAR, query)
+ // Allow extra '?' in query string
+ .match('?', query)
+ .peek('#', this.spanEnd('query')
+ .skipTo(this.spanStart('fragment', fragment)))
+ .otherwise(p.error(ERROR.INVALID_URL, 'Invalid char in url query'));
+
+ fragment
+ .match(this.URL_CHAR, fragment)
+ .match([ '?', '#' ], fragment)
+ .otherwise(
+ p.error(ERROR.INVALID_URL, 'Invalid char in url fragment start'));
+
+ for (const node of [ start, schema, schemaDelim ]) {
+ /* No whitespace allowed here */
+ node.match([ ' ', '\r', '\n' ], this.errorInvalid);
+ }
+
+ // Adaptors
+ const toHTTP = this.node('to_http');
+ const toHTTP09 = this.node('to_http_09');
+
+ const skipToHTTP = this.node('skip_to_http')
+ .skipTo(toHTTP);
+
+ const skipToHTTP09 = this.node('skip_to_http09')
+ .skipTo(toHTTP09);
+
+ const skipCRLF = this.node('skip_lf_to_http09')
+ .match('\r\n', toHTTP09)
+ .otherwise(p.error(ERROR.INVALID_URL, 'Expected CRLF'));
+
+ for (const node of [server, serverWithAt, queryOrFragment, queryStart, query, fragment]) {
+ let spanName: SpanName | undefined;
+
+ if (node === server || node === serverWithAt) {
+ spanName = 'host';
+ } else if (node === queryStart || node === query) {
+ spanName = 'query';
+ } else if (node === fragment) {
+ spanName = 'fragment';
+ }
+
+ const endTo = (target: Node): Node => {
+ let res: Node = this.spanEnd('url', target);
+ if (spanName !== undefined) {
+ res = this.spanEnd(spanName, res);
+ }
+ return res;
+ };
+
+ node.peek(' ', endTo(skipToHTTP));
+
+ node.peek('\r', endTo(skipCRLF));
+ node.peek('\n', endTo(skipToHTTP09));
+ }
+
+ return {
+ entry,
+ exit: {
+ toHTTP,
+ toHTTP09,
+ },
+ };
+ }
+
+ private spanStart(name: SpanName, otherwise?: Node): Node {
+ let res: Node;
+ if (this.spanTable.has(name)) {
+ res = this.spanTable.get(name)!.start();
+ } else {
+ res = this.llparse.node('span_start_stub_' + name);
+ }
+ if (otherwise !== undefined) {
+ res.otherwise(otherwise);
+ }
+ return res;
+ }
+
+ private spanEnd(name: SpanName, otherwise?: Node): Node {
+ let res: Node;
+ if (this.spanTable.has(name)) {
+ res = this.spanTable.get(name)!.end();
+ } else {
+ res = this.llparse.node('span_end_stub_' + name);
+ }
+ if (otherwise !== undefined) {
+ res.otherwise(otherwise);
+ }
+ return res;
+ }
+
+ private node(name: string): Match {
+ const res = this.llparse.node('url_' + name);
+
+ res.match([ '\t', '\f' ], this.errorInvalid);
+
+ return res;
+ }
+}
diff --git a/llhttp/src/llhttp/utils.ts b/llhttp/src/llhttp/utils.ts
new file mode 100644
index 0000000..7c01d66
--- /dev/null
+++ b/llhttp/src/llhttp/utils.ts
@@ -0,0 +1,27 @@
+export interface IEnumMap {
+ [key: string]: number;
+}
+
+export function enumToMap(
+ obj: any,
+ filter?: ReadonlyArray<number>,
+ exceptions?: ReadonlyArray<number>,
+): IEnumMap {
+ const res: IEnumMap = {};
+
+ for (const key of Object.keys(obj)) {
+ const value = obj[key];
+ if (typeof value !== 'number') {
+ continue;
+ }
+ if (filter && !filter.includes(value)) {
+ continue;
+ }
+ if (exceptions && exceptions.includes(value)) {
+ continue;
+ }
+ res[key] = value;
+ }
+
+ return res;
+}
diff --git a/llhttp/src/native/api.c b/llhttp/src/native/api.c
new file mode 100644
index 0000000..8c2ce3d
--- /dev/null
+++ b/llhttp/src/native/api.c
@@ -0,0 +1,510 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "llhttp.h"
+
+#define CALLBACK_MAYBE(PARSER, NAME) \
+ do { \
+ const llhttp_settings_t* settings; \
+ settings = (const llhttp_settings_t*) (PARSER)->settings; \
+ if (settings == NULL || settings->NAME == NULL) { \
+ err = 0; \
+ break; \
+ } \
+ err = settings->NAME((PARSER)); \
+ } while (0)
+
+#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \
+ do { \
+ const llhttp_settings_t* settings; \
+ settings = (const llhttp_settings_t*) (PARSER)->settings; \
+ if (settings == NULL || settings->NAME == NULL) { \
+ err = 0; \
+ break; \
+ } \
+ err = settings->NAME((PARSER), (START), (LEN)); \
+ if (err == -1) { \
+ err = HPE_USER; \
+ llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \
+ } \
+ } while (0)
+
+void llhttp_init(llhttp_t* parser, llhttp_type_t type,
+ const llhttp_settings_t* settings) {
+ llhttp__internal_init(parser);
+
+ parser->type = type;
+ parser->settings = (void*) settings;
+}
+
+
+#if defined(__wasm__)
+
+extern int wasm_on_message_begin(llhttp_t * p);
+extern int wasm_on_url(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_status(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_headers_complete(llhttp_t * p, int status_code,
+ uint8_t upgrade, int should_keep_alive);
+extern int wasm_on_body(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_message_complete(llhttp_t * p);
+
+static int wasm_on_headers_complete_wrap(llhttp_t* p) {
+ return wasm_on_headers_complete(p, p->status_code, p->upgrade,
+ llhttp_should_keep_alive(p));
+}
+
+const llhttp_settings_t wasm_settings = {
+ wasm_on_message_begin,
+ wasm_on_url,
+ wasm_on_status,
+ NULL,
+ NULL,
+ wasm_on_header_field,
+ wasm_on_header_value,
+ NULL,
+ NULL,
+ wasm_on_headers_complete_wrap,
+ wasm_on_body,
+ wasm_on_message_complete,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+llhttp_t* llhttp_alloc(llhttp_type_t type) {
+ llhttp_t* parser = malloc(sizeof(llhttp_t));
+ llhttp_init(parser, type, &wasm_settings);
+ return parser;
+}
+
+void llhttp_free(llhttp_t* parser) {
+ free(parser);
+}
+
+#endif // defined(__wasm__)
+
+/* Some getters required to get stuff from the parser */
+
+uint8_t llhttp_get_type(llhttp_t* parser) {
+ return parser->type;
+}
+
+uint8_t llhttp_get_http_major(llhttp_t* parser) {
+ return parser->http_major;
+}
+
+uint8_t llhttp_get_http_minor(llhttp_t* parser) {
+ return parser->http_minor;
+}
+
+uint8_t llhttp_get_method(llhttp_t* parser) {
+ return parser->method;
+}
+
+int llhttp_get_status_code(llhttp_t* parser) {
+ return parser->status_code;
+}
+
+uint8_t llhttp_get_upgrade(llhttp_t* parser) {
+ return parser->upgrade;
+}
+
+
+void llhttp_reset(llhttp_t* parser) {
+ llhttp_type_t type = parser->type;
+ const llhttp_settings_t* settings = parser->settings;
+ void* data = parser->data;
+ uint16_t lenient_flags = parser->lenient_flags;
+
+ llhttp__internal_init(parser);
+
+ parser->type = type;
+ parser->settings = (void*) settings;
+ parser->data = data;
+ parser->lenient_flags = lenient_flags;
+}
+
+
+llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) {
+ return llhttp__internal_execute(parser, data, data + len);
+}
+
+
+void llhttp_settings_init(llhttp_settings_t* settings) {
+ memset(settings, 0, sizeof(*settings));
+}
+
+
+llhttp_errno_t llhttp_finish(llhttp_t* parser) {
+ int err;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (parser->error != 0) {
+ return 0;
+ }
+
+ switch (parser->finish) {
+ case HTTP_FINISH_SAFE_WITH_CB:
+ CALLBACK_MAYBE(parser, on_message_complete);
+ if (err != HPE_OK) return err;
+
+ /* FALLTHROUGH */
+ case HTTP_FINISH_SAFE:
+ return HPE_OK;
+ case HTTP_FINISH_UNSAFE:
+ parser->reason = "Invalid EOF state";
+ return HPE_INVALID_EOF_STATE;
+ default:
+ abort();
+ }
+}
+
+
+void llhttp_pause(llhttp_t* parser) {
+ if (parser->error != HPE_OK) {
+ return;
+ }
+
+ parser->error = HPE_PAUSED;
+ parser->reason = "Paused";
+}
+
+
+void llhttp_resume(llhttp_t* parser) {
+ if (parser->error != HPE_PAUSED) {
+ return;
+ }
+
+ parser->error = 0;
+}
+
+
+void llhttp_resume_after_upgrade(llhttp_t* parser) {
+ if (parser->error != HPE_PAUSED_UPGRADE) {
+ return;
+ }
+
+ parser->error = 0;
+}
+
+
+llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) {
+ return parser->error;
+}
+
+
+const char* llhttp_get_error_reason(const llhttp_t* parser) {
+ return parser->reason;
+}
+
+
+void llhttp_set_error_reason(llhttp_t* parser, const char* reason) {
+ parser->reason = reason;
+}
+
+
+const char* llhttp_get_error_pos(const llhttp_t* parser) {
+ return parser->error_pos;
+}
+
+
+const char* llhttp_errno_name(llhttp_errno_t err) {
+#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME;
+ switch (err) {
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+ default: abort();
+ }
+#undef HTTP_ERRNO_GEN
+}
+
+
+const char* llhttp_method_name(llhttp_method_t method) {
+#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING;
+ switch (method) {
+ HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN)
+ default: abort();
+ }
+#undef HTTP_METHOD_GEN
+}
+
+const char* llhttp_status_name(llhttp_status_t status) {
+#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING;
+ switch (status) {
+ HTTP_STATUS_MAP(HTTP_STATUS_GEN)
+ default: abort();
+ }
+#undef HTTP_STATUS_GEN
+}
+
+
+void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_HEADERS;
+ } else {
+ parser->lenient_flags &= ~LENIENT_HEADERS;
+ }
+}
+
+
+void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_CHUNKED_LENGTH;
+ } else {
+ parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH;
+ }
+}
+
+
+void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_KEEP_ALIVE;
+ } else {
+ parser->lenient_flags &= ~LENIENT_KEEP_ALIVE;
+ }
+}
+
+void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_TRANSFER_ENCODING;
+ } else {
+ parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING;
+ }
+}
+
+void llhttp_set_lenient_version(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_VERSION;
+ } else {
+ parser->lenient_flags &= ~LENIENT_VERSION;
+ }
+}
+
+void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE;
+ } else {
+ parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE;
+ }
+}
+
+void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR;
+ } else {
+ parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR;
+ }
+}
+
+void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+ } else {
+ parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+ }
+}
+
+void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF;
+ } else {
+ parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF;
+ }
+}
+
+void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE;
+ } else {
+ parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE;
+ }
+}
+
+/* Callbacks */
+
+
+int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_message_begin);
+ return err;
+}
+
+
+int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_url_complete);
+ return err;
+}
+
+
+int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_status_complete);
+ return err;
+}
+
+
+int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_method_complete);
+ return err;
+}
+
+
+int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_version_complete);
+ return err;
+}
+
+
+int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_header_field_complete);
+ return err;
+}
+
+
+int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_header_value_complete);
+ return err;
+}
+
+
+int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_headers_complete);
+ return err;
+}
+
+
+int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_message_complete);
+ return err;
+}
+
+
+int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_header);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_extension_name_complete);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_extension_value_complete);
+ return err;
+}
+
+
+int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_complete);
+ return err;
+}
+
+
+int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_reset);
+ return err;
+}
+
+
+/* Private */
+
+
+void llhttp__debug(llhttp_t* s, const char* p, const char* endp,
+ const char* msg) {
+ if (p == endp) {
+ fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type,
+ s->flags, msg);
+ } else {
+ fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s,
+ s->type, s->flags, *p, msg);
+ }
+}
diff --git a/llhttp/src/native/api.h b/llhttp/src/native/api.h
new file mode 100644
index 0000000..321879c
--- /dev/null
+++ b/llhttp/src/native/api.h
@@ -0,0 +1,355 @@
+#ifndef INCLUDE_LLHTTP_API_H_
+#define INCLUDE_LLHTTP_API_H_
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <stddef.h>
+
+#if defined(__wasm__)
+#define LLHTTP_EXPORT __attribute__((visibility("default")))
+#else
+#define LLHTTP_EXPORT
+#endif
+
+typedef llhttp__internal_t llhttp_t;
+typedef struct llhttp_settings_s llhttp_settings_t;
+
+typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length);
+typedef int (*llhttp_cb)(llhttp_t*);
+
+struct llhttp_settings_s {
+ /* Possible return values 0, -1, `HPE_PAUSED` */
+ llhttp_cb on_message_begin;
+
+ /* Possible return values 0, -1, HPE_USER */
+ llhttp_data_cb on_url;
+ llhttp_data_cb on_status;
+ llhttp_data_cb on_method;
+ llhttp_data_cb on_version;
+ llhttp_data_cb on_header_field;
+ llhttp_data_cb on_header_value;
+ llhttp_data_cb on_chunk_extension_name;
+ llhttp_data_cb on_chunk_extension_value;
+
+ /* Possible return values:
+ * 0 - Proceed normally
+ * 1 - Assume that request/response has no body, and proceed to parsing the
+ * next message
+ * 2 - Assume absence of body (as above) and make `llhttp_execute()` return
+ * `HPE_PAUSED_UPGRADE`
+ * -1 - Error
+ * `HPE_PAUSED`
+ */
+ llhttp_cb on_headers_complete;
+
+ /* Possible return values 0, -1, HPE_USER */
+ llhttp_data_cb on_body;
+
+ /* Possible return values 0, -1, `HPE_PAUSED` */
+ llhttp_cb on_message_complete;
+ llhttp_cb on_url_complete;
+ llhttp_cb on_status_complete;
+ llhttp_cb on_method_complete;
+ llhttp_cb on_version_complete;
+ llhttp_cb on_header_field_complete;
+ llhttp_cb on_header_value_complete;
+ llhttp_cb on_chunk_extension_name_complete;
+ llhttp_cb on_chunk_extension_value_complete;
+
+ /* When on_chunk_header is called, the current chunk length is stored
+ * in parser->content_length.
+ * Possible return values 0, -1, `HPE_PAUSED`
+ */
+ llhttp_cb on_chunk_header;
+ llhttp_cb on_chunk_complete;
+ llhttp_cb on_reset;
+};
+
+/* Initialize the parser with specific type and user settings.
+ *
+ * NOTE: lifetime of `settings` has to be at least the same as the lifetime of
+ * the `parser` here. In practice, `settings` has to be either a static
+ * variable or be allocated with `malloc`, `new`, etc.
+ */
+LLHTTP_EXPORT
+void llhttp_init(llhttp_t* parser, llhttp_type_t type,
+ const llhttp_settings_t* settings);
+
+LLHTTP_EXPORT
+llhttp_t* llhttp_alloc(llhttp_type_t type);
+
+LLHTTP_EXPORT
+void llhttp_free(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_type(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_http_major(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_http_minor(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_method(llhttp_t* parser);
+
+LLHTTP_EXPORT
+int llhttp_get_status_code(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_upgrade(llhttp_t* parser);
+
+/* Reset an already initialized parser back to the start state, preserving the
+ * existing parser type, callback settings, user data, and lenient flags.
+ */
+LLHTTP_EXPORT
+void llhttp_reset(llhttp_t* parser);
+
+/* Initialize the settings object */
+LLHTTP_EXPORT
+void llhttp_settings_init(llhttp_settings_t* settings);
+
+/* Parse full or partial request/response, invoking user callbacks along the
+ * way.
+ *
+ * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing
+ * interrupts, and such errno is returned from `llhttp_execute()`. If
+ * `HPE_PAUSED` was used as a errno, the execution can be resumed with
+ * `llhttp_resume()` call.
+ *
+ * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE`
+ * is returned after fully parsing the request/response. If the user wishes to
+ * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`.
+ *
+ * NOTE: if this function ever returns a non-pause type error, it will continue
+ * to return the same error upon each successive call up until `llhttp_init()`
+ * is called.
+ */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len);
+
+/* This method should be called when the other side has no further bytes to
+ * send (e.g. shutdown of readable side of the TCP connection.)
+ *
+ * Requests without `Content-Length` and other messages might require treating
+ * all incoming bytes as the part of the body, up to the last byte of the
+ * connection. This method will invoke `on_message_complete()` callback if the
+ * request was terminated safely. Otherwise a error code would be returned.
+ */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_finish(llhttp_t* parser);
+
+/* Returns `1` if the incoming message is parsed until the last byte, and has
+ * to be completed by calling `llhttp_finish()` on EOF
+ */
+LLHTTP_EXPORT
+int llhttp_message_needs_eof(const llhttp_t* parser);
+
+/* Returns `1` if there might be any other messages following the last that was
+ * successfully parsed.
+ */
+LLHTTP_EXPORT
+int llhttp_should_keep_alive(const llhttp_t* parser);
+
+/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set
+ * appropriate error reason.
+ *
+ * Important: do not call this from user callbacks! User callbacks must return
+ * `HPE_PAUSED` if pausing is required.
+ */
+LLHTTP_EXPORT
+void llhttp_pause(llhttp_t* parser);
+
+/* Might be called to resume the execution after the pause in user's callback.
+ * See `llhttp_execute()` above for details.
+ *
+ * Call this only if `llhttp_execute()` returns `HPE_PAUSED`.
+ */
+LLHTTP_EXPORT
+void llhttp_resume(llhttp_t* parser);
+
+/* Might be called to resume the execution after the pause in user's callback.
+ * See `llhttp_execute()` above for details.
+ *
+ * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`
+ */
+LLHTTP_EXPORT
+void llhttp_resume_after_upgrade(llhttp_t* parser);
+
+/* Returns the latest return error */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_get_errno(const llhttp_t* parser);
+
+/* Returns the verbal explanation of the latest returned error.
+ *
+ * Note: User callback should set error reason when returning the error. See
+ * `llhttp_set_error_reason()` for details.
+ */
+LLHTTP_EXPORT
+const char* llhttp_get_error_reason(const llhttp_t* parser);
+
+/* Assign verbal description to the returned error. Must be called in user
+ * callbacks right before returning the errno.
+ *
+ * Note: `HPE_USER` error code might be useful in user callbacks.
+ */
+LLHTTP_EXPORT
+void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
+
+/* Returns the pointer to the last parsed byte before the returned error. The
+ * pointer is relative to the `data` argument of `llhttp_execute()`.
+ *
+ * Note: this method might be useful for counting the number of parsed bytes.
+ */
+LLHTTP_EXPORT
+const char* llhttp_get_error_pos(const llhttp_t* parser);
+
+/* Returns textual name of error code */
+LLHTTP_EXPORT
+const char* llhttp_errno_name(llhttp_errno_t err);
+
+/* Returns textual name of HTTP method */
+LLHTTP_EXPORT
+const char* llhttp_method_name(llhttp_method_t method);
+
+/* Returns textual name of HTTP status */
+LLHTTP_EXPORT
+const char* llhttp_status_name(llhttp_status_t status);
+
+/* Enables/disables lenient header value parsing (disabled by default).
+ *
+ * Lenient parsing disables header value token checks, extending llhttp's
+ * protocol support to highly non-compliant clients/server. No
+ * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when
+ * lenient parsing is "on".
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_headers(llhttp_t* parser, int enabled);
+
+
+/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and
+ * `Content-Length` headers (disabled by default).
+ *
+ * Normally `llhttp` would error when `Transfer-Encoding` is present in
+ * conjunction with `Content-Length`. This error is important to prevent HTTP
+ * request smuggling, but may be less desirable for small number of cases
+ * involving legacy servers.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled);
+
+
+/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0
+ * requests responses.
+ *
+ * Normally `llhttp` would error on (in strict mode) or discard (in loose mode)
+ * the HTTP request/response after the request/response with `Connection: close`
+ * and `Content-Length`. This is important to prevent cache poisoning attacks,
+ * but might interact badly with outdated and insecure clients. With this flag
+ * the extra request/response will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * poisoning attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of `Transfer-Encoding` header.
+ *
+ * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value
+ * and another value after it (either in a single header or in multiple
+ * headers whose value are internally joined using `, `).
+ * This is mandated by the spec to reliably determine request body size and thus
+ * avoid request smuggling.
+ * With this flag the extra value will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of HTTP version.
+ *
+ * Normally `llhttp` would error when the HTTP version in the request or status line
+ * is not `0.9`, `1.0`, `1.1` or `2.0`.
+ * With this flag the invalid value will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will allow unsupported
+ * HTTP versions. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_version(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of additional data received after a message ends
+ * and keep-alive is disabled.
+ *
+ * Normally `llhttp` would error when additional unexpected data is received if the message
+ * contains the `Connection` header with `close` value.
+ * With this flag the extra data will discarded without throwing an error.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * poisoning attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of incomplete CRLF sequences.
+ *
+ * Normally `llhttp` would error when a CR is not followed by LF when terminating the
+ * request line, the status line, the headers or a chunk header.
+ * With this flag only a CR is required to terminate such sections.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled);
+
+/*
+ * Enables/disables lenient handling of line separators.
+ *
+ * Normally `llhttp` would error when a LF is not preceded by CR when terminating the
+ * request line, the status line, the headers, a chunk header or a chunk data.
+ * With this flag only a LF is required to terminate such sections.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of chunks not separated via CRLF.
+ *
+ * Normally `llhttp` would error when after a chunk data a CRLF is missing before
+ * starting a new chunk.
+ * With this flag the new chunk can start immediately after the previous one.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of spaces after chunk size.
+ *
+ * Normally `llhttp` would error when after a chunk size is followed by one or more
+ * spaces are present instead of a CRLF or `;`.
+ * With this flag this check is disabled.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+#endif /* INCLUDE_LLHTTP_API_H_ */
diff --git a/llhttp/src/native/http.c b/llhttp/src/native/http.c
new file mode 100644
index 0000000..1ab91a5
--- /dev/null
+++ b/llhttp/src/native/http.c
@@ -0,0 +1,170 @@
+#include <stdio.h>
+#ifndef LLHTTP__TEST
+# include "llhttp.h"
+#else
+# define llhttp_t llparse_t
+#endif /* */
+
+int llhttp_message_needs_eof(const llhttp_t* parser);
+int llhttp_should_keep_alive(const llhttp_t* parser);
+
+int llhttp__before_headers_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ if ((parser->flags & F_UPGRADE) &&
+ (parser->flags & F_CONNECTION_UPGRADE)) {
+ /* For responses, "Upgrade: foo" and "Connection: upgrade" are
+ * mandatory only when it is a 101 Switching Protocols response,
+ * otherwise it is purely informational, to announce support.
+ */
+ parser->upgrade =
+ (parser->type == HTTP_REQUEST || parser->status_code == 101);
+ } else {
+ parser->upgrade = (parser->method == HTTP_CONNECT);
+ }
+ return 0;
+}
+
+
+/* Return values:
+ * 0 - No body, `restart`, message_complete
+ * 1 - CONNECT request, `restart`, message_complete, and pause
+ * 2 - chunk_size_start
+ * 3 - body_identity
+ * 4 - body_identity_eof
+ * 5 - invalid transfer-encoding for request
+ */
+int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ int hasBody;
+
+ hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;
+ if (
+ (parser->upgrade && (parser->method == HTTP_CONNECT ||
+ (parser->flags & F_SKIPBODY) || !hasBody)) ||
+ /* See RFC 2616 section 4.4 - 1xx e.g. Continue */
+ (parser->type == HTTP_RESPONSE && parser->status_code == 101)
+ ) {
+ /* Exit, the rest of the message is in a different protocol. */
+ return 1;
+ }
+
+ if (parser->type == HTTP_RESPONSE && parser->status_code == 100) {
+ /* No body, restart as the message is complete */
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (
+ parser->flags & F_SKIPBODY || /* response to a HEAD request */
+ (
+ parser->type == HTTP_RESPONSE && (
+ parser->status_code == 102 || /* Processing */
+ parser->status_code == 103 || /* Early Hints */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 /* Not Modified */
+ )
+ )
+ ) {
+ return 0;
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header, prepare for a chunk */
+ return 2;
+ } else if (parser->flags & F_TRANSFER_ENCODING) {
+ if (parser->type == HTTP_REQUEST &&
+ (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 &&
+ (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field
+ * is present in a request and the chunked transfer coding is not
+ * the final encoding, the message body length cannot be determined
+ * reliably; the server MUST respond with the 400 (Bad Request)
+ * status code and then close the connection.
+ */
+ return 5;
+ } else {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field is present in a response and
+ * the chunked transfer coding is not the final encoding, the
+ * message body length is determined by reading the connection until
+ * it is closed by the server.
+ */
+ return 4;
+ }
+ } else {
+ if (!(parser->flags & F_CONTENT_LENGTH)) {
+ if (!llhttp_message_needs_eof(parser)) {
+ /* Assume content-length 0 - read the next */
+ return 0;
+ } else {
+ /* Read body until EOF */
+ return 4;
+ }
+ } else if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ return 0;
+ } else {
+ /* Content-Length header given and non-zero */
+ return 3;
+ }
+ }
+}
+
+
+int llhttp__after_message_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ int should_keep_alive;
+
+ should_keep_alive = llhttp_should_keep_alive(parser);
+ parser->finish = HTTP_FINISH_SAFE;
+ parser->flags = 0;
+
+ /* NOTE: this is ignored in loose parsing mode */
+ return should_keep_alive;
+}
+
+
+int llhttp_message_needs_eof(const llhttp_t* parser) {
+ if (parser->type == HTTP_REQUEST) {
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 || /* Not Modified */
+ (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */
+ return 0;
+ }
+
+ /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */
+ if ((parser->flags & F_TRANSFER_ENCODING) &&
+ (parser->flags & F_CHUNKED) == 0) {
+ return 1;
+ }
+
+ if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int llhttp_should_keep_alive(const llhttp_t* parser) {
+ if (parser->http_major > 0 && parser->http_minor > 0) {
+ /* HTTP/1.1 */
+ if (parser->flags & F_CONNECTION_CLOSE) {
+ return 0;
+ }
+ } else {
+ /* HTTP/1.0 or earlier */
+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+ return 0;
+ }
+ }
+
+ return !llhttp_message_needs_eof(parser);
+}
diff --git a/llhttp/test/fixtures/extra.c b/llhttp/test/fixtures/extra.c
new file mode 100644
index 0000000..dadf8dc
--- /dev/null
+++ b/llhttp/test/fixtures/extra.c
@@ -0,0 +1,457 @@
+#include <stdlib.h>
+
+#include "fixture.h"
+
+int llhttp__on_url(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url", p, endp);
+}
+
+
+int llhttp__on_url_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "url complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_URL_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_url_schema(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url.schema", p, endp);
+}
+
+
+int llhttp__on_url_host(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url.host", p, endp);
+}
+
+
+int llhttp__on_url_path(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url.path", p, endp);
+}
+
+
+int llhttp__on_url_query(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url.query", p, endp);
+}
+
+
+int llhttp__on_url_fragment(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("url.fragment", p, endp);
+}
+
+
+#ifdef LLHTTP__TEST_HTTP
+
+void llhttp__test_init_request(llparse_t* s) {
+ s->type = HTTP_REQUEST;
+}
+
+
+void llhttp__test_init_response(llparse_t* s) {
+ s->type = HTTP_RESPONSE;
+}
+
+
+void llhttp__test_init_request_lenient_all(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |=
+ LENIENT_HEADERS | LENIENT_CHUNKED_LENGTH | LENIENT_KEEP_ALIVE |
+ LENIENT_TRANSFER_ENCODING | LENIENT_VERSION | LENIENT_DATA_AFTER_CLOSE |
+ LENIENT_OPTIONAL_LF_AFTER_CR | LENIENT_OPTIONAL_CR_BEFORE_LF |
+ LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+}
+
+
+void llhttp__test_init_response_lenient_all(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |=
+ LENIENT_HEADERS | LENIENT_CHUNKED_LENGTH | LENIENT_KEEP_ALIVE |
+ LENIENT_TRANSFER_ENCODING | LENIENT_VERSION | LENIENT_DATA_AFTER_CLOSE |
+ LENIENT_OPTIONAL_LF_AFTER_CR | LENIENT_OPTIONAL_CR_BEFORE_LF |
+ LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+}
+
+
+void llhttp__test_init_request_lenient_headers(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_HEADERS;
+}
+
+
+void llhttp__test_init_request_lenient_chunked_length(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_CHUNKED_LENGTH;
+}
+
+
+void llhttp__test_init_request_lenient_keep_alive(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_KEEP_ALIVE;
+}
+
+void llhttp__test_init_request_lenient_transfer_encoding(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_TRANSFER_ENCODING;
+}
+
+
+void llhttp__test_init_request_lenient_version(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_VERSION;
+}
+
+
+void llhttp__test_init_response_lenient_keep_alive(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_KEEP_ALIVE;
+}
+
+void llhttp__test_init_response_lenient_version(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_VERSION;
+}
+
+
+void llhttp__test_init_response_lenient_headers(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_HEADERS;
+}
+
+void llhttp__test_init_request_lenient_data_after_close(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_DATA_AFTER_CLOSE;
+}
+
+void llhttp__test_init_response_lenient_data_after_close(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_DATA_AFTER_CLOSE;
+}
+
+void llhttp__test_init_request_lenient_optional_lf_after_cr(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR;
+}
+
+void llhttp__test_init_response_lenient_optional_lf_after_cr(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR;
+}
+
+void llhttp__test_init_request_lenient_optional_cr_before_lf(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF;
+}
+
+void llhttp__test_init_response_lenient_optional_cr_before_lf(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF;
+}
+
+void llhttp__test_init_request_lenient_optional_crlf_after_chunk(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+}
+
+void llhttp__test_init_response_lenient_optional_crlf_after_chunk(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+}
+
+void llhttp__test_init_request_lenient_spaces_after_chunk_size(llparse_t* s) {
+ llhttp__test_init_request(s);
+ s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE;
+}
+
+void llhttp__test_init_response_lenient_spaces_after_chunk_size(llparse_t* s) {
+ llhttp__test_init_response(s);
+ s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE;
+}
+
+
+void llhttp__test_finish(llparse_t* s) {
+ llparse__print(NULL, NULL, "finish=%d", s->finish);
+}
+
+
+int llhttp__on_message_begin(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "message begin");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_BEGIN
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_message_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "message complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_status(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("status", p, endp);
+}
+
+
+int llhttp__on_status_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "status complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_STATUS_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_method(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench || s->type != HTTP_REQUEST)
+ return 0;
+
+ return llparse__print_span("method", p, endp);
+}
+
+
+int llhttp__on_method_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "method complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_METHOD_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_version(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("version", p, endp);
+}
+
+
+int llhttp__on_version_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "version complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_VERSION_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+int llhttp__on_header_field(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("header_field", p, endp);
+}
+
+
+int llhttp__on_header_field_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "header_field complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_FIELD_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_header_value(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("header_value", p, endp);
+}
+
+
+int llhttp__on_header_value_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "header_value complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_VALUE_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_headers_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ if (s->type == HTTP_REQUEST) {
+ llparse__print(p, endp,
+ "headers complete method=%d v=%d/%d flags=%x content_length=%llu",
+ s->method, s->http_major, s->http_minor, s->flags, s->content_length);
+ } else if (s->type == HTTP_RESPONSE) {
+ llparse__print(p, endp,
+ "headers complete status=%d v=%d/%d flags=%x content_length=%llu",
+ s->status_code, s->http_major, s->http_minor, s->flags,
+ s->content_length);
+ } else {
+ llparse__print(p, endp, "invalid headers complete");
+ }
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_HEADERS_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #elif defined(LLHTTP__TEST_SKIP_BODY)
+ llparse__print(p, endp, "skip body");
+ return 1;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_body(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("body", p, endp);
+}
+
+
+int llhttp__on_chunk_header(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "chunk header len=%d", (int) s->content_length);
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_HEADER
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_chunk_extension_name(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("chunk_extension_name", p, endp);
+}
+
+
+int llhttp__on_chunk_extension_name_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "chunk_extension_name complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_EXTENSION_NAME
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_chunk_extension_value(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ return llparse__print_span("chunk_extension_value", p, endp);
+}
+
+
+int llhttp__on_chunk_extension_value_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "chunk_extension_value complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_EXTENSION_VALUE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+
+int llhttp__on_chunk_complete(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "chunk complete");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_COMPLETE
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+int llhttp__on_reset(llparse_t* s, const char* p, const char* endp) {
+ if (llparse__in_bench)
+ return 0;
+
+ llparse__print(p, endp, "reset");
+
+ #ifdef LLHTTP__TEST_PAUSE_ON_RESET
+ return LLPARSE__ERROR_PAUSE;
+ #else
+ return 0;
+ #endif
+}
+
+#endif /* LLHTTP__TEST_HTTP */
diff --git a/llhttp/test/fixtures/index.ts b/llhttp/test/fixtures/index.ts
new file mode 100644
index 0000000..1571f9d
--- /dev/null
+++ b/llhttp/test/fixtures/index.ts
@@ -0,0 +1,116 @@
+import * as fs from 'fs';
+import { ICompilerResult, LLParse } from 'llparse';
+import { Dot } from 'llparse-dot';
+import {
+ Fixture, FixtureResult, IFixtureBuildOptions,
+} from 'llparse-test-fixture';
+import * as path from 'path';
+
+import * as llhttp from '../../src/llhttp';
+
+export { FixtureResult };
+
+export type TestType = 'request' | 'response' | 'request-finish' | 'response-finish' |
+ 'request-lenient-all' | 'response-lenient-all' |
+ 'request-lenient-headers' | 'response-lenient-headers' |
+ 'request-lenient-chunked-length' | 'request-lenient-transfer-encoding' |
+ 'request-lenient-keep-alive' | 'response-lenient-keep-alive' |
+ 'request-lenient-version' | 'response-lenient-version' |
+ 'request-lenient-data-after-close' | 'response-lenient-data-after-close' |
+ 'request-lenient-optional-lf-after-cr' | 'response-lenient-optional-lf-after-cr' |
+ 'request-lenient-optional-cr-before-lf' | 'response-lenient-optional-cr-before-lf' |
+ 'request-lenient-optional-crlf-after-chunk' | 'response-lenient-optional-crlf-after-chunk' |
+ 'request-lenient-spaces-after-chunk-size' | 'response-lenient-spaces-after-chunk-size' |
+ 'none' | 'url';
+
+export const allowedTypes: TestType[] = [
+ 'request',
+ 'response',
+ 'request-finish',
+ 'response-finish',
+ 'request-lenient-all',
+ 'response-lenient-all',
+ 'request-lenient-headers',
+ 'response-lenient-headers',
+ 'request-lenient-keep-alive',
+ 'response-lenient-keep-alive',
+ 'request-lenient-chunked-length',
+ 'request-lenient-transfer-encoding',
+ 'request-lenient-version',
+ 'response-lenient-version',
+ 'request-lenient-data-after-close',
+ 'response-lenient-data-after-close',
+ 'request-lenient-optional-lf-after-cr',
+ 'response-lenient-optional-lf-after-cr',
+ 'request-lenient-optional-cr-before-lf',
+ 'response-lenient-optional-cr-before-lf',
+ 'request-lenient-optional-crlf-after-chunk',
+ 'response-lenient-optional-crlf-after-chunk',
+ 'request-lenient-spaces-after-chunk-size',
+ 'response-lenient-spaces-after-chunk-size',
+];
+
+const BUILD_DIR = path.join(__dirname, '..', 'tmp');
+const CHEADERS_FILE = path.join(BUILD_DIR, 'cheaders.h');
+
+const cheaders = new llhttp.CHeaders().build();
+try {
+ fs.mkdirSync(BUILD_DIR);
+} catch (e) {
+ // no-op
+}
+fs.writeFileSync(CHEADERS_FILE, cheaders);
+
+const fixtures = new Fixture({
+ buildDir: path.join(__dirname, '..', 'tmp'),
+ extra: [
+ '-msse4.2',
+ '-DLLHTTP__TEST',
+ '-DLLPARSE__ERROR_PAUSE=' + llhttp.constants.ERROR.PAUSED,
+ '-include', CHEADERS_FILE,
+ path.join(__dirname, 'extra.c'),
+ ],
+ maxParallel: process.env.LLPARSE_DEBUG ? 1 : undefined,
+});
+
+const cache: Map<any, ICompilerResult> = new Map();
+
+export async function build(
+ llparse: LLParse, node: any, outFile: string,
+ options: IFixtureBuildOptions = {},
+ ty: TestType = 'none'): Promise<FixtureResult> {
+ const dot = new Dot();
+ fs.writeFileSync(path.join(BUILD_DIR, outFile + '.dot'),
+ dot.build(node));
+
+ let artifacts: ICompilerResult;
+ if (cache.has(node)) {
+ artifacts = cache.get(node)!;
+ } else {
+ artifacts = llparse.build(node, {
+ c: { header: outFile },
+ debug: process.env.LLPARSE_DEBUG ? 'llparse__debug' : undefined,
+ });
+ cache.set(node, artifacts);
+ }
+
+ const extra = options.extra === undefined ? [] : options.extra.slice();
+
+ if (allowedTypes.includes(ty)) {
+ extra.push(
+ `-DLLPARSE__TEST_INIT=llhttp__test_init_${ty.replace(/-/g, '_')}`);
+ }
+
+ if (ty === 'request-finish' || ty === 'response-finish') {
+ if (ty === 'request-finish') {
+ extra.push('-DLLPARSE__TEST_INIT=llhttp__test_init_request');
+ } else {
+ extra.push('-DLLPARSE__TEST_INIT=llhttp__test_init_response');
+ }
+ extra.push('-DLLPARSE__TEST_FINISH=llhttp__test_finish');
+ }
+
+ return await fixtures.build(artifacts, outFile, Object.assign(options, {
+ extra,
+ }));
+}
diff --git a/llhttp/test/fuzzers/fuzz_parser.c b/llhttp/test/fuzzers/fuzz_parser.c
new file mode 100644
index 0000000..60d00ae
--- /dev/null
+++ b/llhttp/test/fuzzers/fuzz_parser.c
@@ -0,0 +1,45 @@
+#include "llhttp.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int handle_on_message_complete(llhttp_t *arg) { return 0; }
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ llhttp_t parser;
+ llhttp_settings_t settings;
+ llhttp_type_t http_type;
+
+ /* We need four bytes to determine variable parameters */
+ if (size < 4) {
+ return 0;
+ }
+
+ int headers = (data[0] & 0x01) == 1;
+ int chunked_length = (data[1] & 0x01) == 1;
+ int keep_alive = (data[2] & 0x01) == 1;
+ if (data[0] % 3 == 0) {
+ http_type = HTTP_BOTH;
+ } else if (data[0] % 3 == 1) {
+ http_type = HTTP_REQUEST;
+ } else {
+ http_type = HTTP_RESPONSE;
+ }
+ data += 4;
+ size -= 4;
+
+ /* Initialize user callbacks and settings */
+ llhttp_settings_init(&settings);
+
+ /* Set user callback */
+ settings.on_message_complete = handle_on_message_complete;
+
+ llhttp_init(&parser, http_type, &settings);
+ llhttp_set_lenient_headers(&parser, headers);
+ llhttp_set_lenient_chunked_length(&parser, chunked_length);
+ llhttp_set_lenient_keep_alive(&parser, keep_alive);
+
+ llhttp_execute(&parser, data, size);
+
+ return 0;
+}
diff --git a/llhttp/test/md-test.ts b/llhttp/test/md-test.ts
new file mode 100644
index 0000000..0c24e18
--- /dev/null
+++ b/llhttp/test/md-test.ts
@@ -0,0 +1,269 @@
+import * as assert from 'assert';
+import * as fs from 'fs';
+import { LLParse } from 'llparse';
+import { Group, MDGator, Metadata, Test } from 'mdgator';
+import * as path from 'path';
+import * as vm from 'vm';
+
+import * as llhttp from '../src/llhttp';
+import {IHTTPResult} from '../src/llhttp/http';
+import {IURLResult} from '../src/llhttp/url';
+import { allowedTypes, build, FixtureResult, TestType } from './fixtures';
+
+//
+// Cache nodes/llparse instances ahead of time
+// (different types of tests will re-use them)
+//
+
+interface INodeCacheEntry {
+ llparse: LLParse;
+ entry: IHTTPResult['entry'];
+}
+
+interface IUrlCacheEntry {
+ llparse: LLParse;
+ entry: IURLResult['entry']['normal'];
+}
+
+const modeCache = new Map<string, FixtureResult>();
+
+function buildNode() {
+ const p = new LLParse();
+ const instance = new llhttp.HTTP(p);
+
+ return { llparse: p, entry: instance.build().entry };
+}
+
+function buildURL() {
+ const p = new LLParse();
+ const instance = new llhttp.URL(p, true);
+
+ const node = instance.build();
+
+ // Loop
+ node.exit.toHTTP.otherwise(node.entry.normal);
+ node.exit.toHTTP09.otherwise(node.entry.normal);
+
+ return { llparse: p, entry: node.entry.normal };
+}
+
+//
+// Build binaries using cached nodes/llparse
+//
+
+async function buildMode(ty: TestType, meta: any)
+ : Promise<FixtureResult> {
+
+ const cacheKey = `${ty}:${JSON.stringify(meta || {})}`;
+ let entry = modeCache.get(cacheKey);
+
+ if (entry) {
+ return entry;
+ }
+
+ let node;
+ let prefix: string;
+ let extra: string[];
+ if (ty === 'url') {
+ node = buildURL();
+ prefix = 'url';
+ extra = [];
+ } else {
+ node = buildNode();
+ prefix = 'http';
+ extra = [
+ '-DLLHTTP__TEST_HTTP',
+ path.join(__dirname, '..', 'src', 'native', 'http.c'),
+ ];
+ }
+
+ if (meta.pause) {
+ extra.push(`-DLLHTTP__TEST_PAUSE_${meta.pause.toUpperCase()}=1`);
+ }
+
+ if (meta.skipBody) {
+ extra.push('-DLLHTTP__TEST_SKIP_BODY=1');
+ }
+
+ entry = await build(node.llparse, node.entry, `${prefix}-${ty}`, {
+ extra,
+ }, ty);
+
+ modeCache.set(cacheKey, entry);
+ return entry;
+}
+
+interface IFixtureMap {
+ [key: string]: { [key: string]: Promise<FixtureResult> };
+}
+
+//
+// Run test suite
+//
+
+function run(name: string): void {
+ const md = new MDGator();
+
+ const raw = fs.readFileSync(path.join(__dirname, name + '.md')).toString();
+ const groups = md.parse(raw);
+
+ function runSingleTest(ty: TestType, meta: any,
+ input: string,
+ expected: ReadonlyArray<string | RegExp>): void {
+ it(`should pass for type="${ty}"`, async () => {
+ const binary = await buildMode(ty, meta);
+ await binary.check(input, expected, {
+ noScan: meta.noScan === true,
+ });
+ });
+ }
+
+ function runTest(test: Test) {
+ describe(test.name + ` at ${name}.md:${test.line + 1}`, () => {
+ let types: TestType[] = [];
+
+ const isURL = test.values.has('url');
+ const inputKey = isURL ? 'url' : 'http';
+
+ assert(test.values.has(inputKey),
+ `Missing "${inputKey}" code in md file`);
+ assert.strictEqual(test.values.get(inputKey)!.length, 1,
+ `Expected just one "${inputKey}" input`);
+
+ let meta: Metadata;
+ if (test.meta.has(inputKey)) {
+ meta = test.meta.get(inputKey)![0]!;
+ } else {
+ assert(isURL, 'Missing required http metadata');
+ meta = {};
+ }
+
+ if (isURL) {
+ types = [ 'url' ];
+ } else {
+ assert(meta.hasOwnProperty('type'), 'Missing required `type` metadata');
+
+ if (meta.type) {
+ if (!allowedTypes.includes(meta.type)) {
+ throw new Error(`Invalid value of \`type\` metadata: "${meta.type}"`);
+ }
+
+ types.push(meta.type);
+ }
+ }
+
+ assert(test.values.has('log'), 'Missing `log` code in md file');
+
+ assert.strictEqual(test.values.get('log')!.length, 1,
+ 'Expected just one output');
+
+ let input: string = test.values.get(inputKey)![0];
+ let expected: string = test.values.get('log')![0];
+
+ // Remove trailing newline
+ input = input.replace(/\n$/, '');
+
+ // Remove escaped newlines
+ input = input.replace(/\\(\r\n|\r|\n)/g, '');
+
+ // Normalize all newlines
+ input = input.replace(/\r\n|\r|\n/g, '\r\n');
+
+ // Replace escaped CRLF, tabs, form-feed
+ input = input.replace(/\\r/g, '\r');
+ input = input.replace(/\\n/g, '\n');
+ input = input.replace(/\\t/g, '\t');
+ input = input.replace(/\\f/g, '\f');
+ input = input.replace(/\\x([0-9a-fA-F]+)/g, (all, hex) => {
+ return String.fromCharCode(parseInt(hex, 16));
+ });
+
+ // Useful in token tests
+ input = input.replace(/\\([0-7]{1,3})/g, (_, digits) => {
+ return String.fromCharCode(parseInt(digits, 8));
+ });
+
+ // Evaluate inline JavaScript
+ input = input.replace(/\$\{(.+?)\}/g, (_, code) => {
+ return vm.runInNewContext(code) + '';
+ });
+
+ // Escape first symbol `\r` or `\n`, `|`, `&` for Windows
+ if (process.platform === 'win32') {
+ const firstByte = Buffer.from(input)[0];
+ if (firstByte === 0x0a || firstByte === 0x0d) {
+ input = '\\' + input;
+ }
+
+ input = input.replace(/\|/g, '^|');
+ input = input.replace(/&/g, '^&');
+ }
+
+ // Replace escaped tabs/form-feed in expected too
+ expected = expected.replace(/\\t/g, '\t');
+ expected = expected.replace(/\\f/g, '\f');
+
+ // Split
+ const expectedLines = expected.split(/\n/g).slice(0, -1);
+
+ const fullExpected = expectedLines.map((line) => {
+ if (line.startsWith('/')) {
+ return new RegExp(line.trim().slice(1, -1));
+ } else {
+ return line;
+ }
+ });
+
+ for (const ty of types) {
+ if (meta.skip === true || (process.env.ONLY === 'true' && !meta.only)) {
+ continue;
+ }
+
+ runSingleTest(ty, meta, input, fullExpected);
+ }
+ });
+ }
+
+ function runGroup(group: Group) {
+ describe(group.name + ` at ${name}.md:${group.line + 1}`, function() {
+ this.timeout(60000);
+
+ for (const child of group.children) {
+ runGroup(child);
+ }
+
+ for (const test of group.tests) {
+ runTest(test);
+ }
+ });
+ }
+
+ for (const group of groups) {
+ runGroup(group);
+ }
+}
+
+run('request/sample');
+run('request/lenient-headers');
+run('request/lenient-version');
+run('request/method');
+run('request/uri');
+run('request/connection');
+run('request/content-length');
+run('request/transfer-encoding');
+run('request/invalid');
+run('request/finish');
+run('request/pausing');
+run('request/pipelining');
+
+run('response/sample');
+run('response/connection');
+run('response/content-length');
+run('response/transfer-encoding');
+run('response/invalid');
+run('response/finish');
+run('response/lenient-version');
+run('response/pausing');
+run('response/pipelining');
+
+run('url');
diff --git a/llhttp/test/request/connection.md b/llhttp/test/request/connection.md
new file mode 100644
index 0000000..a03242e
--- /dev/null
+++ b/llhttp/test/request/connection.md
@@ -0,0 +1,732 @@
+Connection header
+=================
+
+## `keep-alive`
+
+### Setting flag
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: keep-alive
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=10 span[header_value]="keep-alive"
+off=43 header_value complete
+off=45 headers complete method=4 v=1/1 flags=1 content_length=0
+off=45 message complete
+```
+
+### Restarting when keep-alive is explicitly
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: keep-alive
+
+PUT /url HTTP/1.1
+Connection: keep-alive
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=10 span[header_value]="keep-alive"
+off=43 header_value complete
+off=45 headers complete method=4 v=1/1 flags=1 content_length=0
+off=45 message complete
+off=45 reset
+off=45 message begin
+off=45 len=3 span[method]="PUT"
+off=48 method complete
+off=49 len=4 span[url]="/url"
+off=54 url complete
+off=59 len=3 span[version]="1.1"
+off=62 version complete
+off=64 len=10 span[header_field]="Connection"
+off=75 header_field complete
+off=76 len=10 span[header_value]="keep-alive"
+off=88 header_value complete
+off=90 headers complete method=4 v=1/1 flags=1 content_length=0
+off=90 message complete
+```
+
+### No restart when keep-alive is off (1.0)
+
+<!-- meta={"type": "request" } -->
+```http
+PUT /url HTTP/1.0
+
+PUT /url HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.0"
+off=17 version complete
+off=21 headers complete method=4 v=1/0 flags=0 content_length=0
+off=21 message complete
+off=22 error code=5 reason="Data after `Connection: close`"
+```
+
+### Resetting flags when keep-alive is off (1.0, lenient)
+
+Even though we allow restarts in loose mode, the flags should be still set to
+`0` upon restart.
+
+<!-- meta={"type": "request-lenient-keep-alive"} -->
+```http
+PUT /url HTTP/1.0
+Content-Length: 0
+
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.0"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="0"
+off=38 header_value complete
+off=40 headers complete method=4 v=1/0 flags=20 content_length=0
+off=40 message complete
+off=40 reset
+off=40 message begin
+off=40 len=3 span[method]="PUT"
+off=43 method complete
+off=44 len=4 span[url]="/url"
+off=49 url complete
+off=54 len=3 span[version]="1.1"
+off=57 version complete
+off=59 len=17 span[header_field]="Transfer-Encoding"
+off=77 header_field complete
+off=78 len=7 span[header_value]="chunked"
+off=87 header_value complete
+off=89 headers complete method=4 v=1/1 flags=208 content_length=0
+```
+
+### CRLF between requests, implicit `keep-alive`
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Host: www.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 4
+
+q=42
+
+GET / HTTP/1.1
+```
+_Note the trailing CRLF above_
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=15 span[header_value]="www.example.com"
+off=40 header_value complete
+off=40 len=12 span[header_field]="Content-Type"
+off=53 header_field complete
+off=54 len=33 span[header_value]="application/x-www-form-urlencoded"
+off=89 header_value complete
+off=89 len=14 span[header_field]="Content-Length"
+off=104 header_field complete
+off=105 len=1 span[header_value]="4"
+off=108 header_value complete
+off=110 headers complete method=3 v=1/1 flags=20 content_length=4
+off=110 len=4 span[body]="q=42"
+off=114 message complete
+off=118 reset
+off=118 message begin
+off=118 len=3 span[method]="GET"
+off=121 method complete
+off=122 len=1 span[url]="/"
+off=124 url complete
+off=129 len=3 span[version]="1.1"
+off=132 version complete
+```
+
+### Not treating `\r` as `-`
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: keep\ralive
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=4 span[header_value]="keep"
+off=36 error code=3 reason="Missing expected LF after header value"
+```
+
+## `close`
+
+### Setting flag on `close`
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: close
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=5 span[header_value]="close"
+off=38 header_value complete
+off=40 headers complete method=4 v=1/1 flags=2 content_length=0
+off=40 message complete
+```
+
+### CRLF between requests, explicit `close`
+
+`close` means closed connection
+
+<!-- meta={"type": "request" } -->
+```http
+POST / HTTP/1.1
+Host: www.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 4
+Connection: close
+
+q=42
+
+GET / HTTP/1.1
+```
+_Note the trailing CRLF above_
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=15 span[header_value]="www.example.com"
+off=40 header_value complete
+off=40 len=12 span[header_field]="Content-Type"
+off=53 header_field complete
+off=54 len=33 span[header_value]="application/x-www-form-urlencoded"
+off=89 header_value complete
+off=89 len=14 span[header_field]="Content-Length"
+off=104 header_field complete
+off=105 len=1 span[header_value]="4"
+off=108 header_value complete
+off=108 len=10 span[header_field]="Connection"
+off=119 header_field complete
+off=120 len=5 span[header_value]="close"
+off=127 header_value complete
+off=129 headers complete method=3 v=1/1 flags=22 content_length=4
+off=129 len=4 span[body]="q=42"
+off=133 message complete
+off=138 error code=5 reason="Data after `Connection: close`"
+```
+
+### CRLF between requests, explicit `close` (lenient)
+
+Loose mode is more lenient, and allows further requests.
+
+<!-- meta={"type": "request-lenient-keep-alive"} -->
+```http
+POST / HTTP/1.1
+Host: www.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 4
+Connection: close
+
+q=42
+
+GET / HTTP/1.1
+```
+_Note the trailing CRLF above_
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=15 span[header_value]="www.example.com"
+off=40 header_value complete
+off=40 len=12 span[header_field]="Content-Type"
+off=53 header_field complete
+off=54 len=33 span[header_value]="application/x-www-form-urlencoded"
+off=89 header_value complete
+off=89 len=14 span[header_field]="Content-Length"
+off=104 header_field complete
+off=105 len=1 span[header_value]="4"
+off=108 header_value complete
+off=108 len=10 span[header_field]="Connection"
+off=119 header_field complete
+off=120 len=5 span[header_value]="close"
+off=127 header_value complete
+off=129 headers complete method=3 v=1/1 flags=22 content_length=4
+off=129 len=4 span[body]="q=42"
+off=133 message complete
+off=137 reset
+off=137 message begin
+off=137 len=3 span[method]="GET"
+off=140 method complete
+off=141 len=1 span[url]="/"
+off=143 url complete
+off=148 len=3 span[version]="1.1"
+off=151 version complete
+```
+
+## Parsing multiple tokens
+
+### Sample
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: close, token, upgrade, token, keep-alive
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=40 span[header_value]="close, token, upgrade, token, keep-alive"
+off=73 header_value complete
+off=75 headers complete method=4 v=1/1 flags=7 content_length=0
+off=75 message complete
+```
+
+### Multiple tokens with folding
+
+<!-- meta={"type": "request"} -->
+```http
+GET /demo HTTP/1.1
+Host: example.com
+Connection: Something,
+ Upgrade, ,Keep-Alive
+Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
+Sec-WebSocket-Protocol: sample
+Upgrade: WebSocket
+Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
+Origin: http://example.com
+
+Hot diggity dogg
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/demo"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=4 span[header_field]="Host"
+off=25 header_field complete
+off=26 len=11 span[header_value]="example.com"
+off=39 header_value complete
+off=39 len=10 span[header_field]="Connection"
+off=50 header_field complete
+off=51 len=10 span[header_value]="Something,"
+off=63 len=21 span[header_value]=" Upgrade, ,Keep-Alive"
+off=86 header_value complete
+off=86 len=18 span[header_field]="Sec-WebSocket-Key2"
+off=105 header_field complete
+off=106 len=18 span[header_value]="12998 5 Y3 1 .P00"
+off=126 header_value complete
+off=126 len=22 span[header_field]="Sec-WebSocket-Protocol"
+off=149 header_field complete
+off=150 len=6 span[header_value]="sample"
+off=158 header_value complete
+off=158 len=7 span[header_field]="Upgrade"
+off=166 header_field complete
+off=167 len=9 span[header_value]="WebSocket"
+off=178 header_value complete
+off=178 len=18 span[header_field]="Sec-WebSocket-Key1"
+off=197 header_field complete
+off=198 len=20 span[header_value]="4 @1 46546xW%0l 1 5"
+off=220 header_value complete
+off=220 len=6 span[header_field]="Origin"
+off=227 header_field complete
+off=228 len=18 span[header_value]="http://example.com"
+off=248 header_value complete
+off=250 headers complete method=1 v=1/1 flags=15 content_length=0
+off=250 message complete
+off=250 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Multiple tokens with folding and LWS
+
+<!-- meta={"type": "request"} -->
+```http
+GET /demo HTTP/1.1
+Connection: keep-alive, upgrade
+Upgrade: WebSocket
+
+Hot diggity dogg
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/demo"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=10 span[header_field]="Connection"
+off=31 header_field complete
+off=32 len=19 span[header_value]="keep-alive, upgrade"
+off=53 header_value complete
+off=53 len=7 span[header_field]="Upgrade"
+off=61 header_field complete
+off=62 len=9 span[header_value]="WebSocket"
+off=73 header_value complete
+off=75 headers complete method=1 v=1/1 flags=15 content_length=0
+off=75 message complete
+off=75 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Multiple tokens with folding, LWS, and CRLF
+
+<!-- meta={"type": "request"} -->
+```http
+GET /demo HTTP/1.1
+Connection: keep-alive, \r\n upgrade
+Upgrade: WebSocket
+
+Hot diggity dogg
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/demo"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=10 span[header_field]="Connection"
+off=31 header_field complete
+off=32 len=12 span[header_value]="keep-alive, "
+off=46 len=8 span[header_value]=" upgrade"
+off=56 header_value complete
+off=56 len=7 span[header_field]="Upgrade"
+off=64 header_field complete
+off=65 len=9 span[header_value]="WebSocket"
+off=76 header_value complete
+off=78 headers complete method=1 v=1/1 flags=15 content_length=0
+off=78 message complete
+off=78 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Invalid whitespace token with `Connection` header field
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection : upgrade
+Content-Length: 4
+Upgrade: ws
+
+abcdefgh
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 error code=10 reason="Invalid header field char"
+```
+
+### Invalid whitespace token with `Connection` header field (lenient)
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+PUT /url HTTP/1.1
+Connection : upgrade
+Content-Length: 4
+Upgrade: ws
+
+abcdefgh
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=11 span[header_field]="Connection "
+off=31 header_field complete
+off=32 len=7 span[header_value]="upgrade"
+off=41 header_value complete
+off=41 len=14 span[header_field]="Content-Length"
+off=56 header_field complete
+off=57 len=1 span[header_value]="4"
+off=60 header_value complete
+off=60 len=7 span[header_field]="Upgrade"
+off=68 header_field complete
+off=69 len=2 span[header_value]="ws"
+off=73 header_value complete
+off=75 headers complete method=4 v=1/1 flags=34 content_length=4
+off=75 len=4 span[body]="abcd"
+off=79 message complete
+off=79 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+## `upgrade`
+
+### Setting a flag and pausing
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: upgrade
+Upgrade: ws
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=7 span[header_value]="upgrade"
+off=40 header_value complete
+off=40 len=7 span[header_field]="Upgrade"
+off=48 header_field complete
+off=49 len=2 span[header_value]="ws"
+off=53 header_value complete
+off=55 headers complete method=4 v=1/1 flags=14 content_length=0
+off=55 message complete
+off=55 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Emitting part of body and pausing
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: upgrade
+Content-Length: 4
+Upgrade: ws
+
+abcdefgh
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=7 span[header_value]="upgrade"
+off=40 header_value complete
+off=40 len=14 span[header_field]="Content-Length"
+off=55 header_field complete
+off=56 len=1 span[header_value]="4"
+off=59 header_value complete
+off=59 len=7 span[header_field]="Upgrade"
+off=67 header_field complete
+off=68 len=2 span[header_value]="ws"
+off=72 header_value complete
+off=74 headers complete method=4 v=1/1 flags=34 content_length=4
+off=74 len=4 span[body]="abcd"
+off=78 message complete
+off=78 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Upgrade GET request
+
+<!-- meta={"type": "request"} -->
+```http
+GET /demo HTTP/1.1
+Host: example.com
+Connection: Upgrade
+Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
+Sec-WebSocket-Protocol: sample
+Upgrade: WebSocket
+Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
+Origin: http://example.com
+
+Hot diggity dogg
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/demo"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=4 span[header_field]="Host"
+off=25 header_field complete
+off=26 len=11 span[header_value]="example.com"
+off=39 header_value complete
+off=39 len=10 span[header_field]="Connection"
+off=50 header_field complete
+off=51 len=7 span[header_value]="Upgrade"
+off=60 header_value complete
+off=60 len=18 span[header_field]="Sec-WebSocket-Key2"
+off=79 header_field complete
+off=80 len=18 span[header_value]="12998 5 Y3 1 .P00"
+off=100 header_value complete
+off=100 len=22 span[header_field]="Sec-WebSocket-Protocol"
+off=123 header_field complete
+off=124 len=6 span[header_value]="sample"
+off=132 header_value complete
+off=132 len=7 span[header_field]="Upgrade"
+off=140 header_field complete
+off=141 len=9 span[header_value]="WebSocket"
+off=152 header_value complete
+off=152 len=18 span[header_field]="Sec-WebSocket-Key1"
+off=171 header_field complete
+off=172 len=20 span[header_value]="4 @1 46546xW%0l 1 5"
+off=194 header_value complete
+off=194 len=6 span[header_field]="Origin"
+off=201 header_field complete
+off=202 len=18 span[header_value]="http://example.com"
+off=222 header_value complete
+off=224 headers complete method=1 v=1/1 flags=14 content_length=0
+off=224 message complete
+off=224 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### Upgrade POST request
+
+<!-- meta={"type": "request"} -->
+```http
+POST /demo HTTP/1.1
+Host: example.com
+Connection: Upgrade
+Upgrade: HTTP/2.0
+Content-Length: 15
+
+sweet post body\
+Hot diggity dogg
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=5 span[url]="/demo"
+off=11 url complete
+off=16 len=3 span[version]="1.1"
+off=19 version complete
+off=21 len=4 span[header_field]="Host"
+off=26 header_field complete
+off=27 len=11 span[header_value]="example.com"
+off=40 header_value complete
+off=40 len=10 span[header_field]="Connection"
+off=51 header_field complete
+off=52 len=7 span[header_value]="Upgrade"
+off=61 header_value complete
+off=61 len=7 span[header_field]="Upgrade"
+off=69 header_field complete
+off=70 len=8 span[header_value]="HTTP/2.0"
+off=80 header_value complete
+off=80 len=14 span[header_field]="Content-Length"
+off=95 header_field complete
+off=96 len=2 span[header_value]="15"
+off=100 header_value complete
+off=102 headers complete method=3 v=1/1 flags=34 content_length=15
+off=102 len=15 span[body]="sweet post body"
+off=117 message complete
+off=117 error code=22 reason="Pause on CONNECT/Upgrade"
+```
diff --git a/llhttp/test/request/content-length.md b/llhttp/test/request/content-length.md
new file mode 100644
index 0000000..524d183
--- /dev/null
+++ b/llhttp/test/request/content-length.md
@@ -0,0 +1,482 @@
+Content-Length header
+=====================
+
+## `Content-Length` with zeroes
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 003
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=3 span[header_value]="003"
+off=40 header_value complete
+off=42 headers complete method=4 v=1/1 flags=20 content_length=3
+off=42 len=3 span[body]="abc"
+off=45 message complete
+```
+
+## `Content-Length` with follow-up headers
+
+The way the parser works is that special headers (like `Content-Length`) first
+set `header_state` to appropriate value, and then apply custom parsing using
+that value. For `Content-Length`, in particular, the `header_state` is used for
+setting the flag too.
+
+Make sure that `header_state` is reset to `0`, so that the flag won't be
+attempted to set twice (and error).
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 003
+Ohai: world
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=3 span[header_value]="003"
+off=40 header_value complete
+off=40 len=4 span[header_field]="Ohai"
+off=45 header_field complete
+off=46 len=5 span[header_value]="world"
+off=53 header_value complete
+off=55 headers complete method=4 v=1/1 flags=20 content_length=3
+off=55 len=3 span[body]="abc"
+off=58 message complete
+```
+
+## Error on `Content-Length` overflow
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 1000000000000000000000
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=21 span[header_value]="100000000000000000000"
+off=56 error code=11 reason="Content-Length overflow"
+```
+
+## Error on duplicate `Content-Length`
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 1
+Content-Length: 2
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="1"
+off=38 header_value complete
+off=38 len=14 span[header_field]="Content-Length"
+off=53 header_field complete
+off=54 error code=4 reason="Duplicate Content-Length"
+```
+
+## Error on simultaneous `Content-Length` and `Transfer-Encoding: identity`
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 1
+Transfer-Encoding: identity
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="1"
+off=38 header_value complete
+off=38 len=17 span[header_field]="Transfer-Encoding"
+off=56 header_field complete
+off=56 error code=15 reason="Transfer-Encoding can't be present with Content-Length"
+```
+
+## Invalid whitespace token with `Content-Length` header field
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Connection: upgrade
+Content-Length : 4
+Upgrade: ws
+
+abcdefgh
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=7 span[header_value]="upgrade"
+off=40 header_value complete
+off=40 len=14 span[header_field]="Content-Length"
+off=55 error code=10 reason="Invalid header field char"
+```
+
+## Invalid whitespace token with `Content-Length` header field (lenient)
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+PUT /url HTTP/1.1
+Connection: upgrade
+Content-Length : 4
+Upgrade: ws
+
+abcdefgh
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=10 span[header_field]="Connection"
+off=30 header_field complete
+off=31 len=7 span[header_value]="upgrade"
+off=40 header_value complete
+off=40 len=15 span[header_field]="Content-Length "
+off=56 header_field complete
+off=57 len=1 span[header_value]="4"
+off=60 header_value complete
+off=60 len=7 span[header_field]="Upgrade"
+off=68 header_field complete
+off=69 len=2 span[header_value]="ws"
+off=73 header_value complete
+off=75 headers complete method=4 v=1/1 flags=34 content_length=4
+off=75 len=4 span[body]="abcd"
+off=79 message complete
+off=79 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+## No error on simultaneous `Content-Length` and `Transfer-Encoding: identity` (lenient)
+
+<!-- meta={"type": "request-lenient-chunked-length"} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 1
+Transfer-Encoding: identity
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="1"
+off=38 header_value complete
+off=38 len=17 span[header_field]="Transfer-Encoding"
+off=56 header_field complete
+off=57 len=8 span[header_value]="identity"
+off=67 header_value complete
+off=69 headers complete method=4 v=1/1 flags=220 content_length=1
+```
+
+## Funky `Content-Length` with body
+
+<!-- meta={"type": "request"} -->
+```http
+GET /get_funky_content_length_body_hello HTTP/1.0
+conTENT-Length: 5
+
+HELLO
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=36 span[url]="/get_funky_content_length_body_hello"
+off=41 url complete
+off=46 len=3 span[version]="1.0"
+off=49 version complete
+off=51 len=14 span[header_field]="conTENT-Length"
+off=66 header_field complete
+off=67 len=1 span[header_value]="5"
+off=70 header_value complete
+off=72 headers complete method=1 v=1/0 flags=20 content_length=5
+off=72 len=5 span[body]="HELLO"
+off=77 message complete
+```
+
+## Spaces in `Content-Length` (surrounding)
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 42
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=34 len=3 span[header_value]="42 "
+off=39 header_value complete
+off=41 headers complete method=3 v=1/1 flags=20 content_length=42
+```
+
+### Spaces in `Content-Length` #2
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 4 2
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=2 span[header_value]="4 "
+off=35 error code=11 reason="Invalid character in Content-Length"
+```
+
+### Spaces in `Content-Length` #3
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 13 37
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=3 span[header_value]="13 "
+off=36 error code=11 reason="Invalid character in Content-Length"
+```
+
+### Empty `Content-Length`
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Content-Length:
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=34 error code=11 reason="Empty Content-Length"
+```
+
+## `Content-Length` with CR instead of dash
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+PUT /url HTTP/1.1
+Content\rLength: 003
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=26 error code=10 reason="Invalid header token"
+```
+
+## Content-Length reset when no body is received
+
+<!-- meta={"type": "request", "skipBody": true} -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 123
+
+POST /url HTTP/1.1
+Content-Length: 456
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=3 span[header_value]="123"
+off=40 header_value complete
+off=42 headers complete method=4 v=1/1 flags=20 content_length=123
+off=42 skip body
+off=42 message complete
+off=42 reset
+off=42 message begin
+off=42 len=4 span[method]="POST"
+off=46 method complete
+off=47 len=4 span[url]="/url"
+off=52 url complete
+off=57 len=3 span[version]="1.1"
+off=60 version complete
+off=62 len=14 span[header_field]="Content-Length"
+off=77 header_field complete
+off=78 len=3 span[header_value]="456"
+off=83 header_value complete
+off=85 headers complete method=3 v=1/1 flags=20 content_length=456
+off=85 skip body
+off=85 message complete
+```
+
+## Missing CRLF-CRLF before body
+
+<!-- meta={"type": "request" } -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 3
+\rabc
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="3"
+off=38 header_value complete
+off=39 error code=2 reason="Expected LF after headers"
+```
+
+## Missing CRLF-CRLF before body (lenient)
+
+<!-- meta={"type": "request-lenient-optional-lf-after-cr" } -->
+```http
+PUT /url HTTP/1.1
+Content-Length: 3
+\rabc
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=14 span[header_field]="Content-Length"
+off=34 header_field complete
+off=35 len=1 span[header_value]="3"
+off=38 header_value complete
+off=39 headers complete method=4 v=1/1 flags=20 content_length=3
+off=39 len=3 span[body]="abc"
+off=42 message complete
+``` \ No newline at end of file
diff --git a/llhttp/test/request/finish.md b/llhttp/test/request/finish.md
new file mode 100644
index 0000000..710daa5
--- /dev/null
+++ b/llhttp/test/request/finish.md
@@ -0,0 +1,69 @@
+Finish
+======
+
+Those tests check the return codes and the behavior of `llhttp_finish()` C API.
+
+## It should be safe to finish after GET request
+
+<!-- meta={"type": "request-finish"} -->
+```http
+GET / HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=18 headers complete method=1 v=1/1 flags=0 content_length=0
+off=18 message complete
+off=NULL finish=0
+```
+
+## It should be unsafe to finish after incomplete PUT request
+
+<!-- meta={"type": "request-finish"} -->
+```http
+PUT / HTTP/1.1
+Content-Length: 100
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=14 span[header_field]="Content-Length"
+off=31 header_field complete
+off=32 len=3 span[header_value]="100"
+off=NULL finish=2
+```
+
+## It should be unsafe to finish inside of the header
+
+<!-- meta={"type": "request-finish"} -->
+```http
+PUT / HTTP/1.1
+Content-Leng
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=12 span[header_field]="Content-Leng"
+off=NULL finish=2
+```
diff --git a/llhttp/test/request/invalid.md b/llhttp/test/request/invalid.md
new file mode 100644
index 0000000..9fb8383
--- /dev/null
+++ b/llhttp/test/request/invalid.md
@@ -0,0 +1,607 @@
+Invalid requests
+================
+
+### ICE protocol and GET method
+
+<!-- meta={"type": "request"} -->
+```http
+GET /music/sweet/music ICE/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=18 span[url]="/music/sweet/music"
+off=23 url complete
+off=27 error code=8 reason="Expected SOURCE method for ICE/x.x request"
+```
+
+### ICE protocol, but not really
+
+<!-- meta={"type": "request"} -->
+```http
+GET /music/sweet/music IHTTP/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=18 span[url]="/music/sweet/music"
+off=23 url complete
+off=24 error code=8 reason="Expected HTTP/"
+```
+
+### RTSP protocol and PUT method
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /music/sweet/music RTSP/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=18 span[url]="/music/sweet/music"
+off=23 url complete
+off=28 error code=8 reason="Invalid method for RTSP/x.x request"
+```
+
+### HTTP protocol and ANNOUNCE method
+
+<!-- meta={"type": "request"} -->
+```http
+ANNOUNCE /music/sweet/music HTTP/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=8 span[method]="ANNOUNCE"
+off=8 method complete
+off=9 len=18 span[url]="/music/sweet/music"
+off=28 url complete
+off=33 error code=8 reason="Invalid method for HTTP/x.x request"
+```
+
+### Headers separated by CR
+
+<!-- meta={"type": "request"} -->
+```http
+GET / HTTP/1.1
+Foo: 1\rBar: 2
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=3 span[header_field]="Foo"
+off=20 header_field complete
+off=21 len=1 span[header_value]="1"
+off=23 error code=3 reason="Missing expected LF after header value"
+```
+
+### Headers separated by LF
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Host: localhost:5000
+x:x\nTransfer-Encoding: chunked
+
+1
+A
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=14 span[header_value]="localhost:5000"
+off=39 header_value complete
+off=39 len=1 span[header_field]="x"
+off=41 header_field complete
+off=41 len=1 span[header_value]="x"
+off=42 error code=25 reason="Missing expected CR after header value"
+```
+
+### Headers separated by dummy characters
+
+<!-- meta={"type": "request"} -->
+```http
+GET / HTTP/1.1
+Connection: close
+Host: a
+\rZGET /evil: HTTP/1.1
+Host: a
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=10 span[header_field]="Connection"
+off=27 header_field complete
+off=28 len=5 span[header_value]="close"
+off=35 header_value complete
+off=35 len=4 span[header_field]="Host"
+off=40 header_field complete
+off=41 len=1 span[header_value]="a"
+off=44 header_value complete
+off=45 error code=2 reason="Expected LF after headers"
+```
+
+
+### Headers separated by dummy characters (lenient)
+
+<!-- meta={"type": "request-lenient-optional-lf-after-cr"} -->
+```http
+GET / HTTP/1.1
+Connection: close
+Host: a
+\rZGET /evil: HTTP/1.1
+Host: a
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=10 span[header_field]="Connection"
+off=27 header_field complete
+off=28 len=5 span[header_value]="close"
+off=35 header_value complete
+off=35 len=4 span[header_field]="Host"
+off=40 header_field complete
+off=41 len=1 span[header_value]="a"
+off=44 header_value complete
+off=45 headers complete method=1 v=1/1 flags=2 content_length=0
+off=45 message complete
+off=46 error code=5 reason="Data after `Connection: close`"
+```
+
+### Empty headers separated by CR
+
+<!-- meta={"type": "request" } -->
+```http
+POST / HTTP/1.1
+Connection: Close
+Host: localhost:5000
+x:\rTransfer-Encoding: chunked
+
+1
+A
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=5 span[header_value]="Close"
+off=36 header_value complete
+off=36 len=4 span[header_field]="Host"
+off=41 header_field complete
+off=42 len=14 span[header_value]="localhost:5000"
+off=58 header_value complete
+off=58 len=1 span[header_field]="x"
+off=60 header_field complete
+off=61 error code=2 reason="Expected LF after CR"
+```
+
+### Empty headers separated by LF
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Host: localhost:5000
+x:\nTransfer-Encoding: chunked
+
+1
+A
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=14 span[header_value]="localhost:5000"
+off=39 header_value complete
+off=39 len=1 span[header_field]="x"
+off=41 header_field complete
+off=42 error code=10 reason="Invalid header value char"
+```
+
+### Invalid header token #1
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+Fo@: Failure
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=18 error code=10 reason="Invalid header token"
+```
+
+### Invalid header token #2
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+Foo\01\test: Bar
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=19 error code=10 reason="Invalid header token"
+```
+
+### Invalid header token #3
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+: Bar
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 error code=10 reason="Invalid header token"
+```
+
+### Invalid method
+
+<!-- meta={"type": "request"} -->
+```http
+MKCOLA / HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=5 span[method]="MKCOL"
+off=5 method complete
+off=5 error code=6 reason="Expected space after method"
+```
+
+### Illegal header field name line folding
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+name
+ : value
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=20 error code=10 reason="Invalid header token"
+```
+
+### Corrupted Connection header
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+Host: www.example.com
+Connection\r\033\065\325eep-Alive
+Accept-Encoding: gzip
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=15 span[header_value]="www.example.com"
+off=39 header_value complete
+off=49 error code=10 reason="Invalid header token"
+```
+
+### Corrupted header name
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+Host: www.example.com
+X-Some-Header\r\033\065\325eep-Alive
+Accept-Encoding: gzip
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=15 span[header_value]="www.example.com"
+off=39 header_value complete
+off=52 error code=10 reason="Invalid header token"
+```
+
+### Missing CR between headers
+
+<!-- meta={"type": "request", "noScan": true} -->
+
+```http
+GET / HTTP/1.1
+Host: localhost
+Dummy: x\nContent-Length: 23
+
+GET / HTTP/1.1
+Dummy: GET /admin HTTP/1.1
+Host: localhost
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=9 span[header_value]="localhost"
+off=33 header_value complete
+off=33 len=5 span[header_field]="Dummy"
+off=39 header_field complete
+off=40 len=1 span[header_value]="x"
+off=41 error code=25 reason="Missing expected CR after header value"
+```
+
+### Invalid HTTP version
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/5.6
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="5.6"
+off=14 error code=9 reason="Invalid HTTP version"
+```
+
+## Invalid space after start line
+
+<!-- meta={"type": "request"} -->
+```http
+GET / HTTP/1.1
+ Host: foo
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=17 error code=30 reason="Unexpected space after start line"
+```
+
+
+### Only LFs present
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1\n\
+Transfer-Encoding: chunked\n\
+Trailer: Baz
+Foo: abc\n\
+Bar: def\n\
+\n\
+1\n\
+A\n\
+1;abc\n\
+B\n\
+1;def=ghi\n\
+C\n\
+1;jkl="mno"\n\
+D\n\
+0\n\
+\n\
+Baz: ghi\n\
+\n\
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=16 error code=9 reason="Expected CRLF after version"
+```
+
+### Only LFs present (lenient)
+
+<!-- meta={"type": "request-lenient-all"} -->
+```http
+POST / HTTP/1.1\n\
+Transfer-Encoding: chunked\n\
+Trailer: Baz
+Foo: abc\n\
+Bar: def\n\
+\n\
+1\n\
+A\n\
+1;abc\n\
+B\n\
+1;def=ghi\n\
+C\n\
+1;jkl="mno"\n\
+D\n\
+0\n\
+\n\
+Baz: ghi\n\
+\n
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=16 len=17 span[header_field]="Transfer-Encoding"
+off=34 header_field complete
+off=35 len=7 span[header_value]="chunked"
+off=43 header_value complete
+off=43 len=7 span[header_field]="Trailer"
+off=51 header_field complete
+off=52 len=3 span[header_value]="Baz"
+off=57 header_value complete
+off=57 len=3 span[header_field]="Foo"
+off=61 header_field complete
+off=62 len=3 span[header_value]="abc"
+off=66 header_value complete
+off=66 len=3 span[header_field]="Bar"
+off=70 header_field complete
+off=71 len=3 span[header_value]="def"
+off=75 header_value complete
+off=76 headers complete method=3 v=1/1 flags=208 content_length=0
+off=78 chunk header len=1
+off=78 len=1 span[body]="A"
+off=80 chunk complete
+off=82 len=3 span[chunk_extension_name]="abc"
+off=85 chunk_extension_name complete
+off=86 chunk header len=1
+off=86 len=1 span[body]="B"
+off=88 chunk complete
+off=90 len=3 span[chunk_extension_name]="def"
+off=94 chunk_extension_name complete
+off=94 len=3 span[chunk_extension_value]="ghi"
+off=97 chunk_extension_value complete
+off=98 chunk header len=1
+off=98 len=1 span[body]="C"
+off=100 chunk complete
+off=102 len=3 span[chunk_extension_name]="jkl"
+off=106 chunk_extension_name complete
+off=106 len=5 span[chunk_extension_value]=""mno""
+off=111 chunk_extension_value complete
+off=112 chunk header len=1
+off=112 len=1 span[body]="D"
+off=114 chunk complete
+off=117 chunk header len=0
+off=117 len=3 span[header_field]="Baz"
+off=121 header_field complete
+off=122 len=3 span[header_value]="ghi"
+off=126 header_value complete
+off=127 chunk complete
+off=127 message complete
+``` \ No newline at end of file
diff --git a/llhttp/test/request/lenient-headers.md b/llhttp/test/request/lenient-headers.md
new file mode 100644
index 0000000..05e105f
--- /dev/null
+++ b/llhttp/test/request/lenient-headers.md
@@ -0,0 +1,145 @@
+Lenient header value parsing
+============================
+
+Parsing with header value token checks off.
+
+## Header value (lenient)
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+GET /url HTTP/1.1
+Header1: \f
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=7 span[header_field]="Header1"
+off=27 header_field complete
+off=28 len=1 span[header_value]="\f"
+off=31 header_value complete
+off=33 headers complete method=1 v=1/1 flags=0 content_length=0
+off=33 message complete
+```
+
+## Second request header value (lenient)
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+GET /url HTTP/1.1
+Header1: Okay
+
+
+GET /url HTTP/1.1
+Header1: \f
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=7 span[header_field]="Header1"
+off=27 header_field complete
+off=28 len=4 span[header_value]="Okay"
+off=34 header_value complete
+off=36 headers complete method=1 v=1/1 flags=0 content_length=0
+off=36 message complete
+off=38 reset
+off=38 message begin
+off=38 len=3 span[method]="GET"
+off=41 method complete
+off=42 len=4 span[url]="/url"
+off=47 url complete
+off=52 len=3 span[version]="1.1"
+off=55 version complete
+off=57 len=7 span[header_field]="Header1"
+off=65 header_field complete
+off=66 len=1 span[header_value]="\f"
+off=69 header_value complete
+off=71 headers complete method=1 v=1/1 flags=0 content_length=0
+off=71 message complete
+```
+
+## Header value
+
+<!-- meta={"type": "request"} -->
+```http
+GET /url HTTP/1.1
+Header1: \f
+
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=7 span[header_field]="Header1"
+off=27 header_field complete
+off=28 len=0 span[header_value]=""
+off=28 error code=10 reason="Invalid header value char"
+```
+
+### Empty headers separated by CR (lenient)
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+POST / HTTP/1.1
+Connection: Close
+Host: localhost:5000
+x:\rTransfer-Encoding: chunked
+
+1
+A
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=5 span[header_value]="Close"
+off=36 header_value complete
+off=36 len=4 span[header_field]="Host"
+off=41 header_field complete
+off=42 len=14 span[header_value]="localhost:5000"
+off=58 header_value complete
+off=58 len=1 span[header_field]="x"
+off=60 header_field complete
+off=61 len=0 span[header_value]=""
+off=61 header_value complete
+off=61 len=17 span[header_field]="Transfer-Encoding"
+off=79 header_field complete
+off=80 len=7 span[header_value]="chunked"
+off=89 header_value complete
+off=91 headers complete method=3 v=1/1 flags=20a content_length=0
+off=94 chunk header len=1
+off=94 len=1 span[body]="A"
+off=97 chunk complete
+off=100 chunk header len=0
+``` \ No newline at end of file
diff --git a/llhttp/test/request/lenient-version.md b/llhttp/test/request/lenient-version.md
new file mode 100644
index 0000000..4185556
--- /dev/null
+++ b/llhttp/test/request/lenient-version.md
@@ -0,0 +1,23 @@
+Lenient HTTP version parsing
+============================
+
+### Invalid HTTP version (lenient)
+
+<!-- meta={"type": "request-lenient-version"} -->
+```http
+GET / HTTP/5.6
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="5.6"
+off=14 version complete
+off=18 headers complete method=1 v=5/6 flags=0 content_length=0
+off=18 message complete
+``` \ No newline at end of file
diff --git a/llhttp/test/request/method.md b/llhttp/test/request/method.md
new file mode 100644
index 0000000..dce262e
--- /dev/null
+++ b/llhttp/test/request/method.md
@@ -0,0 +1,450 @@
+Methods
+=======
+
+### REPORT request
+
+<!-- meta={"type": "request"} -->
+```http
+REPORT /test HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=6 span[method]="REPORT"
+off=6 method complete
+off=7 len=5 span[url]="/test"
+off=13 url complete
+off=18 len=3 span[version]="1.1"
+off=21 version complete
+off=25 headers complete method=20 v=1/1 flags=0 content_length=0
+off=25 message complete
+```
+
+### CONNECT request
+
+<!-- meta={"type": "request"} -->
+```http
+CONNECT 0-home0.netscape.com:443 HTTP/1.0
+User-agent: Mozilla/1.1N
+Proxy-authorization: basic aGVsbG86d29ybGQ=
+
+some data
+and yet even more data
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="CONNECT"
+off=7 method complete
+off=8 len=24 span[url]="0-home0.netscape.com:443"
+off=33 url complete
+off=38 len=3 span[version]="1.0"
+off=41 version complete
+off=43 len=10 span[header_field]="User-agent"
+off=54 header_field complete
+off=55 len=12 span[header_value]="Mozilla/1.1N"
+off=69 header_value complete
+off=69 len=19 span[header_field]="Proxy-authorization"
+off=89 header_field complete
+off=90 len=22 span[header_value]="basic aGVsbG86d29ybGQ="
+off=114 header_value complete
+off=116 headers complete method=5 v=1/0 flags=0 content_length=0
+off=116 message complete
+off=116 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### CONNECT request with CAPS
+
+<!-- meta={"type": "request"} -->
+```http
+CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0
+User-agent: Mozilla/1.1N
+Proxy-authorization: basic aGVsbG86d29ybGQ=
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="CONNECT"
+off=7 method complete
+off=8 len=22 span[url]="HOME0.NETSCAPE.COM:443"
+off=31 url complete
+off=36 len=3 span[version]="1.0"
+off=39 version complete
+off=41 len=10 span[header_field]="User-agent"
+off=52 header_field complete
+off=53 len=12 span[header_value]="Mozilla/1.1N"
+off=67 header_value complete
+off=67 len=19 span[header_field]="Proxy-authorization"
+off=87 header_field complete
+off=88 len=22 span[header_value]="basic aGVsbG86d29ybGQ="
+off=112 header_value complete
+off=114 headers complete method=5 v=1/0 flags=0 content_length=0
+off=114 message complete
+off=114 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### CONNECT with body
+
+<!-- meta={"type": "request"} -->
+```http
+CONNECT foo.bar.com:443 HTTP/1.0
+User-agent: Mozilla/1.1N
+Proxy-authorization: basic aGVsbG86d29ybGQ=
+Content-Length: 10
+
+blarfcicle"
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="CONNECT"
+off=7 method complete
+off=8 len=15 span[url]="foo.bar.com:443"
+off=24 url complete
+off=29 len=3 span[version]="1.0"
+off=32 version complete
+off=34 len=10 span[header_field]="User-agent"
+off=45 header_field complete
+off=46 len=12 span[header_value]="Mozilla/1.1N"
+off=60 header_value complete
+off=60 len=19 span[header_field]="Proxy-authorization"
+off=80 header_field complete
+off=81 len=22 span[header_value]="basic aGVsbG86d29ybGQ="
+off=105 header_value complete
+off=105 len=14 span[header_field]="Content-Length"
+off=120 header_field complete
+off=121 len=2 span[header_value]="10"
+off=125 header_value complete
+off=127 headers complete method=5 v=1/0 flags=20 content_length=10
+off=127 message complete
+off=127 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+### M-SEARCH request
+
+<!-- meta={"type": "request"} -->
+```http
+M-SEARCH * HTTP/1.1
+HOST: 239.255.255.250:1900
+MAN: "ssdp:discover"
+ST: "ssdp:all"
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=8 span[method]="M-SEARCH"
+off=8 method complete
+off=9 len=1 span[url]="*"
+off=11 url complete
+off=16 len=3 span[version]="1.1"
+off=19 version complete
+off=21 len=4 span[header_field]="HOST"
+off=26 header_field complete
+off=27 len=20 span[header_value]="239.255.255.250:1900"
+off=49 header_value complete
+off=49 len=3 span[header_field]="MAN"
+off=53 header_field complete
+off=54 len=15 span[header_value]=""ssdp:discover""
+off=71 header_value complete
+off=71 len=2 span[header_field]="ST"
+off=74 header_field complete
+off=75 len=10 span[header_value]=""ssdp:all""
+off=87 header_value complete
+off=89 headers complete method=24 v=1/1 flags=0 content_length=0
+off=89 message complete
+```
+
+### PATCH request
+
+<!-- meta={"type": "request"} -->
+```http
+PATCH /file.txt HTTP/1.1
+Host: www.example.com
+Content-Type: application/example
+If-Match: "e0023aa4e"
+Content-Length: 10
+
+cccccccccc
+```
+
+```log
+off=0 message begin
+off=0 len=5 span[method]="PATCH"
+off=5 method complete
+off=6 len=9 span[url]="/file.txt"
+off=16 url complete
+off=21 len=3 span[version]="1.1"
+off=24 version complete
+off=26 len=4 span[header_field]="Host"
+off=31 header_field complete
+off=32 len=15 span[header_value]="www.example.com"
+off=49 header_value complete
+off=49 len=12 span[header_field]="Content-Type"
+off=62 header_field complete
+off=63 len=19 span[header_value]="application/example"
+off=84 header_value complete
+off=84 len=8 span[header_field]="If-Match"
+off=93 header_field complete
+off=94 len=11 span[header_value]=""e0023aa4e""
+off=107 header_value complete
+off=107 len=14 span[header_field]="Content-Length"
+off=122 header_field complete
+off=123 len=2 span[header_value]="10"
+off=127 header_value complete
+off=129 headers complete method=28 v=1/1 flags=20 content_length=10
+off=129 len=10 span[body]="cccccccccc"
+off=139 message complete
+```
+
+### PURGE request
+
+<!-- meta={"type": "request"} -->
+```http
+PURGE /file.txt HTTP/1.1
+Host: www.example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=5 span[method]="PURGE"
+off=5 method complete
+off=6 len=9 span[url]="/file.txt"
+off=16 url complete
+off=21 len=3 span[version]="1.1"
+off=24 version complete
+off=26 len=4 span[header_field]="Host"
+off=31 header_field complete
+off=32 len=15 span[header_value]="www.example.com"
+off=49 header_value complete
+off=51 headers complete method=29 v=1/1 flags=0 content_length=0
+off=51 message complete
+```
+
+### SEARCH request
+
+<!-- meta={"type": "request"} -->
+```http
+SEARCH / HTTP/1.1
+Host: www.example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=6 span[method]="SEARCH"
+off=6 method complete
+off=7 len=1 span[url]="/"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=4 span[header_field]="Host"
+off=24 header_field complete
+off=25 len=15 span[header_value]="www.example.com"
+off=42 header_value complete
+off=44 headers complete method=14 v=1/1 flags=0 content_length=0
+off=44 message complete
+```
+
+### LINK request
+
+<!-- meta={"type": "request"} -->
+```http
+LINK /images/my_dog.jpg HTTP/1.1
+Host: example.com
+Link: <http://example.com/profiles/joe>; rel="tag"
+Link: <http://example.com/profiles/sally>; rel="tag"
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="LINK"
+off=4 method complete
+off=5 len=18 span[url]="/images/my_dog.jpg"
+off=24 url complete
+off=29 len=3 span[version]="1.1"
+off=32 version complete
+off=34 len=4 span[header_field]="Host"
+off=39 header_field complete
+off=40 len=11 span[header_value]="example.com"
+off=53 header_value complete
+off=53 len=4 span[header_field]="Link"
+off=58 header_field complete
+off=59 len=44 span[header_value]="<http://example.com/profiles/joe>; rel="tag""
+off=105 header_value complete
+off=105 len=4 span[header_field]="Link"
+off=110 header_field complete
+off=111 len=46 span[header_value]="<http://example.com/profiles/sally>; rel="tag""
+off=159 header_value complete
+off=161 headers complete method=31 v=1/1 flags=0 content_length=0
+off=161 message complete
+```
+
+### LINK request
+
+<!-- meta={"type": "request"} -->
+```http
+UNLINK /images/my_dog.jpg HTTP/1.1
+Host: example.com
+Link: <http://example.com/profiles/sally>; rel="tag"
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=6 span[method]="UNLINK"
+off=6 method complete
+off=7 len=18 span[url]="/images/my_dog.jpg"
+off=26 url complete
+off=31 len=3 span[version]="1.1"
+off=34 version complete
+off=36 len=4 span[header_field]="Host"
+off=41 header_field complete
+off=42 len=11 span[header_value]="example.com"
+off=55 header_value complete
+off=55 len=4 span[header_field]="Link"
+off=60 header_field complete
+off=61 len=46 span[header_value]="<http://example.com/profiles/sally>; rel="tag""
+off=109 header_value complete
+off=111 headers complete method=32 v=1/1 flags=0 content_length=0
+off=111 message complete
+```
+
+### SOURCE request
+
+<!-- meta={"type": "request"} -->
+```http
+SOURCE /music/sweet/music HTTP/1.1
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=6 span[method]="SOURCE"
+off=6 method complete
+off=7 len=18 span[url]="/music/sweet/music"
+off=26 url complete
+off=31 len=3 span[version]="1.1"
+off=34 version complete
+off=36 len=4 span[header_field]="Host"
+off=41 header_field complete
+off=42 len=11 span[header_value]="example.com"
+off=55 header_value complete
+off=57 headers complete method=33 v=1/1 flags=0 content_length=0
+off=57 message complete
+```
+
+### SOURCE request with ICE
+
+<!-- meta={"type": "request"} -->
+```http
+SOURCE /music/sweet/music ICE/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=6 span[method]="SOURCE"
+off=6 method complete
+off=7 len=18 span[url]="/music/sweet/music"
+off=26 url complete
+off=30 len=3 span[version]="1.0"
+off=33 version complete
+off=35 len=4 span[header_field]="Host"
+off=40 header_field complete
+off=41 len=11 span[header_value]="example.com"
+off=54 header_value complete
+off=56 headers complete method=33 v=1/0 flags=0 content_length=0
+off=56 message complete
+```
+
+### OPTIONS request with RTSP
+
+NOTE: `OPTIONS` is a valid HTTP metho too.
+
+<!-- meta={"type": "request"} -->
+```http
+OPTIONS /music/sweet/music RTSP/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="OPTIONS"
+off=7 method complete
+off=8 len=18 span[url]="/music/sweet/music"
+off=27 url complete
+off=32 len=3 span[version]="1.0"
+off=35 version complete
+off=37 len=4 span[header_field]="Host"
+off=42 header_field complete
+off=43 len=11 span[header_value]="example.com"
+off=56 header_value complete
+off=58 headers complete method=6 v=1/0 flags=0 content_length=0
+off=58 message complete
+```
+
+### ANNOUNCE request with RTSP
+
+<!-- meta={"type": "request"} -->
+```http
+ANNOUNCE /music/sweet/music RTSP/1.0
+Host: example.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=8 span[method]="ANNOUNCE"
+off=8 method complete
+off=9 len=18 span[url]="/music/sweet/music"
+off=28 url complete
+off=33 len=3 span[version]="1.0"
+off=36 version complete
+off=38 len=4 span[header_field]="Host"
+off=43 header_field complete
+off=44 len=11 span[header_value]="example.com"
+off=57 header_value complete
+off=59 headers complete method=36 v=1/0 flags=0 content_length=0
+off=59 message complete
+```
+
+### PRI request HTTP2
+
+<!-- meta={"type": "request"} -->
+```http
+PRI * HTTP/1.1
+
+SM
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PRI"
+off=3 method complete
+off=4 len=1 span[url]="*"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=24 error code=23 reason="Pause on PRI/Upgrade"
+```
diff --git a/llhttp/test/request/pausing.md b/llhttp/test/request/pausing.md
new file mode 100644
index 0000000..8e501e3
--- /dev/null
+++ b/llhttp/test/request/pausing.md
@@ -0,0 +1,381 @@
+Pausing
+=======
+
+### on_message_begin
+
+<!-- meta={"type": "request", "pause": "on_message_begin"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 pause
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_message_complete
+
+<!-- meta={"type": "request", "pause": "on_message_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+off=41 pause
+```
+
+### on_method_complete
+
+<!-- meta={"type": "request", "pause": "on_method_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=4 pause
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_url_complete
+
+<!-- meta={"type": "request", "pause": "on_url_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=7 pause
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_version_complete
+
+<!-- meta={"type": "request", "pause": "on_version_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=15 pause
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_header_field_complete
+
+<!-- meta={"type": "request", "pause": "on_header_field_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=32 pause
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_header_value_complete
+
+<!-- meta={"type": "request", "pause": "on_header_value_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=36 pause
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_headers_complete
+
+<!-- meta={"type": "request", "pause": "on_headers_complete"} -->
+```http
+POST / HTTP/1.1
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete method=3 v=1/1 flags=20 content_length=3
+off=38 pause
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_chunk_header
+
+<!-- meta={"type": "request", "pause": "on_chunk_header"} -->
+```http
+PUT / HTTP/1.1
+Transfer-Encoding: chunked
+
+a
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=17 span[header_field]="Transfer-Encoding"
+off=34 header_field complete
+off=35 len=7 span[header_value]="chunked"
+off=44 header_value complete
+off=46 headers complete method=4 v=1/1 flags=208 content_length=0
+off=49 chunk header len=10
+off=49 pause
+off=49 len=10 span[body]="0123456789"
+off=61 chunk complete
+off=64 chunk header len=0
+off=64 pause
+off=66 chunk complete
+off=66 message complete
+```
+
+### on_chunk_extension_name
+
+<!-- meta={"type": "request", "pause": "on_chunk_extension_name"} -->
+```http
+PUT / HTTP/1.1
+Transfer-Encoding: chunked
+
+a;foo=bar
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=17 span[header_field]="Transfer-Encoding"
+off=34 header_field complete
+off=35 len=7 span[header_value]="chunked"
+off=44 header_value complete
+off=46 headers complete method=4 v=1/1 flags=208 content_length=0
+off=48 len=3 span[chunk_extension_name]="foo"
+off=52 chunk_extension_name complete
+off=52 pause
+off=52 len=3 span[chunk_extension_value]="bar"
+off=56 chunk_extension_value complete
+off=57 chunk header len=10
+off=57 len=10 span[body]="0123456789"
+off=69 chunk complete
+off=72 chunk header len=0
+off=74 chunk complete
+off=74 message complete
+```
+
+### on_chunk_extension_value
+
+<!-- meta={"type": "request", "pause": "on_chunk_extension_value"} -->
+```http
+PUT / HTTP/1.1
+Transfer-Encoding: chunked
+
+a;foo=bar
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=17 span[header_field]="Transfer-Encoding"
+off=34 header_field complete
+off=35 len=7 span[header_value]="chunked"
+off=44 header_value complete
+off=46 headers complete method=4 v=1/1 flags=208 content_length=0
+off=48 len=3 span[chunk_extension_name]="foo"
+off=52 chunk_extension_name complete
+off=52 len=3 span[chunk_extension_value]="bar"
+off=56 chunk_extension_value complete
+off=56 pause
+off=57 chunk header len=10
+off=57 len=10 span[body]="0123456789"
+off=69 chunk complete
+off=72 chunk header len=0
+off=74 chunk complete
+off=74 message complete
+```
+
+
+### on_chunk_complete
+
+<!-- meta={"type": "request", "pause": "on_chunk_complete"} -->
+```http
+PUT / HTTP/1.1
+Transfer-Encoding: chunked
+
+a
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=17 span[header_field]="Transfer-Encoding"
+off=34 header_field complete
+off=35 len=7 span[header_value]="chunked"
+off=44 header_value complete
+off=46 headers complete method=4 v=1/1 flags=208 content_length=0
+off=49 chunk header len=10
+off=49 len=10 span[body]="0123456789"
+off=61 chunk complete
+off=61 pause
+off=64 chunk header len=0
+off=66 chunk complete
+off=66 pause
+off=66 message complete
+```
diff --git a/llhttp/test/request/pipelining.md b/llhttp/test/request/pipelining.md
new file mode 100644
index 0000000..bdfe6ab
--- /dev/null
+++ b/llhttp/test/request/pipelining.md
@@ -0,0 +1,66 @@
+Pipelining
+==========
+
+## Should parse multiple events
+
+<!-- meta={"type": "request"} -->
+```http
+POST /aaa HTTP/1.1
+Content-Length: 3
+
+AAA
+PUT /bbb HTTP/1.1
+Content-Length: 4
+
+BBBB
+PATCH /ccc HTTP/1.1
+Content-Length: 5
+
+CCCC
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=4 span[url]="/aaa"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=14 span[header_field]="Content-Length"
+off=35 header_field complete
+off=36 len=1 span[header_value]="3"
+off=39 header_value complete
+off=41 headers complete method=3 v=1/1 flags=20 content_length=3
+off=41 len=3 span[body]="AAA"
+off=44 message complete
+off=46 reset
+off=46 message begin
+off=46 len=3 span[method]="PUT"
+off=49 method complete
+off=50 len=4 span[url]="/bbb"
+off=55 url complete
+off=60 len=3 span[version]="1.1"
+off=63 version complete
+off=65 len=14 span[header_field]="Content-Length"
+off=80 header_field complete
+off=81 len=1 span[header_value]="4"
+off=84 header_value complete
+off=86 headers complete method=4 v=1/1 flags=20 content_length=4
+off=86 len=4 span[body]="BBBB"
+off=90 message complete
+off=92 reset
+off=92 message begin
+off=92 len=5 span[method]="PATCH"
+off=97 method complete
+off=98 len=4 span[url]="/ccc"
+off=103 url complete
+off=108 len=3 span[version]="1.1"
+off=111 version complete
+off=113 len=14 span[header_field]="Content-Length"
+off=128 header_field complete
+off=129 len=1 span[header_value]="5"
+off=132 header_value complete
+off=134 headers complete method=28 v=1/1 flags=20 content_length=5
+off=134 len=4 span[body]="CCCC"
+``` \ No newline at end of file
diff --git a/llhttp/test/request/sample.md b/llhttp/test/request/sample.md
new file mode 100644
index 0000000..f0a5d44
--- /dev/null
+++ b/llhttp/test/request/sample.md
@@ -0,0 +1,629 @@
+Sample requests
+===============
+
+Lots of sample requests, most ported from [http_parser][0] test suite.
+
+## Simple request
+
+<!-- meta={"type": "request"} -->
+```http
+OPTIONS /url HTTP/1.1
+Header1: Value1
+Header2:\t Value2
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="OPTIONS"
+off=7 method complete
+off=8 len=4 span[url]="/url"
+off=13 url complete
+off=18 len=3 span[version]="1.1"
+off=21 version complete
+off=23 len=7 span[header_field]="Header1"
+off=31 header_field complete
+off=32 len=6 span[header_value]="Value1"
+off=40 header_value complete
+off=40 len=7 span[header_field]="Header2"
+off=48 header_field complete
+off=50 len=6 span[header_value]="Value2"
+off=58 header_value complete
+off=60 headers complete method=6 v=1/1 flags=0 content_length=0
+off=60 message complete
+```
+
+## Request with method starting with `H`
+
+There's a optimization in `start_req_or_res` that passes execution to
+`start_req` when the first character is not `H` (because response must start
+with `HTTP/`). However, there're still methods like `HEAD` that should get
+to `start_req`. Verify that it still works after optimization.
+
+<!-- meta={"type": "request", "noScan": true } -->
+```http
+HEAD /url HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="HEAD"
+off=4 method complete
+off=5 len=4 span[url]="/url"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=22 headers complete method=2 v=1/1 flags=0 content_length=0
+off=22 message complete
+```
+
+## curl GET
+
+<!-- meta={"type": "request"} -->
+```http
+GET /test HTTP/1.1
+User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1
+Host: 0.0.0.0=5000
+Accept: */*
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/test"
+off=10 url complete
+off=15 len=3 span[version]="1.1"
+off=18 version complete
+off=20 len=10 span[header_field]="User-Agent"
+off=31 header_field complete
+off=32 len=85 span[header_value]="curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1"
+off=119 header_value complete
+off=119 len=4 span[header_field]="Host"
+off=124 header_field complete
+off=125 len=12 span[header_value]="0.0.0.0=5000"
+off=139 header_value complete
+off=139 len=6 span[header_field]="Accept"
+off=146 header_field complete
+off=147 len=3 span[header_value]="*/*"
+off=152 header_value complete
+off=154 headers complete method=1 v=1/1 flags=0 content_length=0
+off=154 message complete
+```
+
+## Firefox GET
+
+<!-- meta={"type": "request"} -->
+```http
+GET /favicon.ico HTTP/1.1
+Host: 0.0.0.0=5000
+User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 300
+Connection: keep-alive
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=12 span[url]="/favicon.ico"
+off=17 url complete
+off=22 len=3 span[version]="1.1"
+off=25 version complete
+off=27 len=4 span[header_field]="Host"
+off=32 header_field complete
+off=33 len=12 span[header_value]="0.0.0.0=5000"
+off=47 header_value complete
+off=47 len=10 span[header_field]="User-Agent"
+off=58 header_field complete
+off=59 len=76 span[header_value]="Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"
+off=137 header_value complete
+off=137 len=6 span[header_field]="Accept"
+off=144 header_field complete
+off=145 len=63 span[header_value]="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
+off=210 header_value complete
+off=210 len=15 span[header_field]="Accept-Language"
+off=226 header_field complete
+off=227 len=14 span[header_value]="en-us,en;q=0.5"
+off=243 header_value complete
+off=243 len=15 span[header_field]="Accept-Encoding"
+off=259 header_field complete
+off=260 len=12 span[header_value]="gzip,deflate"
+off=274 header_value complete
+off=274 len=14 span[header_field]="Accept-Charset"
+off=289 header_field complete
+off=290 len=30 span[header_value]="ISO-8859-1,utf-8;q=0.7,*;q=0.7"
+off=322 header_value complete
+off=322 len=10 span[header_field]="Keep-Alive"
+off=333 header_field complete
+off=334 len=3 span[header_value]="300"
+off=339 header_value complete
+off=339 len=10 span[header_field]="Connection"
+off=350 header_field complete
+off=351 len=10 span[header_value]="keep-alive"
+off=363 header_value complete
+off=365 headers complete method=1 v=1/1 flags=1 content_length=0
+off=365 message complete
+```
+
+## DUMBPACK
+
+<!-- meta={"type": "request"} -->
+```http
+GET /dumbpack HTTP/1.1
+aaaaaaaaaaaaa:++++++++++
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=9 span[url]="/dumbpack"
+off=14 url complete
+off=19 len=3 span[version]="1.1"
+off=22 version complete
+off=24 len=13 span[header_field]="aaaaaaaaaaaaa"
+off=38 header_field complete
+off=38 len=10 span[header_value]="++++++++++"
+off=50 header_value complete
+off=52 headers complete method=1 v=1/1 flags=0 content_length=0
+off=52 message complete
+```
+
+## No headers and no body
+
+<!-- meta={"type": "request"} -->
+```http
+GET /get_no_headers_no_body/world HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=29 span[url]="/get_no_headers_no_body/world"
+off=34 url complete
+off=39 len=3 span[version]="1.1"
+off=42 version complete
+off=46 headers complete method=1 v=1/1 flags=0 content_length=0
+off=46 message complete
+```
+
+## One header and no body
+
+<!-- meta={"type": "request"} -->
+```http
+GET /get_one_header_no_body HTTP/1.1
+Accept: */*
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=23 span[url]="/get_one_header_no_body"
+off=28 url complete
+off=33 len=3 span[version]="1.1"
+off=36 version complete
+off=38 len=6 span[header_field]="Accept"
+off=45 header_field complete
+off=46 len=3 span[header_value]="*/*"
+off=51 header_value complete
+off=53 headers complete method=1 v=1/1 flags=0 content_length=0
+off=53 message complete
+```
+
+## Apache bench GET
+
+The server receiving this request SHOULD NOT wait for EOF to know that
+`Content-Length == 0`.
+
+<!-- meta={"type": "request"} -->
+```http
+GET /test HTTP/1.0
+Host: 0.0.0.0:5000
+User-Agent: ApacheBench/2.3
+Accept: */*
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=5 span[url]="/test"
+off=10 url complete
+off=15 len=3 span[version]="1.0"
+off=18 version complete
+off=20 len=4 span[header_field]="Host"
+off=25 header_field complete
+off=26 len=12 span[header_value]="0.0.0.0:5000"
+off=40 header_value complete
+off=40 len=10 span[header_field]="User-Agent"
+off=51 header_field complete
+off=52 len=15 span[header_value]="ApacheBench/2.3"
+off=69 header_value complete
+off=69 len=6 span[header_field]="Accept"
+off=76 header_field complete
+off=77 len=3 span[header_value]="*/*"
+off=82 header_value complete
+off=84 headers complete method=1 v=1/0 flags=0 content_length=0
+off=84 message complete
+```
+
+## Prefix newline
+
+Some clients, especially after a POST in a keep-alive connection,
+will send an extra CRLF before the next request.
+
+<!-- meta={"type": "request"} -->
+```http
+\r\nGET /test HTTP/1.1
+
+
+```
+
+```log
+off=2 message begin
+off=2 len=3 span[method]="GET"
+off=5 method complete
+off=6 len=5 span[url]="/test"
+off=12 url complete
+off=17 len=3 span[version]="1.1"
+off=20 version complete
+off=24 headers complete method=1 v=1/1 flags=0 content_length=0
+off=24 message complete
+```
+
+## No HTTP version
+
+<!-- meta={"type": "request"} -->
+```http
+GET /
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=7 url complete
+off=9 headers complete method=1 v=0/9 flags=0 content_length=0
+off=9 message complete
+```
+
+## Line folding in header value with CRLF
+
+<!-- meta={"type": "request-lenient-headers"} -->
+```http
+GET / HTTP/1.1
+Line1: abc
+\tdef
+ ghi
+\t\tjkl
+ mno
+\t \tqrs
+Line2: \t line2\t
+Line3:
+ line3
+Line4:
+
+Connection:
+ close
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=5 span[header_field]="Line1"
+off=22 header_field complete
+off=25 len=3 span[header_value]="abc"
+off=30 len=4 span[header_value]="\tdef"
+off=36 len=4 span[header_value]=" ghi"
+off=42 len=5 span[header_value]="\t\tjkl"
+off=49 len=6 span[header_value]=" mno "
+off=57 len=6 span[header_value]="\t \tqrs"
+off=65 header_value complete
+off=65 len=5 span[header_field]="Line2"
+off=71 header_field complete
+off=74 len=6 span[header_value]="line2\t"
+off=82 header_value complete
+off=82 len=5 span[header_field]="Line3"
+off=88 header_field complete
+off=91 len=5 span[header_value]="line3"
+off=98 header_value complete
+off=98 len=5 span[header_field]="Line4"
+off=104 header_field complete
+off=110 len=0 span[header_value]=""
+off=110 header_value complete
+off=110 len=10 span[header_field]="Connection"
+off=121 header_field complete
+off=124 len=5 span[header_value]="close"
+off=131 header_value complete
+off=133 headers complete method=1 v=1/1 flags=2 content_length=0
+off=133 message complete
+```
+
+## Line folding in header value with LF
+
+<!-- meta={"type": "request"} -->
+
+```http
+GET / HTTP/1.1
+Line1: abc\n\
+\tdef\n\
+ ghi\n\
+\t\tjkl\n\
+ mno \n\
+\t \tqrs\n\
+Line2: \t line2\t\n\
+Line3:\n\
+ line3\n\
+Line4: \n\
+ \n\
+Connection:\n\
+ close\n\
+\n
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=5 span[header_field]="Line1"
+off=22 header_field complete
+off=25 len=3 span[header_value]="abc"
+off=28 error code=25 reason="Missing expected CR after header value"
+```
+
+## No LF after CR
+
+<!-- meta={"type":"request"} -->
+
+```http
+GET / HTTP/1.1\rLine: 1
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=15 error code=2 reason="Expected CRLF after version"
+```
+
+## No LF after CR (lenient)
+
+<!-- meta={"type":"request-lenient-optional-lf-after-cr"} -->
+
+```http
+GET / HTTP/1.1\rLine: 1
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=15 len=4 span[header_field]="Line"
+off=20 header_field complete
+off=21 len=1 span[header_value]="1"
+```
+
+## Request starting with CRLF
+
+<!-- meta={"type": "request"} -->
+```http
+\r\nGET /url HTTP/1.1
+Header1: Value1
+
+
+```
+
+```log
+off=2 message begin
+off=2 len=3 span[method]="GET"
+off=5 method complete
+off=6 len=4 span[url]="/url"
+off=11 url complete
+off=16 len=3 span[version]="1.1"
+off=19 version complete
+off=21 len=7 span[header_field]="Header1"
+off=29 header_field complete
+off=30 len=6 span[header_value]="Value1"
+off=38 header_value complete
+off=40 headers complete method=1 v=1/1 flags=0 content_length=0
+off=40 message complete
+```
+
+## Extended Characters
+
+See nodejs/test/parallel/test-http-headers-obstext.js
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET / HTTP/1.1
+Test: DĆ¼sseldorf
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Test"
+off=21 header_field complete
+off=22 len=11 span[header_value]="DĆ¼sseldorf"
+off=35 header_value complete
+off=37 headers complete method=1 v=1/1 flags=0 content_length=0
+off=37 message complete
+```
+
+## 255 ASCII in header value
+
+Note: `Buffer.from([ 0xff ]).toString('latin1') === 'Ćæ'`.
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+OPTIONS /url HTTP/1.1
+Header1: Value1
+Header2: \xffValue2
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="OPTIONS"
+off=7 method complete
+off=8 len=4 span[url]="/url"
+off=13 url complete
+off=18 len=3 span[version]="1.1"
+off=21 version complete
+off=23 len=7 span[header_field]="Header1"
+off=31 header_field complete
+off=32 len=6 span[header_value]="Value1"
+off=40 header_value complete
+off=40 len=7 span[header_field]="Header2"
+off=48 header_field complete
+off=49 len=8 span[header_value]="ĆæValue2"
+off=59 header_value complete
+off=61 headers complete method=6 v=1/1 flags=0 content_length=0
+off=61 message complete
+```
+
+## X-SSL-Nonsense
+
+See nodejs/test/parallel/test-http-headers-obstext.js
+
+<!-- meta={"type": "request"} -->
+```http
+GET / HTTP/1.1
+X-SSL-Nonsense: -----BEGIN CERTIFICATE-----
+\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx
+\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT
+\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu
+\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV
+\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV
+\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB
+\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF
+\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR
+\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL
+\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP
+\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR
+\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG
+\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs
+\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD
+\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj
+\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj
+\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG
+\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE
+\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO
+\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1
+\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0
+\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD
+\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv
+\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3
+\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8
+\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk
+\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK
+\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu
+\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3
+\tRA==
+\t-----END CERTIFICATE-----
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=14 span[header_field]="X-SSL-Nonsense"
+off=31 header_field complete
+off=34 len=27 span[header_value]="-----BEGIN CERTIFICATE-----"
+off=63 len=65 span[header_value]="\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx"
+off=130 len=65 span[header_value]="\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT"
+off=197 len=65 span[header_value]="\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu"
+off=264 len=65 span[header_value]="\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV"
+off=331 len=65 span[header_value]="\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV"
+off=398 len=65 span[header_value]="\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB"
+off=465 len=65 span[header_value]="\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF"
+off=532 len=65 span[header_value]="\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR"
+off=599 len=65 span[header_value]="\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL"
+off=666 len=65 span[header_value]="\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP"
+off=733 len=65 span[header_value]="\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR"
+off=800 len=65 span[header_value]="\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG"
+off=867 len=66 span[header_value]="\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs"
+off=935 len=65 span[header_value]="\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD"
+off=1002 len=65 span[header_value]="\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj"
+off=1069 len=65 span[header_value]="\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj"
+off=1136 len=65 span[header_value]="\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG"
+off=1203 len=65 span[header_value]="\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE"
+off=1270 len=65 span[header_value]="\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO"
+off=1337 len=65 span[header_value]="\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1"
+off=1404 len=75 span[header_value]="\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0"
+off=1481 len=65 span[header_value]="\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD"
+off=1548 len=55 span[header_value]="\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv"
+off=1605 len=65 span[header_value]="\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3"
+off=1672 len=65 span[header_value]="\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8"
+off=1739 len=65 span[header_value]="\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk"
+off=1806 len=65 span[header_value]="\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK"
+off=1873 len=65 span[header_value]="\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu"
+off=1940 len=65 span[header_value]="\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3"
+off=2007 len=5 span[header_value]="\tRA=="
+off=2014 len=26 span[header_value]="\t-----END CERTIFICATE-----"
+off=2042 header_value complete
+off=2044 headers complete method=1 v=1/1 flags=0 content_length=0
+off=2044 message complete
+```
+
+[0]: https://github.com/nodejs/http-parser
diff --git a/llhttp/test/request/transfer-encoding.md b/llhttp/test/request/transfer-encoding.md
new file mode 100644
index 0000000..0f839bc
--- /dev/null
+++ b/llhttp/test/request/transfer-encoding.md
@@ -0,0 +1,1187 @@
+Transfer-Encoding header
+========================
+
+## `chunked`
+
+### Parsing and setting flag
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+```
+
+### Parse chunks with lowercase size
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+a
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=52 chunk header len=10
+off=52 len=10 span[body]="0123456789"
+off=64 chunk complete
+off=67 chunk header len=0
+off=69 chunk complete
+off=69 message complete
+```
+
+### Parse chunks with uppercase size
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+A
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=52 chunk header len=10
+off=52 len=10 span[body]="0123456789"
+off=64 chunk complete
+off=67 chunk header len=0
+off=69 chunk complete
+off=69 message complete
+```
+
+### POST with `Transfer-Encoding: chunked`
+
+<!-- meta={"type": "request"} -->
+```http
+POST /post_chunked_all_your_base HTTP/1.1
+Transfer-Encoding: chunked
+
+1e
+all your base are belong to us
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=27 span[url]="/post_chunked_all_your_base"
+off=33 url complete
+off=38 len=3 span[version]="1.1"
+off=41 version complete
+off=43 len=17 span[header_field]="Transfer-Encoding"
+off=61 header_field complete
+off=62 len=7 span[header_value]="chunked"
+off=71 header_value complete
+off=73 headers complete method=3 v=1/1 flags=208 content_length=0
+off=77 chunk header len=30
+off=77 len=30 span[body]="all your base are belong to us"
+off=109 chunk complete
+off=112 chunk header len=0
+off=114 chunk complete
+off=114 message complete
+```
+
+### Two chunks and triple zero prefixed end chunk
+
+<!-- meta={"type": "request"} -->
+```http
+POST /two_chunks_mult_zero_end HTTP/1.1
+Transfer-Encoding: chunked
+
+5
+hello
+6
+ world
+000
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=25 span[url]="/two_chunks_mult_zero_end"
+off=31 url complete
+off=36 len=3 span[version]="1.1"
+off=39 version complete
+off=41 len=17 span[header_field]="Transfer-Encoding"
+off=59 header_field complete
+off=60 len=7 span[header_value]="chunked"
+off=69 header_value complete
+off=71 headers complete method=3 v=1/1 flags=208 content_length=0
+off=74 chunk header len=5
+off=74 len=5 span[body]="hello"
+off=81 chunk complete
+off=84 chunk header len=6
+off=84 len=6 span[body]=" world"
+off=92 chunk complete
+off=97 chunk header len=0
+off=99 chunk complete
+off=99 message complete
+```
+
+### Trailing headers
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_trailing_headers HTTP/1.1
+Transfer-Encoding: chunked
+
+5
+hello
+6
+ world
+0
+Vary: *
+Content-Type: text/plain
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=27 span[url]="/chunked_w_trailing_headers"
+off=33 url complete
+off=38 len=3 span[version]="1.1"
+off=41 version complete
+off=43 len=17 span[header_field]="Transfer-Encoding"
+off=61 header_field complete
+off=62 len=7 span[header_value]="chunked"
+off=71 header_value complete
+off=73 headers complete method=3 v=1/1 flags=208 content_length=0
+off=76 chunk header len=5
+off=76 len=5 span[body]="hello"
+off=83 chunk complete
+off=86 chunk header len=6
+off=86 len=6 span[body]=" world"
+off=94 chunk complete
+off=97 chunk header len=0
+off=97 len=4 span[header_field]="Vary"
+off=102 header_field complete
+off=103 len=1 span[header_value]="*"
+off=106 header_value complete
+off=106 len=12 span[header_field]="Content-Type"
+off=119 header_field complete
+off=120 len=10 span[header_value]="text/plain"
+off=132 header_value complete
+off=134 chunk complete
+off=134 message complete
+```
+
+### Chunk extensions
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_unicorns_after_length HTTP/1.1
+Transfer-Encoding: chunked
+
+5;ilovew3;somuchlove=aretheseparametersfor;another=withvalue
+hello
+6;blahblah;blah
+ world
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=32 span[url]="/chunked_w_unicorns_after_length"
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=48 len=17 span[header_field]="Transfer-Encoding"
+off=66 header_field complete
+off=67 len=7 span[header_value]="chunked"
+off=76 header_value complete
+off=78 headers complete method=3 v=1/1 flags=208 content_length=0
+off=80 len=7 span[chunk_extension_name]="ilovew3"
+off=88 chunk_extension_name complete
+off=88 len=10 span[chunk_extension_name]="somuchlove"
+off=99 chunk_extension_name complete
+off=99 len=21 span[chunk_extension_value]="aretheseparametersfor"
+off=121 chunk_extension_value complete
+off=121 len=7 span[chunk_extension_name]="another"
+off=129 chunk_extension_name complete
+off=129 len=9 span[chunk_extension_value]="withvalue"
+off=139 chunk_extension_value complete
+off=140 chunk header len=5
+off=140 len=5 span[body]="hello"
+off=147 chunk complete
+off=149 len=8 span[chunk_extension_name]="blahblah"
+off=158 chunk_extension_name complete
+off=158 len=4 span[chunk_extension_name]="blah"
+off=163 chunk_extension_name complete
+off=164 chunk header len=6
+off=164 len=6 span[body]=" world"
+off=172 chunk complete
+off=175 chunk header len=0
+```
+
+### No semicolon before chunk extensions
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_unicorns_after_length HTTP/1.1
+Host: localhost
+Transfer-encoding: chunked
+
+2 erfrferferf
+aa
+0 rrrr
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=32 span[url]="/chunked_w_unicorns_after_length"
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=48 len=4 span[header_field]="Host"
+off=53 header_field complete
+off=54 len=9 span[header_value]="localhost"
+off=65 header_value complete
+off=65 len=17 span[header_field]="Transfer-encoding"
+off=83 header_field complete
+off=84 len=7 span[header_value]="chunked"
+off=93 header_value complete
+off=95 headers complete method=3 v=1/1 flags=208 content_length=0
+off=97 error code=12 reason="Invalid character in chunk size"
+```
+
+### No extension after semicolon
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_unicorns_after_length HTTP/1.1
+Host: localhost
+Transfer-encoding: chunked
+
+2;
+aa
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=32 span[url]="/chunked_w_unicorns_after_length"
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=48 len=4 span[header_field]="Host"
+off=53 header_field complete
+off=54 len=9 span[header_value]="localhost"
+off=65 header_value complete
+off=65 len=17 span[header_field]="Transfer-encoding"
+off=83 header_field complete
+off=84 len=7 span[header_value]="chunked"
+off=93 header_value complete
+off=95 headers complete method=3 v=1/1 flags=208 content_length=0
+off=98 error code=2 reason="Invalid character in chunk extensions"
+```
+
+
+### Chunk extensions quoting
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_unicorns_after_length HTTP/1.1
+Transfer-Encoding: chunked
+
+5;ilovew3="I \"love\"; \\extensions\\";somuchlove="aretheseparametersfor";blah;foo=bar
+hello
+6;blahblah;blah
+ world
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=32 span[url]="/chunked_w_unicorns_after_length"
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=48 len=17 span[header_field]="Transfer-Encoding"
+off=66 header_field complete
+off=67 len=7 span[header_value]="chunked"
+off=76 header_value complete
+off=78 headers complete method=3 v=1/1 flags=208 content_length=0
+off=80 len=7 span[chunk_extension_name]="ilovew3"
+off=88 chunk_extension_name complete
+off=88 len=28 span[chunk_extension_value]=""I \"love\"; \\extensions\\""
+off=116 chunk_extension_value complete
+off=117 len=10 span[chunk_extension_name]="somuchlove"
+off=128 chunk_extension_name complete
+off=128 len=23 span[chunk_extension_value]=""aretheseparametersfor""
+off=151 chunk_extension_value complete
+off=152 len=4 span[chunk_extension_name]="blah"
+off=157 chunk_extension_name complete
+off=157 len=3 span[chunk_extension_name]="foo"
+off=161 chunk_extension_name complete
+off=161 len=3 span[chunk_extension_value]="bar"
+off=165 chunk_extension_value complete
+off=166 chunk header len=5
+off=166 len=5 span[body]="hello"
+off=173 chunk complete
+off=175 len=8 span[chunk_extension_name]="blahblah"
+off=184 chunk_extension_name complete
+off=184 len=4 span[chunk_extension_name]="blah"
+off=189 chunk_extension_name complete
+off=190 chunk header len=6
+off=190 len=6 span[body]=" world"
+off=198 chunk complete
+off=201 chunk header len=0
+```
+
+
+### Unbalanced chunk extensions quoting
+
+<!-- meta={"type": "request"} -->
+```http
+POST /chunked_w_unicorns_after_length HTTP/1.1
+Transfer-Encoding: chunked
+
+5;ilovew3="abc";somuchlove="def; ghi
+hello
+6;blahblah;blah
+ world
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=32 span[url]="/chunked_w_unicorns_after_length"
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=48 len=17 span[header_field]="Transfer-Encoding"
+off=66 header_field complete
+off=67 len=7 span[header_value]="chunked"
+off=76 header_value complete
+off=78 headers complete method=3 v=1/1 flags=208 content_length=0
+off=80 len=7 span[chunk_extension_name]="ilovew3"
+off=88 chunk_extension_name complete
+off=88 len=5 span[chunk_extension_value]=""abc""
+off=93 chunk_extension_value complete
+off=94 len=10 span[chunk_extension_name]="somuchlove"
+off=105 chunk_extension_name complete
+off=105 len=9 span[chunk_extension_value]=""def; ghi"
+off=115 error code=2 reason="Invalid character in chunk extensions quoted value"
+```
+
+## Ignoring `pigeons`
+
+Requests cannot have invalid `Transfer-Encoding`. It is impossible to determine
+their body size. Not erroring would make HTTP smuggling attacks possible.
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: pigeons
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="pigeons"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=200 content_length=0
+off=49 error code=15 reason="Request has invalid `Transfer-Encoding`"
+```
+
+## POST with `Transfer-Encoding` and `Content-Length`
+
+<!-- meta={"type": "request"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: identity
+Content-Length: 5
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=8 span[header_value]="identity"
+off=96 header_value complete
+off=96 len=14 span[header_field]="Content-Length"
+off=111 header_field complete
+off=111 error code=11 reason="Content-Length can't be present with Transfer-Encoding"
+```
+
+## POST with `Transfer-Encoding` and `Content-Length` (lenient)
+
+TODO(indutny): should we allow it even in lenient mode? (Consider disabling
+this).
+
+NOTE: `Content-Length` is ignored when `Transfer-Encoding` is present. Messages
+(in lenient mode) are read until EOF.
+
+<!-- meta={"type": "request-lenient-chunked-length"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: identity
+Content-Length: 1
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=8 span[header_value]="identity"
+off=96 header_value complete
+off=96 len=14 span[header_field]="Content-Length"
+off=111 header_field complete
+off=112 len=1 span[header_value]="1"
+off=115 header_value complete
+off=117 headers complete method=3 v=1/1 flags=220 content_length=1
+off=117 len=5 span[body]="World"
+```
+
+## POST with empty `Transfer-Encoding` and `Content-Length` (lenient)
+
+<!-- meta={"type": "request"} -->
+```http
+POST / HTTP/1.1
+Host: foo
+Content-Length: 10
+Transfer-Encoding:
+Transfer-Encoding:
+Transfer-Encoding:
+
+2
+AA
+0
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=1 span[url]="/"
+off=7 url complete
+off=12 len=3 span[version]="1.1"
+off=15 version complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=3 span[header_value]="foo"
+off=28 header_value complete
+off=28 len=14 span[header_field]="Content-Length"
+off=43 header_field complete
+off=44 len=2 span[header_value]="10"
+off=48 header_value complete
+off=48 len=17 span[header_field]="Transfer-Encoding"
+off=66 header_field complete
+off=66 error code=15 reason="Transfer-Encoding can't be present with Content-Length"
+```
+
+## POST with `chunked` before other transfer coding names
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: chunked, deflate
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=7 span[header_value]="chunked"
+off=94 error code=15 reason="Invalid `Transfer-Encoding` header value"
+```
+
+## POST with `chunked` and duplicate transfer-encoding
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: chunked
+Transfer-Encoding: deflate
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=7 span[header_value]="chunked"
+off=95 header_value complete
+off=95 len=17 span[header_field]="Transfer-Encoding"
+off=113 header_field complete
+off=114 len=0 span[header_value]=""
+off=115 error code=15 reason="Invalid `Transfer-Encoding` header value"
+```
+
+## POST with `chunked` before other transfer-coding (lenient)
+
+<!-- meta={"type": "request-lenient-transfer-encoding"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: chunked, deflate
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=16 span[header_value]="chunked, deflate"
+off=104 header_value complete
+off=106 headers complete method=3 v=1/1 flags=200 content_length=0
+off=106 len=5 span[body]="World"
+```
+
+## POST with `chunked` and duplicate transfer-encoding (lenient)
+
+<!-- meta={"type": "request-lenient-transfer-encoding"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: chunked
+Transfer-Encoding: deflate
+
+World
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=7 span[header_value]="chunked"
+off=95 header_value complete
+off=95 len=17 span[header_field]="Transfer-Encoding"
+off=113 header_field complete
+off=114 len=7 span[header_value]="deflate"
+off=123 header_value complete
+off=125 headers complete method=3 v=1/1 flags=200 content_length=0
+off=125 len=5 span[body]="World"
+```
+
+## POST with `chunked` as last transfer-encoding
+
+<!-- meta={"type": "request"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: deflate, chunked
+
+5
+World
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=16 span[header_value]="deflate, chunked"
+off=104 header_value complete
+off=106 headers complete method=3 v=1/1 flags=208 content_length=0
+off=109 chunk header len=5
+off=109 len=5 span[body]="World"
+off=116 chunk complete
+off=119 chunk header len=0
+off=121 chunk complete
+off=121 message complete
+```
+
+## POST with `chunked` as last transfer-encoding (multiple headers)
+
+<!-- meta={"type": "request"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: deflate
+Transfer-Encoding: chunked
+
+5
+World
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=7 span[header_value]="deflate"
+off=95 header_value complete
+off=95 len=17 span[header_field]="Transfer-Encoding"
+off=113 header_field complete
+off=114 len=7 span[header_value]="chunked"
+off=123 header_value complete
+off=125 headers complete method=3 v=1/1 flags=208 content_length=0
+off=128 chunk header len=5
+off=128 len=5 span[body]="World"
+off=135 chunk complete
+off=138 chunk header len=0
+off=140 chunk complete
+off=140 message complete
+```
+
+## POST with `chunkedchunked` as transfer-encoding
+
+<!-- meta={"type": "request"} -->
+```http
+POST /post_identity_body_world?q=search#hey HTTP/1.1
+Accept: */*
+Transfer-Encoding: chunkedchunked
+
+5
+World
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=4 span[method]="POST"
+off=4 method complete
+off=5 len=38 span[url]="/post_identity_body_world?q=search#hey"
+off=44 url complete
+off=49 len=3 span[version]="1.1"
+off=52 version complete
+off=54 len=6 span[header_field]="Accept"
+off=61 header_field complete
+off=62 len=3 span[header_value]="*/*"
+off=67 header_value complete
+off=67 len=17 span[header_field]="Transfer-Encoding"
+off=85 header_field complete
+off=86 len=14 span[header_value]="chunkedchunked"
+off=102 header_value complete
+off=104 headers complete method=3 v=1/1 flags=200 content_length=0
+off=104 error code=15 reason="Request has invalid `Transfer-Encoding`"
+```
+
+## Missing last-chunk
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+3
+foo
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=52 chunk header len=3
+off=52 len=3 span[body]="foo"
+off=57 chunk complete
+off=57 error code=12 reason="Invalid character in chunk size"
+```
+
+## Validate chunk parameters
+
+<!-- meta={"type": "request" } -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+3 \n \r\n\
+foo
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=51 error code=12 reason="Invalid character in chunk size"
+```
+
+## Invalid OBS fold after chunked value
+
+<!-- meta={"type": "request" } -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+ abc
+
+5
+World
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 len=5 span[header_value]=" abc"
+off=54 header_value complete
+off=56 headers complete method=4 v=1/1 flags=200 content_length=0
+off=56 error code=15 reason="Request has invalid `Transfer-Encoding`"
+```
+
+### Chunk header not terminated by CRLF
+
+<!-- meta={"type": "request" } -->
+
+```http
+GET / HTTP/1.1
+Host: a
+Connection: close
+Transfer-Encoding: chunked
+
+5\r\r;ABCD
+34
+E
+0
+
+GET / HTTP/1.1
+Host: a
+Content-Length: 5
+
+0
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=1 span[header_value]="a"
+off=25 header_value complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=6 span[header_value]="close "
+off=45 header_value complete
+off=45 len=17 span[header_field]="Transfer-Encoding"
+off=63 header_field complete
+off=64 len=8 span[header_value]="chunked "
+off=74 header_value complete
+off=76 headers complete method=1 v=1/1 flags=20a content_length=0
+off=78 error code=2 reason="Expected LF after chunk size"
+```
+
+### Chunk header not terminated by CRLF (lenient)
+
+<!-- meta={"type": "request-lenient-optional-lf-after-cr" } -->
+
+```http
+GET / HTTP/1.1
+Host: a
+Connection: close
+Transfer-Encoding: chunked
+
+6\r\r;ABCD
+33
+E
+0
+
+GET / HTTP/1.1
+Host: a
+Content-Length: 5
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=1 span[header_value]="a"
+off=25 header_value complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=6 span[header_value]="close "
+off=45 header_value complete
+off=45 len=17 span[header_field]="Transfer-Encoding"
+off=63 header_field complete
+off=64 len=8 span[header_value]="chunked "
+off=74 header_value complete
+off=76 headers complete method=1 v=1/1 flags=20a content_length=0
+off=78 chunk header len=6
+off=78 len=1 span[body]=cr
+off=79 len=5 span[body]=";ABCD"
+off=86 chunk complete
+off=90 chunk header len=51
+off=90 len=1 span[body]="E"
+off=91 len=1 span[body]=cr
+off=92 len=1 span[body]=lf
+off=93 len=1 span[body]="0"
+off=94 len=1 span[body]=cr
+off=95 len=1 span[body]=lf
+off=96 len=1 span[body]=cr
+off=97 len=1 span[body]=lf
+off=98 len=15 span[body]="GET / HTTP/1.1 "
+off=113 len=1 span[body]=cr
+off=114 len=1 span[body]=lf
+off=115 len=7 span[body]="Host: a"
+off=122 len=1 span[body]=cr
+off=123 len=1 span[body]=lf
+off=124 len=17 span[body]="Content-Length: 5"
+off=143 chunk complete
+off=146 chunk header len=0
+off=148 chunk complete
+off=148 message complete
+```
+
+### Chunk data not terminated by CRLF
+
+<!-- meta={"type": "request" } -->
+
+```http
+GET / HTTP/1.1
+Host: a
+Connection: close
+Transfer-Encoding: chunked
+
+5
+ABCDE0
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=1 span[header_value]="a"
+off=25 header_value complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=6 span[header_value]="close "
+off=45 header_value complete
+off=45 len=17 span[header_field]="Transfer-Encoding"
+off=63 header_field complete
+off=64 len=8 span[header_value]="chunked "
+off=74 header_value complete
+off=76 headers complete method=1 v=1/1 flags=20a content_length=0
+off=79 chunk header len=5
+off=79 len=5 span[body]="ABCDE"
+off=84 error code=2 reason="Expected LF after chunk data"
+```
+
+### Chunk data not terminated by CRLF (lenient)
+
+<!-- meta={"type": "request-lenient-optional-crlf-after-chunk" } -->
+
+```http
+GET / HTTP/1.1
+Host: a
+Connection: close
+Transfer-Encoding: chunked
+
+5
+ABCDE0
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=1 span[url]="/"
+off=6 url complete
+off=11 len=3 span[version]="1.1"
+off=14 version complete
+off=16 len=4 span[header_field]="Host"
+off=21 header_field complete
+off=22 len=1 span[header_value]="a"
+off=25 header_value complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=6 span[header_value]="close "
+off=45 header_value complete
+off=45 len=17 span[header_field]="Transfer-Encoding"
+off=63 header_field complete
+off=64 len=8 span[header_value]="chunked "
+off=74 header_value complete
+off=76 headers complete method=1 v=1/1 flags=20a content_length=0
+off=79 chunk header len=5
+off=79 len=5 span[body]="ABCDE"
+off=84 chunk complete
+off=87 chunk header len=0
+```
+
+## Space after chunk header
+
+<!-- meta={"type": "request"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+a \r\n0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=51 error code=12 reason="Invalid character in chunk size"
+```
+
+## Space after chunk header (lenient)
+
+<!-- meta={"type": "request-lenient-spaces-after-chunk-size"} -->
+```http
+PUT /url HTTP/1.1
+Transfer-Encoding: chunked
+
+a \r\n0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="PUT"
+off=3 method complete
+off=4 len=4 span[url]="/url"
+off=9 url complete
+off=14 len=3 span[version]="1.1"
+off=17 version complete
+off=19 len=17 span[header_field]="Transfer-Encoding"
+off=37 header_field complete
+off=38 len=7 span[header_value]="chunked"
+off=47 header_value complete
+off=49 headers complete method=4 v=1/1 flags=208 content_length=0
+off=53 chunk header len=10
+off=53 len=10 span[body]="0123456789"
+off=65 chunk complete
+off=68 chunk header len=0
+off=70 chunk complete
+off=70 message complete
+```
diff --git a/llhttp/test/request/uri.md b/llhttp/test/request/uri.md
new file mode 100644
index 0000000..f7f12b0
--- /dev/null
+++ b/llhttp/test/request/uri.md
@@ -0,0 +1,243 @@
+URI
+===
+
+## Quotes in URI
+
+<!-- meta={"type": "request"} -->
+```http
+GET /with_"lovely"_quotes?foo=\"bar\" HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=33 span[url]="/with_"lovely"_quotes?foo=\"bar\""
+off=38 url complete
+off=43 len=3 span[version]="1.1"
+off=46 version complete
+off=50 headers complete method=1 v=1/1 flags=0 content_length=0
+off=50 message complete
+```
+
+## Query URL with question mark
+
+Some clients include `?` characters in query strings.
+
+<!-- meta={"type": "request"} -->
+```http
+GET /test.cgi?foo=bar?baz HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=21 span[url]="/test.cgi?foo=bar?baz"
+off=26 url complete
+off=31 len=3 span[version]="1.1"
+off=34 version complete
+off=38 headers complete method=1 v=1/1 flags=0 content_length=0
+off=38 message complete
+```
+
+## Host terminated by a query string
+
+<!-- meta={"type": "request"} -->
+```http
+GET http://hypnotoad.org?hail=all HTTP/1.1\r\n
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=29 span[url]="http://hypnotoad.org?hail=all"
+off=34 url complete
+off=39 len=3 span[version]="1.1"
+off=42 version complete
+off=46 headers complete method=1 v=1/1 flags=0 content_length=0
+off=46 message complete
+```
+
+## `host:port` terminated by a query string
+
+<!-- meta={"type": "request"} -->
+```http
+GET http://hypnotoad.org:1234?hail=all HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=34 span[url]="http://hypnotoad.org:1234?hail=all"
+off=39 url complete
+off=44 len=3 span[version]="1.1"
+off=47 version complete
+off=51 headers complete method=1 v=1/1 flags=0 content_length=0
+off=51 message complete
+```
+
+## Query URL with vertical bar character
+
+It should be allowed to have vertical bar symbol in URI: `|`.
+
+See: https://github.com/nodejs/node/issues/27584
+
+<!-- meta={"type": "request"} -->
+```http
+GET /test.cgi?query=| HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=17 span[url]="/test.cgi?query=|"
+off=22 url complete
+off=27 len=3 span[version]="1.1"
+off=30 version complete
+off=34 headers complete method=1 v=1/1 flags=0 content_length=0
+off=34 message complete
+```
+
+## `host:port` terminated by a space
+
+<!-- meta={"type": "request"} -->
+```http
+GET http://hypnotoad.org:1234 HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=25 span[url]="http://hypnotoad.org:1234"
+off=30 url complete
+off=35 len=3 span[version]="1.1"
+off=38 version complete
+off=42 headers complete method=1 v=1/1 flags=0 content_length=0
+off=42 message complete
+```
+
+## Disallow UTF-8 in URI path in strict mode
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET /Ī“Ā¶/Ī“t/pope?q=1#narf HTTP/1.1
+Host: github.com
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=5 error code=7 reason="Invalid char in url path"
+```
+
+## Fragment in URI
+
+<!-- meta={"type": "request"} -->
+```http
+GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=40 span[url]="/forums/1/topics/2375?page=1#posts-17408"
+off=45 url complete
+off=50 len=3 span[version]="1.1"
+off=53 version complete
+off=57 headers complete method=1 v=1/1 flags=0 content_length=0
+off=57 message complete
+```
+
+## Underscore in hostname
+
+<!-- meta={"type": "request"} -->
+```http
+CONNECT home_0.netscape.com:443 HTTP/1.0
+User-agent: Mozilla/1.1N
+Proxy-authorization: basic aGVsbG86d29ybGQ=
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=7 span[method]="CONNECT"
+off=7 method complete
+off=8 len=23 span[url]="home_0.netscape.com:443"
+off=32 url complete
+off=37 len=3 span[version]="1.0"
+off=40 version complete
+off=42 len=10 span[header_field]="User-agent"
+off=53 header_field complete
+off=54 len=12 span[header_value]="Mozilla/1.1N"
+off=68 header_value complete
+off=68 len=19 span[header_field]="Proxy-authorization"
+off=88 header_field complete
+off=89 len=22 span[header_value]="basic aGVsbG86d29ybGQ="
+off=113 header_value complete
+off=115 headers complete method=5 v=1/0 flags=0 content_length=0
+off=115 message complete
+off=115 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+## `host:port` and basic auth
+
+<!-- meta={"type": "request"} -->
+```http
+GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=41 span[url]="http://a%12:b!&*$@hypnotoad.org:1234/toto"
+off=46 url complete
+off=51 len=3 span[version]="1.1"
+off=54 version complete
+off=58 headers complete method=1 v=1/1 flags=0 content_length=0
+off=58 message complete
+```
+
+## Space in URI
+
+<!-- meta={"type": "request", "noScan": true} -->
+```http
+GET /foo bar/ HTTP/1.1
+
+
+```
+
+```log
+off=0 message begin
+off=0 len=3 span[method]="GET"
+off=3 method complete
+off=4 len=4 span[url]="/foo"
+off=9 url complete
+off=9 error code=8 reason="Expected HTTP/"
+```
diff --git a/llhttp/test/response/connection.md b/llhttp/test/response/connection.md
new file mode 100644
index 0000000..11f9eb6
--- /dev/null
+++ b/llhttp/test/response/connection.md
@@ -0,0 +1,647 @@
+Connection header
+=================
+
+## Proxy-Connection
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Content-Type: text/html; charset=UTF-8
+Content-Length: 11
+Proxy-Connection: close
+Date: Thu, 31 Dec 2009 20:55:48 +0000
+
+hello world
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=12 span[header_field]="Content-Type"
+off=30 header_field complete
+off=31 len=24 span[header_value]="text/html; charset=UTF-8"
+off=57 header_value complete
+off=57 len=14 span[header_field]="Content-Length"
+off=72 header_field complete
+off=73 len=2 span[header_value]="11"
+off=77 header_value complete
+off=77 len=16 span[header_field]="Proxy-Connection"
+off=94 header_field complete
+off=95 len=5 span[header_value]="close"
+off=102 header_value complete
+off=102 len=4 span[header_field]="Date"
+off=107 header_field complete
+off=108 len=31 span[header_value]="Thu, 31 Dec 2009 20:55:48 +0000"
+off=141 header_value complete
+off=143 headers complete status=200 v=1/1 flags=22 content_length=11
+off=143 len=11 span[body]="hello world"
+off=154 message complete
+```
+
+## HTTP/1.0 with keep-alive and EOF-terminated 200 status
+
+There is no `Content-Length` in this response, so even though the
+`keep-alive` is on - it should read until EOF.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.0 200 OK
+Connection: keep-alive
+
+HTTP/1.0 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.0"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=10 span[header_value]="keep-alive"
+off=41 header_value complete
+off=43 headers complete status=200 v=1/0 flags=1 content_length=0
+off=43 len=15 span[body]="HTTP/1.0 200 OK"
+```
+
+## HTTP/1.0 with keep-alive and 204 status
+
+Responses with `204` status cannot have a body.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.0 204 No content
+Connection: keep-alive
+
+HTTP/1.0 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.0"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=10 span[header_value]="keep-alive"
+off=49 header_value complete
+off=51 headers complete status=204 v=1/0 flags=1 content_length=0
+off=51 message complete
+off=51 reset
+off=51 message begin
+off=56 len=3 span[version]="1.0"
+off=59 version complete
+off=64 len=2 span[status]="OK"
+```
+
+## HTTP/1.1 with EOF-terminated 200 status
+
+There is no `Content-Length` in this response, so even though the
+`keep-alive` is on (implicitly in HTTP 1.1) - it should read until EOF.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+
+HTTP/1.1 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=19 headers complete status=200 v=1/1 flags=0 content_length=0
+off=19 len=15 span[body]="HTTP/1.1 200 OK"
+```
+
+## HTTP/1.1 with 204 status
+
+Responses with `204` status cannot have a body.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 204 No content
+
+HTTP/1.1 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=27 headers complete status=204 v=1/1 flags=0 content_length=0
+off=27 message complete
+off=27 reset
+off=27 message begin
+off=32 len=3 span[version]="1.1"
+off=35 version complete
+off=40 len=2 span[status]="OK"
+```
+
+## HTTP/1.1 with keep-alive disabled and 204 status
+
+<!-- meta={"type": "response" } -->
+```http
+HTTP/1.1 204 No content
+Connection: close
+
+HTTP/1.1 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=5 span[header_value]="close"
+off=44 header_value complete
+off=46 headers complete status=204 v=1/1 flags=2 content_length=0
+off=46 message complete
+off=47 error code=5 reason="Data after `Connection: close`"
+```
+
+## HTTP/1.1 with keep-alive disabled, content-length (lenient)
+
+Parser should discard extra request in lenient mode.
+
+<!-- meta={"type": "response-lenient-data-after-close" } -->
+```http
+HTTP/1.1 200 No content
+Content-Length: 5
+Connection: close
+
+2ad731e3-4dcd-4f70-b871-0ad284b29ffc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=25 len=14 span[header_field]="Content-Length"
+off=40 header_field complete
+off=41 len=1 span[header_value]="5"
+off=44 header_value complete
+off=44 len=10 span[header_field]="Connection"
+off=55 header_field complete
+off=56 len=5 span[header_value]="close"
+off=63 header_value complete
+off=65 headers complete status=200 v=1/1 flags=22 content_length=5
+off=65 len=5 span[body]="2ad73"
+off=70 message complete
+```
+
+## HTTP/1.1 with keep-alive disabled, content-length
+
+Parser should discard extra request in strict mode.
+
+<!-- meta={"type": "response" } -->
+```http
+HTTP/1.1 200 No content
+Content-Length: 5
+Connection: close
+
+2ad731e3-4dcd-4f70-b871-0ad284b29ffc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=25 len=14 span[header_field]="Content-Length"
+off=40 header_field complete
+off=41 len=1 span[header_value]="5"
+off=44 header_value complete
+off=44 len=10 span[header_field]="Connection"
+off=55 header_field complete
+off=56 len=5 span[header_value]="close"
+off=63 header_value complete
+off=65 headers complete status=200 v=1/1 flags=22 content_length=5
+off=65 len=5 span[body]="2ad73"
+off=70 message complete
+off=71 error code=5 reason="Data after `Connection: close`"
+```
+
+## HTTP/1.1 with keep-alive disabled and 204 status (lenient)
+
+<!-- meta={"type": "response-lenient-keep-alive"} -->
+```http
+HTTP/1.1 204 No content
+Connection: close
+
+HTTP/1.1 200 OK
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=10 span[status]="No content"
+off=25 status complete
+off=25 len=10 span[header_field]="Connection"
+off=36 header_field complete
+off=37 len=5 span[header_value]="close"
+off=44 header_value complete
+off=46 headers complete status=204 v=1/1 flags=2 content_length=0
+off=46 message complete
+off=46 reset
+off=46 message begin
+off=51 len=3 span[version]="1.1"
+off=54 version complete
+off=59 len=2 span[status]="OK"
+```
+
+## HTTP 101 response with Upgrade and Content-Length header
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 101 Switching Protocols
+Connection: upgrade
+Upgrade: h2c
+Content-Length: 4
+
+body\
+proto
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=19 span[status]="Switching Protocols"
+off=34 status complete
+off=34 len=10 span[header_field]="Connection"
+off=45 header_field complete
+off=46 len=7 span[header_value]="upgrade"
+off=55 header_value complete
+off=55 len=7 span[header_field]="Upgrade"
+off=63 header_field complete
+off=64 len=3 span[header_value]="h2c"
+off=69 header_value complete
+off=69 len=14 span[header_field]="Content-Length"
+off=84 header_field complete
+off=85 len=1 span[header_value]="4"
+off=88 header_value complete
+off=90 headers complete status=101 v=1/1 flags=34 content_length=4
+off=90 message complete
+off=90 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+## HTTP 101 response with Upgrade and Transfer-Encoding header
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 101 Switching Protocols
+Connection: upgrade
+Upgrade: h2c
+Transfer-Encoding: chunked
+
+2
+bo
+2
+dy
+0
+
+proto
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=19 span[status]="Switching Protocols"
+off=34 status complete
+off=34 len=10 span[header_field]="Connection"
+off=45 header_field complete
+off=46 len=7 span[header_value]="upgrade"
+off=55 header_value complete
+off=55 len=7 span[header_field]="Upgrade"
+off=63 header_field complete
+off=64 len=3 span[header_value]="h2c"
+off=69 header_value complete
+off=69 len=17 span[header_field]="Transfer-Encoding"
+off=87 header_field complete
+off=88 len=7 span[header_value]="chunked"
+off=97 header_value complete
+off=99 headers complete status=101 v=1/1 flags=21c content_length=0
+off=99 message complete
+off=99 error code=22 reason="Pause on CONNECT/Upgrade"
+```
+
+## HTTP 200 response with Upgrade header
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Connection: upgrade
+Upgrade: h2c
+
+body
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=7 span[header_value]="upgrade"
+off=38 header_value complete
+off=38 len=7 span[header_field]="Upgrade"
+off=46 header_field complete
+off=47 len=3 span[header_value]="h2c"
+off=52 header_value complete
+off=54 headers complete status=200 v=1/1 flags=14 content_length=0
+off=54 len=4 span[body]="body"
+```
+
+## HTTP 200 response with Upgrade header and Content-Length
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Connection: upgrade
+Upgrade: h2c
+Content-Length: 4
+
+body
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=7 span[header_value]="upgrade"
+off=38 header_value complete
+off=38 len=7 span[header_field]="Upgrade"
+off=46 header_field complete
+off=47 len=3 span[header_value]="h2c"
+off=52 header_value complete
+off=52 len=14 span[header_field]="Content-Length"
+off=67 header_field complete
+off=68 len=1 span[header_value]="4"
+off=71 header_value complete
+off=73 headers complete status=200 v=1/1 flags=34 content_length=4
+off=73 len=4 span[body]="body"
+off=77 message complete
+```
+
+## HTTP 200 response with Upgrade header and Transfer-Encoding
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Connection: upgrade
+Upgrade: h2c
+Transfer-Encoding: chunked
+
+2
+bo
+2
+dy
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=10 span[header_field]="Connection"
+off=28 header_field complete
+off=29 len=7 span[header_value]="upgrade"
+off=38 header_value complete
+off=38 len=7 span[header_field]="Upgrade"
+off=46 header_field complete
+off=47 len=3 span[header_value]="h2c"
+off=52 header_value complete
+off=52 len=17 span[header_field]="Transfer-Encoding"
+off=70 header_field complete
+off=71 len=7 span[header_value]="chunked"
+off=80 header_value complete
+off=82 headers complete status=200 v=1/1 flags=21c content_length=0
+off=85 chunk header len=2
+off=85 len=2 span[body]="bo"
+off=89 chunk complete
+off=92 chunk header len=2
+off=92 len=2 span[body]="dy"
+off=96 chunk complete
+off=99 chunk header len=0
+off=101 chunk complete
+off=101 message complete
+```
+
+## HTTP 304 with Content-Length
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 304 Not Modified
+Content-Length: 10
+
+
+HTTP/1.1 200 OK
+Content-Length: 5
+
+hello
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=12 span[status]="Not Modified"
+off=27 status complete
+off=27 len=14 span[header_field]="Content-Length"
+off=42 header_field complete
+off=43 len=2 span[header_value]="10"
+off=47 header_value complete
+off=49 headers complete status=304 v=1/1 flags=20 content_length=10
+off=49 message complete
+off=51 reset
+off=51 message begin
+off=56 len=3 span[version]="1.1"
+off=59 version complete
+off=64 len=2 span[status]="OK"
+off=68 status complete
+off=68 len=14 span[header_field]="Content-Length"
+off=83 header_field complete
+off=84 len=1 span[header_value]="5"
+off=87 header_value complete
+off=89 headers complete status=200 v=1/1 flags=20 content_length=5
+off=89 len=5 span[body]="hello"
+off=94 message complete
+```
+
+## HTTP 304 with Transfer-Encoding
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 304 Not Modified
+Transfer-Encoding: chunked
+
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+5
+hello
+0
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=12 span[status]="Not Modified"
+off=27 status complete
+off=27 len=17 span[header_field]="Transfer-Encoding"
+off=45 header_field complete
+off=46 len=7 span[header_value]="chunked"
+off=55 header_value complete
+off=57 headers complete status=304 v=1/1 flags=208 content_length=0
+off=57 message complete
+off=57 reset
+off=57 message begin
+off=62 len=3 span[version]="1.1"
+off=65 version complete
+off=70 len=2 span[status]="OK"
+off=74 status complete
+off=74 len=17 span[header_field]="Transfer-Encoding"
+off=92 header_field complete
+off=93 len=7 span[header_value]="chunked"
+off=102 header_value complete
+off=104 headers complete status=200 v=1/1 flags=208 content_length=0
+off=107 chunk header len=5
+off=107 len=5 span[body]="hello"
+off=114 chunk complete
+off=117 chunk header len=0
+```
+
+## HTTP 100 first, then 400
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 100 Continue
+
+
+HTTP/1.1 404 Not Found
+Content-Type: text/plain; charset=utf-8
+Content-Length: 14
+Date: Fri, 15 Sep 2023 19:47:23 GMT
+Server: Python/3.10 aiohttp/4.0.0a2.dev0
+
+404: Not Found
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=8 span[status]="Continue"
+off=23 status complete
+off=25 headers complete status=100 v=1/1 flags=0 content_length=0
+off=25 message complete
+off=27 reset
+off=27 message begin
+off=32 len=3 span[version]="1.1"
+off=35 version complete
+off=40 len=9 span[status]="Not Found"
+off=51 status complete
+off=51 len=12 span[header_field]="Content-Type"
+off=64 header_field complete
+off=65 len=25 span[header_value]="text/plain; charset=utf-8"
+off=92 header_value complete
+off=92 len=14 span[header_field]="Content-Length"
+off=107 header_field complete
+off=108 len=2 span[header_value]="14"
+off=112 header_value complete
+off=112 len=4 span[header_field]="Date"
+off=117 header_field complete
+off=118 len=29 span[header_value]="Fri, 15 Sep 2023 19:47:23 GMT"
+off=149 header_value complete
+off=149 len=6 span[header_field]="Server"
+off=156 header_field complete
+off=157 len=32 span[header_value]="Python/3.10 aiohttp/4.0.0a2.dev0"
+off=191 header_value complete
+off=193 headers complete status=404 v=1/1 flags=20 content_length=14
+off=193 len=14 span[body]="404: Not Found"
+off=207 message complete
+```
+
+## HTTP 103 first, then 200
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 103 Early Hints
+Link: </styles.css>; rel=preload; as=style
+
+HTTP/1.1 200 OK
+Date: Wed, 13 Sep 2023 11:09:41 GMT
+Connection: keep-alive
+Keep-Alive: timeout=5
+Content-Length: 17
+
+response content
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=11 span[status]="Early Hints"
+off=26 status complete
+off=26 len=4 span[header_field]="Link"
+off=31 header_field complete
+off=32 len=36 span[header_value]="</styles.css>; rel=preload; as=style"
+off=70 header_value complete
+off=72 headers complete status=103 v=1/1 flags=0 content_length=0
+off=72 message complete
+off=72 reset
+off=72 message begin
+off=77 len=3 span[version]="1.1"
+off=80 version complete
+off=85 len=2 span[status]="OK"
+off=89 status complete
+off=89 len=4 span[header_field]="Date"
+off=94 header_field complete
+off=95 len=29 span[header_value]="Wed, 13 Sep 2023 11:09:41 GMT"
+off=126 header_value complete
+off=126 len=10 span[header_field]="Connection"
+off=137 header_field complete
+off=138 len=10 span[header_value]="keep-alive"
+off=150 header_value complete
+off=150 len=10 span[header_field]="Keep-Alive"
+off=161 header_field complete
+off=162 len=9 span[header_value]="timeout=5"
+off=173 header_value complete
+off=173 len=14 span[header_field]="Content-Length"
+off=188 header_field complete
+off=189 len=2 span[header_value]="17"
+off=193 header_value complete
+off=195 headers complete status=200 v=1/1 flags=21 content_length=17
+off=195 len=16 span[body]="response content"
+``` \ No newline at end of file
diff --git a/llhttp/test/response/content-length.md b/llhttp/test/response/content-length.md
new file mode 100644
index 0000000..6c33924
--- /dev/null
+++ b/llhttp/test/response/content-length.md
@@ -0,0 +1,158 @@
+Content-Length header
+=====================
+
+## Response without `Content-Length`, but with body
+
+The client should wait for the server's EOF. That is, when
+`Content-Length` is not specified, and `Connection: close`, the end of body is
+specified by the EOF.
+
+_(Compare with APACHEBENCH_GET)_
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Date: Tue, 04 Aug 2009 07:59:32 GMT
+Server: Apache
+X-Powered-By: Servlet/2.5 JSP/2.1
+Content-Type: text/xml; charset=utf-8
+Connection: close
+
+<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
+<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n\
+ <SOAP-ENV:Body>\n\
+ <SOAP-ENV:Fault>\n\
+ <faultcode>SOAP-ENV:Client</faultcode>\n\
+ <faultstring>Client Error</faultstring>\n\
+ </SOAP-ENV:Fault>\n\
+ </SOAP-ENV:Body>\n\
+</SOAP-ENV:Envelope>
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Date"
+off=22 header_field complete
+off=23 len=29 span[header_value]="Tue, 04 Aug 2009 07:59:32 GMT"
+off=54 header_value complete
+off=54 len=6 span[header_field]="Server"
+off=61 header_field complete
+off=62 len=6 span[header_value]="Apache"
+off=70 header_value complete
+off=70 len=12 span[header_field]="X-Powered-By"
+off=83 header_field complete
+off=84 len=19 span[header_value]="Servlet/2.5 JSP/2.1"
+off=105 header_value complete
+off=105 len=12 span[header_field]="Content-Type"
+off=118 header_field complete
+off=119 len=23 span[header_value]="text/xml; charset=utf-8"
+off=144 header_value complete
+off=144 len=10 span[header_field]="Connection"
+off=155 header_field complete
+off=156 len=5 span[header_value]="close"
+off=163 header_value complete
+off=165 headers complete status=200 v=1/1 flags=2 content_length=0
+off=165 len=42 span[body]="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+off=207 len=1 span[body]=lf
+off=208 len=80 span[body]="<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+off=288 len=1 span[body]=lf
+off=289 len=17 span[body]=" <SOAP-ENV:Body>"
+off=306 len=1 span[body]=lf
+off=307 len=20 span[body]=" <SOAP-ENV:Fault>"
+off=327 len=1 span[body]=lf
+off=328 len=45 span[body]=" <faultcode>SOAP-ENV:Client</faultcode>"
+off=373 len=1 span[body]=lf
+off=374 len=46 span[body]=" <faultstring>Client Error</faultstring>"
+off=420 len=1 span[body]=lf
+off=421 len=21 span[body]=" </SOAP-ENV:Fault>"
+off=442 len=1 span[body]=lf
+off=443 len=18 span[body]=" </SOAP-ENV:Body>"
+off=461 len=1 span[body]=lf
+off=462 len=20 span[body]="</SOAP-ENV:Envelope>"
+```
+
+## Content-Length-X
+
+The header that starts with `Content-Length*` should not be treated as
+`Content-Length`.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length-X: 0
+Transfer-Encoding: chunked
+
+2
+OK
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=16 span[header_field]="Content-Length-X"
+off=34 header_field complete
+off=35 len=1 span[header_value]="0"
+off=38 header_value complete
+off=38 len=17 span[header_field]="Transfer-Encoding"
+off=56 header_field complete
+off=57 len=7 span[header_value]="chunked"
+off=66 header_value complete
+off=68 headers complete status=200 v=1/1 flags=208 content_length=0
+off=71 chunk header len=2
+off=71 len=2 span[body]="OK"
+off=75 chunk complete
+off=78 chunk header len=0
+off=80 chunk complete
+off=80 message complete
+```
+
+## Content-Length reset when no body is received
+
+<!-- meta={"type": "response", "skipBody": true} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 123
+
+HTTP/1.1 200 OK
+Content-Length: 456
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=3 span[header_value]="123"
+off=38 header_value complete
+off=40 headers complete status=200 v=1/1 flags=20 content_length=123
+off=40 skip body
+off=40 message complete
+off=40 reset
+off=40 message begin
+off=45 len=3 span[version]="1.1"
+off=48 version complete
+off=53 len=2 span[status]="OK"
+off=57 status complete
+off=57 len=14 span[header_field]="Content-Length"
+off=72 header_field complete
+off=73 len=3 span[header_value]="456"
+off=78 header_value complete
+off=80 headers complete status=200 v=1/1 flags=20 content_length=456
+off=80 skip body
+off=80 message complete
+```
diff --git a/llhttp/test/response/finish.md b/llhttp/test/response/finish.md
new file mode 100644
index 0000000..2938b83
--- /dev/null
+++ b/llhttp/test/response/finish.md
@@ -0,0 +1,23 @@
+Finish
+======
+
+Those tests check the return codes and the behavior of `llhttp_finish()` C API.
+
+## It should be safe to finish with cb after empty response
+
+<!-- meta={"type": "response-finish"} -->
+```http
+HTTP/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=19 headers complete status=200 v=1/1 flags=0 content_length=0
+off=NULL finish=1
+```
diff --git a/llhttp/test/response/invalid.md b/llhttp/test/response/invalid.md
new file mode 100644
index 0000000..034fc4d
--- /dev/null
+++ b/llhttp/test/response/invalid.md
@@ -0,0 +1,285 @@
+Invalid responses
+=================
+
+### Incomplete HTTP protocol
+
+<!-- meta={"type": "response"} -->
+```http
+HTP/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=2 error code=8 reason="Expected HTTP/"
+```
+
+### Extra digit in HTTP major version
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/01.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=1 span[version]="0"
+off=6 error code=9 reason="Expected dot"
+```
+
+### Extra digit in HTTP major version #2
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/11.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=1 span[version]="1"
+off=6 error code=9 reason="Expected dot"
+```
+
+### Extra digit in HTTP minor version
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.01 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.0"
+off=8 version complete
+off=8 error code=9 reason="Expected space after version"
+```
+-->
+
+### Tab after HTTP version
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1\t200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=8 error code=9 reason="Expected space after version"
+```
+
+### CR before response and tab after HTTP version
+
+<!-- meta={"type": "response"} -->
+```http
+\rHTTP/1.1\t200 OK
+
+
+```
+
+```log
+off=1 message begin
+off=6 len=3 span[version]="1.1"
+off=9 version complete
+off=9 error code=9 reason="Expected space after version"
+```
+
+### Headers separated by CR
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Foo: 1\rBar: 2
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=3 span[header_field]="Foo"
+off=21 header_field complete
+off=22 len=1 span[header_value]="1"
+off=24 error code=3 reason="Missing expected LF after header value"
+```
+
+### Invalid HTTP version
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/5.6 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="5.6"
+off=8 error code=9 reason="Invalid HTTP version"
+```
+
+## Invalid space after start line
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+ Host: foo
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=18 error code=30 reason="Unexpected space after start line"
+```
+
+### Extra space between HTTP version and status code
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=9 error code=13 reason="Invalid status code"
+```
+
+### Extra space between status code and reason
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=3 span[status]=" OK"
+off=18 status complete
+off=20 headers complete status=200 v=1/1 flags=0 content_length=0
+```
+
+### One-digit status code
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 2 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=10 error code=13 reason="Invalid status code"
+```
+
+### Only LFs present and no body
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK\nContent-Length: 0\n\n
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 error code=25 reason="Missing expected CR after response line"
+```
+
+### Only LFs present and no body (lenient)
+
+<!-- meta={"type": "response-lenient-all"} -->
+```http
+HTTP/1.1 200 OK\nContent-Length: 0\n\n
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 status complete
+off=16 len=14 span[header_field]="Content-Length"
+off=31 header_field complete
+off=32 len=1 span[header_value]="0"
+off=34 header_value complete
+off=35 headers complete status=200 v=1/1 flags=20 content_length=0
+off=35 message complete
+```
+
+### Only LFs present
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK\n\
+Foo: abc\n\
+Bar: def\n\
+\n\
+BODY\n\
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 error code=25 reason="Missing expected CR after response line"
+```
+
+### Only LFs present (lenient)
+
+<!-- meta={"type": "response-lenient-all"} -->
+```http
+HTTP/1.1 200 OK\n\
+Foo: abc\n\
+Bar: def\n\
+\n\
+BODY\n\
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 status complete
+off=16 len=3 span[header_field]="Foo"
+off=20 header_field complete
+off=21 len=3 span[header_value]="abc"
+off=25 header_value complete
+off=25 len=3 span[header_field]="Bar"
+off=29 header_field complete
+off=30 len=3 span[header_value]="def"
+off=34 header_value complete
+off=35 headers complete status=200 v=1/1 flags=0 content_length=0
+off=35 len=4 span[body]="BODY"
+off=39 len=1 span[body]=lf
+off=40 len=1 span[body]="\"
+``` \ No newline at end of file
diff --git a/llhttp/test/response/lenient-version.md b/llhttp/test/response/lenient-version.md
new file mode 100644
index 0000000..86c6ede
--- /dev/null
+++ b/llhttp/test/response/lenient-version.md
@@ -0,0 +1,20 @@
+Lenient HTTP version parsing
+============================
+
+### Invalid HTTP version (lenient)
+
+<!-- meta={"type": "response-lenient-version"} -->
+```http
+HTTP/5.6 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="5.6"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=19 headers complete status=200 v=5/6 flags=0 content_length=0
+```
diff --git a/llhttp/test/response/pausing.md b/llhttp/test/response/pausing.md
new file mode 100644
index 0000000..d2e870b
--- /dev/null
+++ b/llhttp/test/response/pausing.md
@@ -0,0 +1,330 @@
+Pausing
+=======
+
+### on_message_begin
+
+<!-- meta={"type": "response", "pause": "on_message_begin"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=0 pause
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_message_complete
+
+<!-- meta={"type": "response", "pause": "on_message_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+off=41 pause
+```
+
+### on_version_complete
+
+<!-- meta={"type": "response", "pause": "on_version_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=8 pause
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_status_complete
+
+<!-- meta={"type": "response", "pause": "on_status_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 pause
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_header_field_complete
+
+<!-- meta={"type": "response", "pause": "on_header_field_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=32 pause
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_header_value_complete
+
+<!-- meta={"type": "response", "pause": "on_header_value_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=36 pause
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_headers_complete
+
+<!-- meta={"type": "response", "pause": "on_headers_complete"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+abc
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 pause
+off=38 len=3 span[body]="abc"
+off=41 message complete
+```
+
+### on_chunk_header
+
+<!-- meta={"type": "response", "pause": "on_chunk_header"} -->
+```http
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+a
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=17 span[header_field]="Transfer-Encoding"
+off=35 header_field complete
+off=36 len=7 span[header_value]="chunked"
+off=45 header_value complete
+off=47 headers complete status=200 v=1/1 flags=208 content_length=0
+off=50 chunk header len=10
+off=50 pause
+off=50 len=10 span[body]="0123456789"
+off=62 chunk complete
+off=65 chunk header len=0
+off=65 pause
+off=67 chunk complete
+off=67 message complete
+```
+
+### on_chunk_extension_name
+
+<!-- meta={"type": "response", "pause": "on_chunk_extension_name"} -->
+```http
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+a;foo=bar
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=17 span[header_field]="Transfer-Encoding"
+off=35 header_field complete
+off=36 len=7 span[header_value]="chunked"
+off=45 header_value complete
+off=47 headers complete status=200 v=1/1 flags=208 content_length=0
+off=49 len=3 span[chunk_extension_name]="foo"
+off=53 chunk_extension_name complete
+off=53 pause
+off=53 len=3 span[chunk_extension_value]="bar"
+off=57 chunk_extension_value complete
+off=58 chunk header len=10
+off=58 len=10 span[body]="0123456789"
+off=70 chunk complete
+off=73 chunk header len=0
+off=75 chunk complete
+off=75 message complete
+```
+
+### on_chunk_extension_value
+
+<!-- meta={"type": "response", "pause": "on_chunk_extension_value"} -->
+```http
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+a;foo=bar
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=17 span[header_field]="Transfer-Encoding"
+off=35 header_field complete
+off=36 len=7 span[header_value]="chunked"
+off=45 header_value complete
+off=47 headers complete status=200 v=1/1 flags=208 content_length=0
+off=49 len=3 span[chunk_extension_name]="foo"
+off=53 chunk_extension_name complete
+off=53 len=3 span[chunk_extension_value]="bar"
+off=57 chunk_extension_value complete
+off=57 pause
+off=58 chunk header len=10
+off=58 len=10 span[body]="0123456789"
+off=70 chunk complete
+off=73 chunk header len=0
+off=75 chunk complete
+off=75 message complete
+```
+
+### on_chunk_complete
+
+<!-- meta={"type": "response", "pause": "on_chunk_complete"} -->
+```http
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+a
+0123456789
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=17 span[header_field]="Transfer-Encoding"
+off=35 header_field complete
+off=36 len=7 span[header_value]="chunked"
+off=45 header_value complete
+off=47 headers complete status=200 v=1/1 flags=208 content_length=0
+off=50 chunk header len=10
+off=50 len=10 span[body]="0123456789"
+off=62 chunk complete
+off=62 pause
+off=65 chunk header len=0
+off=67 chunk complete
+off=67 pause
+off=67 message complete
+```
diff --git a/llhttp/test/response/pipelining.md b/llhttp/test/response/pipelining.md
new file mode 100644
index 0000000..01e007a
--- /dev/null
+++ b/llhttp/test/response/pipelining.md
@@ -0,0 +1,60 @@
+Pipelining
+==========
+
+## Should parse multiple events
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Content-Length: 3
+
+AAA
+HTTP/1.1 201 Created
+Content-Length: 4
+
+BBBB
+HTTP/1.1 202 Accepted
+Content-Length: 5
+
+CCCC
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=14 span[header_field]="Content-Length"
+off=32 header_field complete
+off=33 len=1 span[header_value]="3"
+off=36 header_value complete
+off=38 headers complete status=200 v=1/1 flags=20 content_length=3
+off=38 len=3 span[body]="AAA"
+off=41 message complete
+off=43 reset
+off=43 message begin
+off=48 len=3 span[version]="1.1"
+off=51 version complete
+off=56 len=7 span[status]="Created"
+off=65 status complete
+off=65 len=14 span[header_field]="Content-Length"
+off=80 header_field complete
+off=81 len=1 span[header_value]="4"
+off=84 header_value complete
+off=86 headers complete status=201 v=1/1 flags=20 content_length=4
+off=86 len=4 span[body]="BBBB"
+off=90 message complete
+off=92 reset
+off=92 message begin
+off=97 len=3 span[version]="1.1"
+off=100 version complete
+off=105 len=8 span[status]="Accepted"
+off=115 status complete
+off=115 len=14 span[header_field]="Content-Length"
+off=130 header_field complete
+off=131 len=1 span[header_value]="5"
+off=134 header_value complete
+off=136 headers complete status=202 v=1/1 flags=20 content_length=5
+off=136 len=4 span[body]="CCCC"
+``` \ No newline at end of file
diff --git a/llhttp/test/response/sample.md b/llhttp/test/response/sample.md
new file mode 100644
index 0000000..be2e82d
--- /dev/null
+++ b/llhttp/test/response/sample.md
@@ -0,0 +1,653 @@
+Sample responses
+================
+
+## Simple response
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Header1: Value1
+Header2:\t Value2
+Content-Length: 0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=7 span[header_field]="Header1"
+off=25 header_field complete
+off=26 len=6 span[header_value]="Value1"
+off=34 header_value complete
+off=34 len=7 span[header_field]="Header2"
+off=42 header_field complete
+off=44 len=6 span[header_value]="Value2"
+off=52 header_value complete
+off=52 len=14 span[header_field]="Content-Length"
+off=67 header_field complete
+off=68 len=1 span[header_value]="0"
+off=71 header_value complete
+off=73 headers complete status=200 v=1/1 flags=20 content_length=0
+off=73 message complete
+```
+
+## Error on invalid response start
+
+Every response must start with `HTTP/`.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTPER/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=4 error code=8 reason="Expected HTTP/"
+```
+
+## Empty body should not trigger spurious span callbacks
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=19 headers complete status=200 v=1/1 flags=0 content_length=0
+```
+
+## Google 301
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 301 Moved Permanently
+Location: http://www.google.com/
+Content-Type: text/html; charset=UTF-8
+Date: Sun, 26 Apr 2009 11:11:49 GMT
+Expires: Tue, 26 May 2009 11:11:49 GMT
+X-$PrototypeBI-Version: 1.6.0.3
+Cache-Control: public, max-age=2592000
+Server: gws
+Content-Length: 219
+
+<HTML><HEAD><meta http-equiv=content-type content=text/html;charset=utf-8>\n\
+<TITLE>301 Moved</TITLE></HEAD><BODY>\n\
+<H1>301 Moved</H1>\n\
+The document has moved\n\
+<A HREF="http://www.google.com/">here</A>.
+</BODY></HTML>
+```
+_(Note the `$` char in header field)_
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=17 span[status]="Moved Permanently"
+off=32 status complete
+off=32 len=8 span[header_field]="Location"
+off=41 header_field complete
+off=42 len=22 span[header_value]="http://www.google.com/"
+off=66 header_value complete
+off=66 len=12 span[header_field]="Content-Type"
+off=79 header_field complete
+off=80 len=24 span[header_value]="text/html; charset=UTF-8"
+off=106 header_value complete
+off=106 len=4 span[header_field]="Date"
+off=111 header_field complete
+off=112 len=29 span[header_value]="Sun, 26 Apr 2009 11:11:49 GMT"
+off=143 header_value complete
+off=143 len=7 span[header_field]="Expires"
+off=151 header_field complete
+off=152 len=29 span[header_value]="Tue, 26 May 2009 11:11:49 GMT"
+off=183 header_value complete
+off=183 len=22 span[header_field]="X-$PrototypeBI-Version"
+off=206 header_field complete
+off=207 len=7 span[header_value]="1.6.0.3"
+off=216 header_value complete
+off=216 len=13 span[header_field]="Cache-Control"
+off=230 header_field complete
+off=231 len=23 span[header_value]="public, max-age=2592000"
+off=256 header_value complete
+off=256 len=6 span[header_field]="Server"
+off=263 header_field complete
+off=264 len=3 span[header_value]="gws"
+off=269 header_value complete
+off=269 len=14 span[header_field]="Content-Length"
+off=284 header_field complete
+off=286 len=5 span[header_value]="219 "
+off=293 header_value complete
+off=295 headers complete status=301 v=1/1 flags=20 content_length=219
+off=295 len=74 span[body]="<HTML><HEAD><meta http-equiv=content-type content=text/html;charset=utf-8>"
+off=369 len=1 span[body]=lf
+off=370 len=37 span[body]="<TITLE>301 Moved</TITLE></HEAD><BODY>"
+off=407 len=1 span[body]=lf
+off=408 len=18 span[body]="<H1>301 Moved</H1>"
+off=426 len=1 span[body]=lf
+off=427 len=22 span[body]="The document has moved"
+off=449 len=1 span[body]=lf
+off=450 len=42 span[body]="<A HREF="http://www.google.com/">here</A>."
+off=492 len=1 span[body]=cr
+off=493 len=1 span[body]=lf
+off=494 len=14 span[body]="</BODY></HTML>"
+```
+
+## amazon.com
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 301 MovedPermanently
+Date: Wed, 15 May 2013 17:06:33 GMT
+Server: Server
+x-amz-id-1: 0GPHKXSJQ826RK7GZEB2
+p3p: policyref="http://www.amazon.com/w3c/p3p.xml",CP="CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC "
+x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD
+Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846
+Vary: Accept-Encoding,User-Agent
+Content-Type: text/html; charset=ISO-8859-1
+Transfer-Encoding: chunked
+
+1
+\n
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=16 span[status]="MovedPermanently"
+off=31 status complete
+off=31 len=4 span[header_field]="Date"
+off=36 header_field complete
+off=37 len=29 span[header_value]="Wed, 15 May 2013 17:06:33 GMT"
+off=68 header_value complete
+off=68 len=6 span[header_field]="Server"
+off=75 header_field complete
+off=76 len=6 span[header_value]="Server"
+off=84 header_value complete
+off=84 len=10 span[header_field]="x-amz-id-1"
+off=95 header_field complete
+off=96 len=20 span[header_value]="0GPHKXSJQ826RK7GZEB2"
+off=118 header_value complete
+off=118 len=3 span[header_field]="p3p"
+off=122 header_field complete
+off=123 len=178 span[header_value]="policyref="http://www.amazon.com/w3c/p3p.xml",CP="CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC ""
+off=303 header_value complete
+off=303 len=10 span[header_field]="x-amz-id-2"
+off=314 header_field complete
+off=315 len=64 span[header_value]="STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD"
+off=381 header_value complete
+off=381 len=8 span[header_field]="Location"
+off=390 header_field complete
+off=391 len=214 span[header_value]="http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846"
+off=607 header_value complete
+off=607 len=4 span[header_field]="Vary"
+off=612 header_field complete
+off=613 len=26 span[header_value]="Accept-Encoding,User-Agent"
+off=641 header_value complete
+off=641 len=12 span[header_field]="Content-Type"
+off=654 header_field complete
+off=655 len=29 span[header_value]="text/html; charset=ISO-8859-1"
+off=686 header_value complete
+off=686 len=17 span[header_field]="Transfer-Encoding"
+off=704 header_field complete
+off=705 len=7 span[header_value]="chunked"
+off=714 header_value complete
+off=716 headers complete status=301 v=1/1 flags=208 content_length=0
+off=719 chunk header len=1
+off=719 len=1 span[body]=lf
+off=722 chunk complete
+off=725 chunk header len=0
+off=727 chunk complete
+off=727 message complete
+```
+
+## No headers and no body
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 404 Not Found
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=9 span[status]="Not Found"
+off=24 status complete
+off=26 headers complete status=404 v=1/1 flags=0 content_length=0
+```
+
+## No reason phrase
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 301
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=14 status complete
+off=16 headers complete status=301 v=1/1 flags=0 content_length=0
+```
+
+## Empty reason phrase after space
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 \r\n\
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=0 span[status]=""
+off=15 status complete
+off=17 headers complete status=200 v=1/1 flags=0 content_length=0
+```
+
+## No carriage ret
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK\n\
+Content-Type: text/html; charset=utf-8\n\
+Connection: close\n\
+\n\
+these headers are from http://news.ycombinator.com/
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 error code=25 reason="Missing expected CR after response line"
+```
+
+## No carriage ret (lenient)
+
+<!-- meta={"type": "response-lenient-optional-cr-before-lf"} -->
+```http
+HTTP/1.1 200 OK\n\
+Content-Type: text/html; charset=utf-8\n\
+Connection: close\n\
+\n\
+these headers are from http://news.ycombinator.com/
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=16 status complete
+off=16 len=12 span[header_field]="Content-Type"
+off=29 header_field complete
+off=30 len=24 span[header_value]="text/html; charset=utf-8"
+off=55 header_value complete
+off=55 len=10 span[header_field]="Connection"
+off=66 header_field complete
+off=67 len=5 span[header_value]="close"
+off=73 header_value complete
+off=74 headers complete status=200 v=1/1 flags=2 content_length=0
+off=74 len=51 span[body]="these headers are from http://news.ycombinator.com/"
+```
+
+## Underscore in header key
+
+Shown by: `curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;"`
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Server: DCLK-AdSvr
+Content-Type: text/xml
+Content-Length: 0
+DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=6 span[header_field]="Server"
+off=24 header_field complete
+off=25 len=10 span[header_value]="DCLK-AdSvr"
+off=37 header_value complete
+off=37 len=12 span[header_field]="Content-Type"
+off=50 header_field complete
+off=51 len=8 span[header_value]="text/xml"
+off=61 header_value complete
+off=61 len=14 span[header_field]="Content-Length"
+off=76 header_field complete
+off=77 len=1 span[header_value]="0"
+off=80 header_value complete
+off=80 len=8 span[header_field]="DCLK_imp"
+off=89 header_field complete
+off=90 len=81 span[header_value]="v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o"
+off=173 header_value complete
+off=175 headers complete status=200 v=1/1 flags=20 content_length=0
+off=175 message complete
+```
+
+## bonjourmadame.fr
+
+The client should not merge two headers fields when the first one doesn't
+have a value.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.0 301 Moved Permanently
+Date: Thu, 03 Jun 2010 09:56:32 GMT
+Server: Apache/2.2.3 (Red Hat)
+Cache-Control: public
+Pragma: \r\n\
+Location: http://www.bonjourmadame.fr/
+Vary: Accept-Encoding
+Content-Length: 0
+Content-Type: text/html; charset=UTF-8
+Connection: keep-alive
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.0"
+off=8 version complete
+off=13 len=17 span[status]="Moved Permanently"
+off=32 status complete
+off=32 len=4 span[header_field]="Date"
+off=37 header_field complete
+off=38 len=29 span[header_value]="Thu, 03 Jun 2010 09:56:32 GMT"
+off=69 header_value complete
+off=69 len=6 span[header_field]="Server"
+off=76 header_field complete
+off=77 len=22 span[header_value]="Apache/2.2.3 (Red Hat)"
+off=101 header_value complete
+off=101 len=13 span[header_field]="Cache-Control"
+off=115 header_field complete
+off=116 len=6 span[header_value]="public"
+off=124 header_value complete
+off=124 len=6 span[header_field]="Pragma"
+off=131 header_field complete
+off=134 len=0 span[header_value]=""
+off=134 header_value complete
+off=134 len=8 span[header_field]="Location"
+off=143 header_field complete
+off=144 len=28 span[header_value]="http://www.bonjourmadame.fr/"
+off=174 header_value complete
+off=174 len=4 span[header_field]="Vary"
+off=179 header_field complete
+off=180 len=15 span[header_value]="Accept-Encoding"
+off=197 header_value complete
+off=197 len=14 span[header_field]="Content-Length"
+off=212 header_field complete
+off=213 len=1 span[header_value]="0"
+off=216 header_value complete
+off=216 len=12 span[header_field]="Content-Type"
+off=229 header_field complete
+off=230 len=24 span[header_value]="text/html; charset=UTF-8"
+off=256 header_value complete
+off=256 len=10 span[header_field]="Connection"
+off=267 header_field complete
+off=268 len=10 span[header_value]="keep-alive"
+off=280 header_value complete
+off=282 headers complete status=301 v=1/0 flags=21 content_length=0
+off=282 message complete
+```
+
+## Spaces in header value
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Date: Tue, 28 Sep 2010 01:14:13 GMT
+Server: Apache
+Cache-Control: no-cache, must-revalidate
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com
+Vary: Accept-Encoding
+_eep-Alive: timeout=45
+_onnection: Keep-Alive
+Transfer-Encoding: chunked
+Content-Type: text/html
+Connection: close
+
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Date"
+off=22 header_field complete
+off=23 len=29 span[header_value]="Tue, 28 Sep 2010 01:14:13 GMT"
+off=54 header_value complete
+off=54 len=6 span[header_field]="Server"
+off=61 header_field complete
+off=62 len=6 span[header_value]="Apache"
+off=70 header_value complete
+off=70 len=13 span[header_field]="Cache-Control"
+off=84 header_field complete
+off=85 len=25 span[header_value]="no-cache, must-revalidate"
+off=112 header_value complete
+off=112 len=7 span[header_field]="Expires"
+off=120 header_field complete
+off=121 len=29 span[header_value]="Mon, 26 Jul 1997 05:00:00 GMT"
+off=152 header_value complete
+off=152 len=10 span[header_field]=".et-Cookie"
+off=163 header_field complete
+off=164 len=54 span[header_value]="PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com"
+off=220 header_value complete
+off=220 len=4 span[header_field]="Vary"
+off=225 header_field complete
+off=226 len=15 span[header_value]="Accept-Encoding"
+off=243 header_value complete
+off=243 len=10 span[header_field]="_eep-Alive"
+off=254 header_field complete
+off=255 len=10 span[header_value]="timeout=45"
+off=267 header_value complete
+off=267 len=10 span[header_field]="_onnection"
+off=278 header_field complete
+off=279 len=10 span[header_value]="Keep-Alive"
+off=291 header_value complete
+off=291 len=17 span[header_field]="Transfer-Encoding"
+off=309 header_field complete
+off=310 len=7 span[header_value]="chunked"
+off=319 header_value complete
+off=319 len=12 span[header_field]="Content-Type"
+off=332 header_field complete
+off=333 len=9 span[header_value]="text/html"
+off=344 header_value complete
+off=344 len=10 span[header_field]="Connection"
+off=355 header_field complete
+off=356 len=5 span[header_value]="close"
+off=363 header_value complete
+off=365 headers complete status=200 v=1/1 flags=20a content_length=0
+off=368 chunk header len=0
+off=370 chunk complete
+off=370 message complete
+```
+
+## Spaces in header name
+
+<!-- meta={"type": "response", "noScan": true} -->
+```http
+HTTP/1.1 200 OK
+Server: Microsoft-IIS/6.0
+X-Powered-By: ASP.NET
+en-US Content-Type: text/xml
+Content-Type: text/xml
+Content-Length: 16
+Date: Fri, 23 Jul 2010 18:45:38 GMT
+Connection: keep-alive
+
+<xml>hello</xml>
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=6 span[header_field]="Server"
+off=24 header_field complete
+off=25 len=17 span[header_value]="Microsoft-IIS/6.0"
+off=44 header_value complete
+off=44 len=12 span[header_field]="X-Powered-By"
+off=57 header_field complete
+off=58 len=7 span[header_value]="ASP.NET"
+off=67 header_value complete
+off=72 error code=10 reason="Invalid header token"
+```
+
+## Non ASCII in status line
+
+<!-- meta={"type": "response", "noScan": true} -->
+```http
+HTTP/1.1 500 Oriƫntatieprobleem
+Date: Fri, 5 Nov 2010 23:07:12 GMT+2
+Content-Length: 0
+Connection: close
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=19 span[status]="Oriƫntatieprobleem"
+off=34 status complete
+off=34 len=4 span[header_field]="Date"
+off=39 header_field complete
+off=40 len=30 span[header_value]="Fri, 5 Nov 2010 23:07:12 GMT+2"
+off=72 header_value complete
+off=72 len=14 span[header_field]="Content-Length"
+off=87 header_field complete
+off=88 len=1 span[header_value]="0"
+off=91 header_value complete
+off=91 len=10 span[header_field]="Connection"
+off=102 header_field complete
+off=103 len=5 span[header_value]="close"
+off=110 header_value complete
+off=112 headers complete status=500 v=1/1 flags=22 content_length=0
+off=112 message complete
+```
+
+## HTTP version 0.9
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/0.9 200 OK
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="0.9"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=19 headers complete status=200 v=0/9 flags=0 content_length=0
+```
+
+## No Content-Length, no Transfer-Encoding
+
+The client should wait for the server's EOF. That is, when neither
+content-length nor transfer-encoding is specified, the end of body
+is specified by the EOF.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Content-Type: text/plain
+
+hello world
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=12 span[header_field]="Content-Type"
+off=30 header_field complete
+off=31 len=10 span[header_value]="text/plain"
+off=43 header_value complete
+off=45 headers complete status=200 v=1/1 flags=0 content_length=0
+off=45 len=11 span[body]="hello world"
+```
+
+## Response starting with CRLF
+
+<!-- meta={"type": "response"} -->
+```http
+\r\nHTTP/1.1 200 OK
+Header1: Value1
+Header2:\t Value2
+Content-Length: 0
+
+
+```
+
+```log
+off=2 message begin
+off=7 len=3 span[version]="1.1"
+off=10 version complete
+off=15 len=2 span[status]="OK"
+off=19 status complete
+off=19 len=7 span[header_field]="Header1"
+off=27 header_field complete
+off=28 len=6 span[header_value]="Value1"
+off=36 header_value complete
+off=36 len=7 span[header_field]="Header2"
+off=44 header_field complete
+off=46 len=6 span[header_value]="Value2"
+off=54 header_value complete
+off=54 len=14 span[header_field]="Content-Length"
+off=69 header_field complete
+off=70 len=1 span[header_value]="0"
+off=73 header_value complete
+off=75 headers complete status=200 v=1/1 flags=20 content_length=0
+off=75 message complete
+```
diff --git a/llhttp/test/response/transfer-encoding.md b/llhttp/test/response/transfer-encoding.md
new file mode 100644
index 0000000..e1fd10a
--- /dev/null
+++ b/llhttp/test/response/transfer-encoding.md
@@ -0,0 +1,410 @@
+Transfer-Encoding header
+========================
+
+## Trailing space on chunked body
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Content-Type: text/plain
+Transfer-Encoding: chunked
+
+25 \r\n\
+This is the data in the first chunk
+
+1C
+and this is the second one
+
+0 \r\n\
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=12 span[header_field]="Content-Type"
+off=30 header_field complete
+off=31 len=10 span[header_value]="text/plain"
+off=43 header_value complete
+off=43 len=17 span[header_field]="Transfer-Encoding"
+off=61 header_field complete
+off=62 len=7 span[header_value]="chunked"
+off=71 header_value complete
+off=73 headers complete status=200 v=1/1 flags=208 content_length=0
+off=76 error code=12 reason="Invalid character in chunk size"
+```
+
+## `chunked` before other transfer-encoding
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Accept: */*
+Transfer-Encoding: chunked, deflate
+
+World
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=6 span[header_field]="Accept"
+off=24 header_field complete
+off=25 len=3 span[header_value]="*/*"
+off=30 header_value complete
+off=30 len=17 span[header_field]="Transfer-Encoding"
+off=48 header_field complete
+off=49 len=16 span[header_value]="chunked, deflate"
+off=67 header_value complete
+off=69 headers complete status=200 v=1/1 flags=200 content_length=0
+off=69 len=5 span[body]="World"
+```
+
+## multiple transfer-encoding where chunked is not the last one
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Accept: */*
+Transfer-Encoding: chunked
+Transfer-Encoding: identity
+
+World
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=6 span[header_field]="Accept"
+off=24 header_field complete
+off=25 len=3 span[header_value]="*/*"
+off=30 header_value complete
+off=30 len=17 span[header_field]="Transfer-Encoding"
+off=48 header_field complete
+off=49 len=7 span[header_value]="chunked"
+off=58 header_value complete
+off=58 len=17 span[header_field]="Transfer-Encoding"
+off=76 header_field complete
+off=77 len=8 span[header_value]="identity"
+off=87 header_value complete
+off=89 headers complete status=200 v=1/1 flags=200 content_length=0
+off=89 len=5 span[body]="World"
+```
+
+## `chunkedchunked` transfer-encoding does not enable chunked enconding
+
+This check that the word `chunked` repeat more than once (with or without spaces) does not mistakenly enables chunked encoding.
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Accept: */*
+Transfer-Encoding: chunkedchunked
+
+2
+OK
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=6 span[header_field]="Accept"
+off=24 header_field complete
+off=25 len=3 span[header_value]="*/*"
+off=30 header_value complete
+off=30 len=17 span[header_field]="Transfer-Encoding"
+off=48 header_field complete
+off=49 len=14 span[header_value]="chunkedchunked"
+off=65 header_value complete
+off=67 headers complete status=200 v=1/1 flags=200 content_length=0
+off=67 len=1 span[body]="2"
+off=68 len=1 span[body]=cr
+off=69 len=1 span[body]=lf
+off=70 len=2 span[body]="OK"
+off=72 len=1 span[body]=cr
+off=73 len=1 span[body]=lf
+off=74 len=1 span[body]="0"
+off=75 len=1 span[body]=cr
+off=76 len=1 span[body]=lf
+off=77 len=1 span[body]=cr
+off=78 len=1 span[body]=lf
+```
+
+## Chunk extensions
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Host: localhost
+Transfer-encoding: chunked
+
+5;ilovew3;somuchlove=aretheseparametersfor
+hello
+6;blahblah;blah
+ world
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=9 span[header_value]="localhost"
+off=34 header_value complete
+off=34 len=17 span[header_field]="Transfer-encoding"
+off=52 header_field complete
+off=53 len=7 span[header_value]="chunked"
+off=62 header_value complete
+off=64 headers complete status=200 v=1/1 flags=208 content_length=0
+off=66 len=7 span[chunk_extension_name]="ilovew3"
+off=74 chunk_extension_name complete
+off=74 len=10 span[chunk_extension_name]="somuchlove"
+off=85 chunk_extension_name complete
+off=85 len=21 span[chunk_extension_value]="aretheseparametersfor"
+off=107 chunk_extension_value complete
+off=108 chunk header len=5
+off=108 len=5 span[body]="hello"
+off=115 chunk complete
+off=117 len=8 span[chunk_extension_name]="blahblah"
+off=126 chunk_extension_name complete
+off=126 len=4 span[chunk_extension_name]="blah"
+off=131 chunk_extension_name complete
+off=132 chunk header len=6
+off=132 len=6 span[body]=" world"
+off=140 chunk complete
+off=143 chunk header len=0
+off=145 chunk complete
+off=145 message complete
+```
+
+## No semicolon before chunk extensions
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Host: localhost
+Transfer-encoding: chunked
+
+2 erfrferferf
+aa
+0 rrrr
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=9 span[header_value]="localhost"
+off=34 header_value complete
+off=34 len=17 span[header_field]="Transfer-encoding"
+off=52 header_field complete
+off=53 len=7 span[header_value]="chunked"
+off=62 header_value complete
+off=64 headers complete status=200 v=1/1 flags=208 content_length=0
+off=66 error code=12 reason="Invalid character in chunk size"
+```
+
+
+## No extension after semicolon
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Host: localhost
+Transfer-encoding: chunked
+
+2;
+aa
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=9 span[header_value]="localhost"
+off=34 header_value complete
+off=34 len=17 span[header_field]="Transfer-encoding"
+off=52 header_field complete
+off=53 len=7 span[header_value]="chunked"
+off=62 header_value complete
+off=64 headers complete status=200 v=1/1 flags=208 content_length=0
+off=67 error code=2 reason="Invalid character in chunk extensions"
+```
+
+
+## Chunk extensions quoting
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Host: localhost
+Transfer-Encoding: chunked
+
+5;ilovew3="I love; extensions";somuchlove="aretheseparametersfor";blah;foo=bar
+hello
+6;blahblah;blah
+ world
+0
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=9 span[header_value]="localhost"
+off=34 header_value complete
+off=34 len=17 span[header_field]="Transfer-Encoding"
+off=52 header_field complete
+off=53 len=7 span[header_value]="chunked"
+off=62 header_value complete
+off=64 headers complete status=200 v=1/1 flags=208 content_length=0
+off=66 len=7 span[chunk_extension_name]="ilovew3"
+off=74 chunk_extension_name complete
+off=74 len=20 span[chunk_extension_value]=""I love; extensions""
+off=94 chunk_extension_value complete
+off=95 len=10 span[chunk_extension_name]="somuchlove"
+off=106 chunk_extension_name complete
+off=106 len=23 span[chunk_extension_value]=""aretheseparametersfor""
+off=129 chunk_extension_value complete
+off=130 len=4 span[chunk_extension_name]="blah"
+off=135 chunk_extension_name complete
+off=135 len=3 span[chunk_extension_name]="foo"
+off=139 chunk_extension_name complete
+off=139 len=3 span[chunk_extension_value]="bar"
+off=143 chunk_extension_value complete
+off=144 chunk header len=5
+off=144 len=5 span[body]="hello"
+off=151 chunk complete
+off=153 len=8 span[chunk_extension_name]="blahblah"
+off=162 chunk_extension_name complete
+off=162 len=4 span[chunk_extension_name]="blah"
+off=167 chunk_extension_name complete
+off=168 chunk header len=6
+off=168 len=6 span[body]=" world"
+off=176 chunk complete
+off=179 chunk header len=0
+```
+
+
+## Unbalanced chunk extensions quoting
+
+<!-- meta={"type": "response"} -->
+```http
+HTTP/1.1 200 OK
+Host: localhost
+Transfer-Encoding: chunked
+
+5;ilovew3="abc";somuchlove="def; ghi
+hello
+6;blahblah;blah
+ world
+0
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=4 span[header_field]="Host"
+off=22 header_field complete
+off=23 len=9 span[header_value]="localhost"
+off=34 header_value complete
+off=34 len=17 span[header_field]="Transfer-Encoding"
+off=52 header_field complete
+off=53 len=7 span[header_value]="chunked"
+off=62 header_value complete
+off=64 headers complete status=200 v=1/1 flags=208 content_length=0
+off=66 len=7 span[chunk_extension_name]="ilovew3"
+off=74 chunk_extension_name complete
+off=74 len=5 span[chunk_extension_value]=""abc""
+off=79 chunk_extension_value complete
+off=80 len=10 span[chunk_extension_name]="somuchlove"
+off=91 chunk_extension_name complete
+off=91 len=9 span[chunk_extension_value]=""def; ghi"
+off=101 error code=2 reason="Invalid character in chunk extensions quoted value"
+```
+
+
+## Invalid OBS fold after chunked value
+
+<!-- meta={"type": "response" } -->
+```http
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+ abc
+
+5
+World
+0
+
+
+```
+
+```log
+off=0 message begin
+off=5 len=3 span[version]="1.1"
+off=8 version complete
+off=13 len=2 span[status]="OK"
+off=17 status complete
+off=17 len=17 span[header_field]="Transfer-Encoding"
+off=35 header_field complete
+off=36 len=7 span[header_value]="chunked"
+off=45 len=5 span[header_value]=" abc"
+off=52 header_value complete
+off=54 headers complete status=200 v=1/1 flags=200 content_length=0
+off=54 len=1 span[body]="5"
+off=55 len=1 span[body]=cr
+off=56 len=1 span[body]=lf
+off=57 len=5 span[body]="World"
+off=62 len=1 span[body]=cr
+off=63 len=1 span[body]=lf
+off=64 len=1 span[body]="0"
+off=65 len=1 span[body]=cr
+off=66 len=1 span[body]=lf
+off=67 len=1 span[body]=cr
+off=68 len=1 span[body]=lf
+```
+
diff --git a/llhttp/test/url.md b/llhttp/test/url.md
new file mode 100644
index 0000000..13a1b01
--- /dev/null
+++ b/llhttp/test/url.md
@@ -0,0 +1,261 @@
+# URL tests
+
+## Absolute URL
+
+```url
+http://example.com/path?query=value#schema
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=11 span[url.host]="example.com"
+off=18 len=5 span[url.path]="/path"
+off=24 len=11 span[url.query]="query=value"
+off=36 len=6 span[url.fragment]="schema"
+```
+
+## Relative URL
+
+```url
+/path?query=value#schema
+```
+
+```log
+off=0 len=5 span[url.path]="/path"
+off=6 len=11 span[url.query]="query=value"
+off=18 len=6 span[url.fragment]="schema"
+```
+
+## Failing on broken schema
+
+<!-- meta={"noScan": true} -->
+```url
+schema:/path?query=value#schema
+```
+
+```log
+off=0 len=6 span[url.schema]="schema"
+off=8 error code=7 reason="Unexpected char in url schema"
+```
+
+## Proxy request
+
+```url
+http://hostname/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=8 span[url.host]="hostname"
+off=15 len=1 span[url.path]="/"
+```
+
+## Proxy request with port
+
+```url
+http://hostname:444/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=12 span[url.host]="hostname:444"
+off=19 len=1 span[url.path]="/"
+```
+
+## Proxy IPv6 request
+
+```url
+http://[1:2::3:4]/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=10 span[url.host]="[1:2::3:4]"
+off=17 len=1 span[url.path]="/"
+```
+
+## Proxy IPv6 request with port
+
+```url
+http://[1:2::3:4]:67/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=13 span[url.host]="[1:2::3:4]:67"
+off=20 len=1 span[url.path]="/"
+```
+
+## IPv4 in IPv6 address
+
+```url
+http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=39 span[url.host]="[2001:0000:0000:0000:0000:0000:1.9.1.1]"
+off=46 len=1 span[url.path]="/"
+```
+
+## Extra `?` in query string
+
+```url
+http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,\
+fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,\
+fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=10 span[url.host]="a.tbcdn.cn"
+off=17 len=12 span[url.path]="/p/fp/2010c/"
+off=30 len=187 span[url.query]="?fp-header-min.css,fp-base-min.css,fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css"
+```
+
+## URL encoded space
+
+```url
+/toto.html?toto=a%20b
+```
+
+```log
+off=0 len=10 span[url.path]="/toto.html"
+off=11 len=10 span[url.query]="toto=a%20b"
+```
+
+## URL fragment
+
+```url
+/toto.html#titi
+```
+
+```log
+off=0 len=10 span[url.path]="/toto.html"
+off=11 len=4 span[url.fragment]="titi"
+```
+
+## Complex URL fragment
+
+```url
+http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=\
+http://www.example.com/index.html?foo=bar&hello=world#midpage
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=22 span[url.host]="www.webmasterworld.com"
+off=29 len=6 span[url.path]="/r.cgi"
+off=36 len=69 span[url.query]="f=21&d=8405&url=http://www.example.com/index.html?foo=bar&hello=world"
+off=106 len=7 span[url.fragment]="midpage"
+```
+
+## Complex URL from node.js url parser doc
+
+```url
+http://host.com:8080/p/a/t/h?query=string#hash
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=13 span[url.host]="host.com:8080"
+off=20 len=8 span[url.path]="/p/a/t/h"
+off=29 len=12 span[url.query]="query=string"
+off=42 len=4 span[url.fragment]="hash"
+```
+
+## Complex URL with basic auth from node.js url parser doc
+
+```url
+http://a:b@host.com:8080/p/a/t/h?query=string#hash
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=17 span[url.host]="a:b@host.com:8080"
+off=24 len=8 span[url.path]="/p/a/t/h"
+off=33 len=12 span[url.query]="query=string"
+off=46 len=4 span[url.fragment]="hash"
+```
+
+## Double `@`
+
+<!-- meta={"noScan": true} -->
+```url
+http://a:b@@hostname:443/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=12 error code=7 reason="Double @ in url"
+```
+
+## Proxy basic auth with url encoded space
+
+```url
+http://a%20:b@host.com/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=15 span[url.host]="a%20:b@host.com"
+off=22 len=1 span[url.path]="/"
+```
+
+## Proxy basic auth with unreserved chars
+
+```url
+http://a!;-_!=+$@host.com/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=18 span[url.host]="a!;-_!=+$@host.com"
+off=25 len=1 span[url.path]="/"
+```
+
+## IPv6 address with Zone ID
+
+```url
+http://[fe80::a%25eth0]/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=16 span[url.host]="[fe80::a%25eth0]"
+off=23 len=1 span[url.path]="/"
+```
+
+## IPv6 address with Zone ID, but `%` is not percent-encoded
+
+```url
+http://[fe80::a%eth0]/
+```
+
+```log
+off=0 len=4 span[url.schema]="http"
+off=7 len=14 span[url.host]="[fe80::a%eth0]"
+off=21 len=1 span[url.path]="/"
+```
+
+## Disallow tab in URL
+
+<!-- meta={ "noScan": true} -->
+```url
+/foo\tbar/
+```
+
+```log
+off=5 error code=7 reason="Invalid characters in url"
+```
+
+## Disallow form-feed in URL
+
+<!-- meta={ "noScan": true} -->
+```url
+/foo\fbar/
+```
+
+```log
+off=5 error code=7 reason="Invalid characters in url"
+```
diff --git a/llhttp/tsconfig.json b/llhttp/tsconfig.json
new file mode 100644
index 0000000..01ec7c2
--- /dev/null
+++ b/llhttp/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "target": "es2017",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "outDir": "./lib",
+ "declaration": true,
+ "pretty": true,
+ "sourceMap": true
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
diff --git a/llhttp/tslint.json b/llhttp/tslint.json
new file mode 100644
index 0000000..b0aaf97
--- /dev/null
+++ b/llhttp/tslint.json
@@ -0,0 +1,14 @@
+{
+ "defaultSeverity": "error",
+ "extends": [
+ "tslint:recommended"
+ ],
+ "jsRules": {},
+ "rules": {
+ "no-bitwise": null,
+ "quotemark": [
+ true, "single", "avoid-escape", "avoid-template"
+ ]
+ },
+ "rulesDirectory": []
+}