├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── aiohttp.yml │ ├── ci.yaml │ ├── codeql.yml │ └── scorecards.yml ├── .gitignore ├── .npmrc ├── CMakeLists.txt ├── CNAME ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── bench └── index.ts ├── bin ├── build_wasm.ts └── generate.ts ├── docs └── releasing.md ├── eslint.config.mjs ├── examples └── wasm.ts ├── images ├── http-loose-none.png └── http-strict-none.png ├── libllhttp.pc.in ├── package-lock.json ├── package.json ├── src ├── common.gypi ├── llhttp.gyp ├── llhttp.ts ├── llhttp │ ├── c-headers.ts │ ├── constants.ts │ ├── http.ts │ ├── url.ts │ └── utils.ts └── native │ ├── api.c │ ├── api.h │ └── http.c ├── test ├── fixtures │ ├── extra.c │ └── index.ts ├── fuzzers │ └── fuzz_parser.c ├── md-test.ts ├── request │ ├── connection.md │ ├── content-length.md │ ├── finish.md │ ├── invalid.md │ ├── lenient-headers.md │ ├── lenient-version.md │ ├── method.md │ ├── pausing.md │ ├── pipelining.md │ ├── sample.md │ ├── transfer-encoding.md │ └── uri.md ├── response │ ├── connection.md │ ├── content-length.md │ ├── finish.md │ ├── invalid.md │ ├── lenient-version.md │ ├── pausing.md │ ├── pipelining.md │ ├── sample.md │ └── transfer-encoding.md └── url.md ├── tsconfig.json └── tsconfig.test.json /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !package.json 3 | !package-lock.json 4 | !tsconfig.json 5 | !tsconfig.base.json 6 | !bin 7 | !src 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: !!int ${{secrets.OPEN_PR_LIMIT}} 8 | 9 | - package-ecosystem: docker 10 | directory: / 11 | schedule: 12 | interval: weekly 13 | open-pull-requests-limit: !!int ${{secrets.OPEN_PR_LIMIT}} 14 | 15 | - package-ecosystem: npm 16 | directory: / 17 | schedule: 18 | interval: weekly 19 | open-pull-requests-limit: !!int ${{secrets.OPEN_PR_LIMIT}} 20 | -------------------------------------------------------------------------------- /.github/workflows/aiohttp.yml: -------------------------------------------------------------------------------- 1 | name: Aiohttp 2 | # If you don't understand the reason for a test failure, ping @Dreamsorcerer or open an issue in aio-libs/aiohttp. 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'main' 8 | pull_request: 9 | branches: 10 | - 'main' 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | permissions: 18 | contents: read # to fetch code (actions/checkout) 19 | 20 | name: Aiohttp regression tests 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout aiohttp 24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | repository: aio-libs/aiohttp 27 | - name: Checkout llhttp 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | path: vendor/llhttp 31 | - name: Restore node_modules cache 32 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 33 | with: 34 | path: vendor/llhttp/.npm 35 | key: ubuntu-latest-node-${{ hashFiles('vendor/llhttp/**/package-lock.json') }} 36 | restore-keys: ubuntu-latest-node- 37 | - name: Install llhttp dependencies 38 | run: npm ci --ignore-scripts 39 | working-directory: vendor/llhttp 40 | - name: Build llhttp 41 | run: make 42 | working-directory: vendor/llhttp 43 | - name: Setup Python 44 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 45 | with: 46 | python-version: 3.x 47 | cache: 'pip' 48 | cache-dependency-path: 'requirements/*.txt' 49 | - name: Provision the dev env 50 | run: >- 51 | PATH="${HOME}/.local/bin:${PATH}" 52 | make .develop 53 | - name: Run tests 54 | env: 55 | COLOR: yes 56 | run: >- 57 | PATH="${HOME}/.local/bin:${PATH}" 58 | pytest tests/test_http_parser.py tests/test_web_functional.py 59 | - name: Run dev_mode tests 60 | env: 61 | COLOR: yes 62 | PYTHONDEVMODE: 1 63 | run: >- 64 | PATH="${HOME}/.local/bin:${PATH}" 65 | pytest -m dev_mode tests/test_http_parser.py tests/test_web_functional.py 66 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CI: true 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | ci: 13 | name: Build, Test and Lint 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: 18 | - macos-latest 19 | - ubuntu-latest 20 | - windows-latest 21 | steps: 22 | - name: Install clang for Windows 23 | if: runner.os == 'Windows' 24 | run: | 25 | iwr -useb get.scoop.sh -outfile 'install.ps1' 26 | .\install.ps1 -RunAsAdmin 27 | scoop install llvm --global 28 | 29 | # Scoop modifies the PATH so we make the modified PATH global. 30 | echo $env:PATH >> $env:GITHUB_PATH 31 | 32 | - name: Setup Node.js 33 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 34 | with: 35 | node-version: 20.18.0 36 | 37 | - name: Setup Docker 38 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 39 | if: runner.os == 'Linux' 40 | 41 | - name: Fetch 42 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | with: 44 | fetch-depth: 1 45 | 46 | # Skip macOS & Windows, cache there is slower 47 | - name: Restore node_modules cache for Linux 48 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 49 | with: 50 | path: ~/.npm 51 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 52 | 53 | - name: Install dependencies 54 | run: npm ci 55 | 56 | - name: Build libllhttp.a 57 | shell: bash 58 | run: | 59 | make build/libllhttp.a 60 | 61 | - name: Build WebAssembly 62 | run: npm run build-wasm 63 | if: runner.os == 'Linux' 64 | 65 | - name: Run tests 66 | run: npm run test 67 | 68 | - name: Lint Code 69 | run: npm run lint 70 | if: runner.os == 'Linux' -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript", "typescript"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["main"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | 32 | steps: 33 | - name: "Checkout code" 34 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 35 | with: 36 | persist-credentials: false 37 | 38 | - name: "Run analysis" 39 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 40 | with: 41 | results_file: results.sarif 42 | results_format: sarif 43 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 44 | # - you want to enable the Branch-Protection check on a *public* repository, or 45 | # - you are installing Scorecards on a *private* repository 46 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 47 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 48 | 49 | # Public repositories: 50 | # - Publish results to OpenSSF REST API for easy access by consumers 51 | # - Allows the repository to include the Scorecard badge. 52 | # - See https://github.com/ossf/scorecard-action#publishing-results. 53 | # For private repositories: 54 | # - `publish_results` will always be set to `false`, regardless 55 | # of the value entered here. 56 | publish_results: true 57 | 58 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 59 | # format to the repository Actions tab. 60 | - name: "Upload artifact" 61 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 62 | with: 63 | name: SARIF file 64 | path: results.sarif 65 | retention-days: 5 66 | 67 | # Upload the results to GitHub's code scanning dashboard. 68 | - name: "Upload to code-scanning" 69 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 70 | with: 71 | sarif_file: results.sarif 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | test/tmp/ 4 | lib/ 5 | build/ 6 | release/ 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25.0) 2 | cmake_policy(SET CMP0069 NEW) 3 | 4 | project(llhttp VERSION _RELEASE_) 5 | include(GNUInstallDirs) 6 | 7 | set(CMAKE_C_STANDARD 99) 8 | 9 | # By default build in relwithdebinfo type, supports both lowercase and uppercase 10 | if(NOT CMAKE_CONFIGURATION_TYPES) 11 | set(allowableBuildTypes DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) 12 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${allowableBuildTypes}") 13 | if(NOT CMAKE_BUILD_TYPE) 14 | set(CMAKE_BUILD_TYPE RELWITHDEBINFO CACHE STRING "" FORCE) 15 | else() 16 | string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) 17 | if(NOT CMAKE_BUILD_TYPE IN_LIST allowableBuildTypes) 18 | message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}") 19 | endif() 20 | endif() 21 | endif() 22 | 23 | # 24 | # Options 25 | # 26 | # Generic option 27 | option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so)" ON) 28 | option(BUILD_STATIC_LIBS "Build static libraries (.lib/.a)" OFF) 29 | 30 | # Source code 31 | set(LLHTTP_SOURCES 32 | ${CMAKE_CURRENT_SOURCE_DIR}/src/llhttp.c 33 | ${CMAKE_CURRENT_SOURCE_DIR}/src/http.c 34 | ${CMAKE_CURRENT_SOURCE_DIR}/src/api.c 35 | ) 36 | 37 | set(LLHTTP_HEADERS 38 | ${CMAKE_CURRENT_SOURCE_DIR}/include/llhttp.h 39 | ) 40 | 41 | configure_file( 42 | ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc.in 43 | ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc 44 | @ONLY 45 | ) 46 | 47 | function(config_library target) 48 | target_sources(${target} PRIVATE ${LLHTTP_SOURCES} ${LLHTTP_HEADERS}) 49 | 50 | target_include_directories(${target} PUBLIC 51 | $ 52 | $ 53 | ) 54 | 55 | set_target_properties(${target} PROPERTIES 56 | OUTPUT_NAME llhttp 57 | VERSION ${PROJECT_VERSION} 58 | SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 59 | PUBLIC_HEADER ${LLHTTP_HEADERS} 60 | ) 61 | 62 | install(TARGETS ${target} 63 | EXPORT llhttp 64 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 65 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 66 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 67 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 68 | ) 69 | 70 | install(FILES 71 | ${CMAKE_CURRENT_SOURCE_DIR}/libllhttp.pc 72 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig 73 | ) 74 | 75 | # This is required to work with FetchContent 76 | install(EXPORT llhttp 77 | FILE llhttp-config.cmake 78 | NAMESPACE llhttp:: 79 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/llhttp 80 | ) 81 | endfunction(config_library target) 82 | 83 | if(BUILD_SHARED_LIBS) 84 | add_library(llhttp_shared SHARED 85 | ${llhttp_src} 86 | ) 87 | add_library(llhttp::llhttp ALIAS llhttp_shared) 88 | config_library(llhttp_shared) 89 | endif() 90 | 91 | if(BUILD_STATIC_LIBS) 92 | add_library(llhttp_static STATIC 93 | ${llhttp_src} 94 | ) 95 | if(NOT BUILD_SHARED_LIBS) 96 | add_library(llhttp::llhttp ALIAS llhttp_static) 97 | endif() 98 | config_library(llhttp_static) 99 | endif() 100 | 101 | # On windows with Visual Studio, add a debug postfix so that release 102 | # and debug libraries can coexist. 103 | if(MSVC) 104 | set(CMAKE_DEBUG_POSTFIX "d") 105 | endif() 106 | 107 | # Print project configure summary 108 | message(STATUS "") 109 | message(STATUS "") 110 | message(STATUS "Project configure summary:") 111 | message(STATUS "") 112 | message(STATUS " CMake build type .................: ${CMAKE_BUILD_TYPE}") 113 | message(STATUS " Install prefix ...................: ${CMAKE_INSTALL_PREFIX}") 114 | message(STATUS " Build shared library .............: ${BUILD_SHARED_LIBS}") 115 | message(STATUS " Build static library .............: ${BUILD_STATIC_LIBS}") 116 | message(STATUS "") 117 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | llhttp.org -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | * [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/main/CODE_OF_CONDUCT.md) 4 | * [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/main/Moderation-Policy.md) 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:24-alpine@sha256:91aa1bb6b5f57ec5109155332f4af2aa5d73ff7b4512c8e5dfce5dc88dbbae0e 2 | ARG UID=1000 3 | ARG GID=1000 4 | 5 | RUN apk add -U clang lld wasi-sdk && mkdir /home/node/llhttp 6 | 7 | WORKDIR /home/node/llhttp 8 | 9 | COPY . . 10 | 11 | RUN npm ci 12 | 13 | USER node 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright Fedor Indutny, 2018. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to permit 10 | persons to whom the Software is furnished to do so, subject to the 11 | following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 21 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 22 | USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CLANG ?= clang 2 | CFLAGS ?= 3 | OS ?= 4 | 5 | CFLAGS += -Os -g3 -Wall -Wextra -Wno-unused-parameter 6 | ifneq ($(OS),Windows_NT) 7 | # NOTE: clang on windows does not support fPIC 8 | CFLAGS += -fPIC 9 | endif 10 | 11 | INCLUDES += -Ibuild/ 12 | 13 | INSTALL ?= install 14 | PREFIX ?= /usr/local 15 | LIBDIR = $(PREFIX)/lib 16 | INCLUDEDIR = $(PREFIX)/include 17 | 18 | all: build/libllhttp.a build/libllhttp.so 19 | 20 | clean: 21 | rm -rf release/ 22 | rm -rf build/ 23 | 24 | build/libllhttp.so: build/c/llhttp.o build/native/api.o \ 25 | build/native/http.o 26 | $(CLANG) -shared $^ -o $@ 27 | 28 | build/libllhttp.a: build/c/llhttp.o build/native/api.o \ 29 | build/native/http.o 30 | $(AR) rcs $@ build/c/llhttp.o build/native/api.o build/native/http.o 31 | 32 | build/c/llhttp.o: build/c/llhttp.c 33 | $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@ 34 | 35 | build/native/%.o: src/native/%.c build/llhttp.h src/native/api.h \ 36 | build/native 37 | $(CLANG) $(CFLAGS) $(INCLUDES) -c $< -o $@ 38 | 39 | build/llhttp.h: generate 40 | build/c/llhttp.c: generate 41 | 42 | build/native: 43 | mkdir -p build/native 44 | 45 | release: clean generate 46 | @echo "${RELEASE}" | grep -q -E ".+" || { echo "Please make sure the RELEASE argument is set."; exit 1; } 47 | rm -rf release 48 | mkdir -p release/src 49 | mkdir -p release/include 50 | cp -rf build/llhttp.h release/include/ 51 | cp -rf build/c/llhttp.c release/src/ 52 | cp -rf src/native/*.c release/src/ 53 | cp -rf src/llhttp.gyp release/ 54 | cp -rf src/common.gypi release/ 55 | sed s/_RELEASE_/$(RELEASE)/ CMakeLists.txt > release/CMakeLists.txt 56 | cp -rf libllhttp.pc.in release/ 57 | cp -rf README.md release/ 58 | cp -rf LICENSE release/ 59 | 60 | github-release: 61 | @echo "${RELEASE_V}" | grep -q -E "^v" || { echo "Please make sure version starts with \"v\"."; exit 1; } 62 | gh release create -d --generate-notes ${RELEASE_V} 63 | @sleep 5 64 | gh release view ${RELEASE_V} -t "{{.body}}" --json body > RELEASE_NOTES 65 | gh release delete ${RELEASE_V} -y 66 | gh release create -F RELEASE_NOTES -d --title ${RELEASE_V} --target release release/${RELEASE_V} 67 | @sleep 5 68 | rm -rf RELEASE_NOTES 69 | open $$(gh release view release/${RELEASE_V} --json url -t "{{.url}}") 70 | 71 | postversion: release 72 | git fetch origin 73 | git push 74 | git checkout release -- 75 | cp -rf release/* ./ 76 | rm -rf release 77 | git add include src *.gyp *.gypi CMakeLists.txt README.md LICENSE libllhttp.pc.in 78 | git commit -a -m "release: $(RELEASE)" 79 | git tag "release/v$(RELEASE)" 80 | git push && git push --tags 81 | git checkout main 82 | 83 | generate: 84 | node --import tsx bin/generate.ts 85 | 86 | install: build/libllhttp.a build/libllhttp.so 87 | $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR) 88 | $(INSTALL) -d $(DESTDIR)$(LIBDIR) 89 | $(INSTALL) -C build/llhttp.h $(DESTDIR)$(INCLUDEDIR)/llhttp.h 90 | $(INSTALL) -C build/libllhttp.a $(DESTDIR)$(LIBDIR)/libllhttp.a 91 | $(INSTALL) build/libllhttp.so $(DESTDIR)$(LIBDIR)/libllhttp.so 92 | 93 | .PHONY: all generate clean release postversion github-release 94 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /bench/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @stylistic/max-len */ 2 | 3 | import assert from "assert"; 4 | import { spawnSync } from "child_process"; 5 | import { existsSync } from "fs"; 6 | import { resolve } from "path"; 7 | 8 | function request(tpl: TemplateStringsArray): string { 9 | return tpl.raw[0].replace(/^\s+/gm, '').replace(/\n/gm, '').replace(/\\r/gm, '\r').replace(/\\n/gm, '\n') 10 | } 11 | 12 | const urlExecutable = resolve(__dirname, "../test/tmp/url-url-c"); 13 | const httpExecutable = resolve(__dirname, "../test/tmp/http-request-c"); 14 | 15 | const httpRequests: Record = { 16 | "seanmonstar/httparse": request` 17 | GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n 18 | Host: www.kittyhell.com\r\n 19 | 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 20 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n 21 | Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n 22 | Accept-Encoding: gzip,deflate\r\n 23 | Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n 24 | Keep-Alive: 115\r\n 25 | Connection: keep-alive\r\n 26 | 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 27 | `, 28 | "nodejs/http-parser": request` 29 | POST /joyent/http-parser HTTP/1.1\r\n 30 | Host: github.com\r\n 31 | DNT: 1\r\n 32 | Accept-Encoding: gzip, deflate, sdch\r\n 33 | Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n 34 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) 35 | AppleWebKit/537.36 (KHTML, like Gecko) 36 | Chrome/39.0.2171.65 Safari/537.36\r\n 37 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9, 38 | image/webp,*/*;q=0.8\r\n 39 | Referer: https://github.com/joyent/http-parser\r\n 40 | Connection: keep-alive\r\n 41 | Transfer-Encoding: chunked\r\n 42 | Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n 43 | ` 44 | } 45 | const urlRequest = "http://example.com/path/to/file?query=value#fragment"; 46 | 47 | if (!existsSync(urlExecutable) || !existsSync(urlExecutable)) { 48 | console.error( 49 | "\x1b[31m\x1b[1mPlease run npm test in order to create required executables." 50 | ); 51 | process.exit(1); 52 | } 53 | 54 | if (process.argv[2] === "loop") { 55 | const reqName = process.argv[3]; 56 | const request = httpRequests[reqName]!; 57 | 58 | assert(request, `Unknown request name: "${reqName}"`); 59 | spawnSync(httpExecutable, [ "loop", request ], { stdio: "inherit" }); 60 | process.exit(0); 61 | } 62 | 63 | if (!process.argv[2] || process.argv[2] === "url") { 64 | console.log("url (C)"); 65 | spawnSync(urlExecutable, [ "bench", urlRequest ], { stdio: "inherit" }); 66 | } 67 | 68 | if (!process.argv[2] || process.argv[2] === "http") { 69 | for (const [ name, request ] of Object.entries(httpRequests)) { 70 | console.log('http: "%s" (C)', name); 71 | spawnSync(httpExecutable, [ "bench", request ], { stdio: "inherit" }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /bin/build_wasm.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { copyFileSync, mkdirSync } from 'fs'; 3 | import { join, resolve } from 'path'; 4 | 5 | let platform = process.env.WASM_PLATFORM ?? ''; 6 | const WASM_OUT = resolve(__dirname, '../build/wasm'); 7 | const WASM_SRC = resolve(__dirname, '../'); 8 | 9 | if (!platform && process.argv[2]) { 10 | platform = execSync('docker info -f "{{.OSType}}/{{.Architecture}}"').toString().trim(); 11 | } 12 | 13 | if (process.argv[2] === '--prebuild') { 14 | const cmd = `docker build --platform=${platform.toString().trim()} -t llhttp_wasm_builder .`; 15 | 16 | console.log(`> ${cmd}\n\n`); 17 | execSync(cmd, { stdio: 'inherit' }); 18 | 19 | process.exit(0); 20 | } 21 | 22 | if (process.argv[2] === '--setup') { 23 | try { 24 | mkdirSync(join(WASM_SRC, 'build')); 25 | process.exit(0); 26 | } catch (error: unknown) { 27 | if (isErrorWithCode(error) && error.code !== 'EEXIST') { 28 | throw error; 29 | } 30 | process.exit(0); 31 | } 32 | } 33 | 34 | if (process.argv[2] === '--docker') { 35 | let cmd = `docker run --rm --platform=${platform.toString().trim()}`; 36 | // Try to avoid root permission problems on compiled assets 37 | // when running on linux. 38 | // It will work flawessly if uid === gid === 1000 39 | // there will be some warnings otherwise. 40 | if (process.platform === 'linux') { 41 | cmd += ` --user ${process.getuid!()}:${process.getegid!()}`; 42 | } 43 | cmd += ` --mount type=bind,source=${WASM_SRC}/build,target=/home/node/llhttp/build llhttp_wasm_builder npm run wasm`; 44 | 45 | console.log(`> ${cmd}\n\n`); 46 | execSync(cmd, { cwd: WASM_SRC, stdio: 'inherit' }); 47 | process.exit(0); 48 | } 49 | 50 | try { 51 | mkdirSync(WASM_OUT); 52 | } catch (error: unknown) { 53 | if (isErrorWithCode(error) && error.code !== 'EEXIST') { 54 | throw error; 55 | } 56 | } 57 | 58 | // Build ts 59 | execSync('npm run build', { cwd: WASM_SRC, stdio: 'inherit' }); 60 | 61 | // Build wasm binary 62 | execSync( 63 | `clang \ 64 | --sysroot=/usr/share/wasi-sysroot \ 65 | -target wasm32-unknown-wasi \ 66 | -Ofast \ 67 | -fno-exceptions \ 68 | -fvisibility=hidden \ 69 | -mexec-model=reactor \ 70 | -Wl,-error-limit=0 \ 71 | -Wl,-O3 \ 72 | -Wl,--lto-O3 \ 73 | -Wl,--strip-all \ 74 | -Wl,--allow-undefined \ 75 | -Wl,--export-dynamic \ 76 | -Wl,--export-table \ 77 | -Wl,--export=malloc \ 78 | -Wl,--export=free \ 79 | -Wl,--no-entry \ 80 | ${join(WASM_SRC, 'build', 'c')}/*.c \ 81 | ${join(WASM_SRC, 'src', 'native')}/*.c \ 82 | -I${join(WASM_SRC, 'build')} \ 83 | -o ${join(WASM_OUT, 'llhttp.wasm')}`, 84 | { stdio: 'inherit' }, 85 | ); 86 | 87 | // Copy constants for `.js` and `.ts` users. 88 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.js'), join(WASM_OUT, 'constants.js')); 89 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.js.map'), join(WASM_OUT, 'constants.js.map')); 90 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'constants.d.ts'), join(WASM_OUT, 'constants.d.ts')); 91 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.js'), join(WASM_OUT, 'utils.js')); 92 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.js.map'), join(WASM_OUT, 'utils.js.map')); 93 | copyFileSync(join(WASM_SRC, 'lib', 'llhttp', 'utils.d.ts'), join(WASM_OUT, 'utils.d.ts')); 94 | 95 | function isErrorWithCode(error: unknown): error is Error & { code: string } { 96 | return typeof error === 'object' && error !== null && 'code' in error; 97 | } 98 | -------------------------------------------------------------------------------- /bin/generate.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --import tsx 2 | 3 | import { mkdirSync, readFileSync, writeFileSync } from 'fs'; 4 | import { LLParse } from 'llparse'; 5 | import { dirname, resolve } from 'path'; 6 | import { CHeaders, HTTP } from '../src/llhttp'; 7 | 8 | // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string 9 | // eslint-disable-next-line @stylistic/max-len 10 | const semverRE = /^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; 11 | 12 | const C_FILE = resolve(__dirname, '../build/c/llhttp.c'); 13 | const HEADER_FILE = resolve(__dirname, '../build/llhttp.h'); 14 | 15 | const pkg = JSON.parse( 16 | readFileSync(resolve(__dirname, '..', 'package.json')).toString(), 17 | ); 18 | 19 | const version = pkg.version.match(semverRE)?.groups; 20 | const llparse = new LLParse('llhttp__internal'); 21 | 22 | const cHeaders = new CHeaders(); 23 | const nativeHeaders = readFileSync(resolve(__dirname, '../src/native/api.h')); 24 | const generated = llparse.build(new HTTP(llparse).build().entry, { 25 | c: { 26 | header: 'llhttp', 27 | }, 28 | debug: process.env.LLPARSE_DEBUG ? 'llhttp__debug' : undefined, 29 | headerGuard: 'INCLUDE_LLHTTP_ITSELF_H_', 30 | }); 31 | 32 | const headers = ` 33 | #ifndef INCLUDE_LLHTTP_H_ 34 | #define INCLUDE_LLHTTP_H_ 35 | 36 | #define LLHTTP_VERSION_MAJOR ${version.major} 37 | #define LLHTTP_VERSION_MINOR ${version.minor} 38 | #define LLHTTP_VERSION_PATCH ${version.patch} 39 | 40 | ${generated.header} 41 | 42 | ${cHeaders.build()} 43 | 44 | ${nativeHeaders} 45 | 46 | #endif /* INCLUDE_LLHTTP_H_ */ 47 | `; 48 | 49 | mkdirSync(dirname(C_FILE), { recursive: true }); 50 | writeFileSync(HEADER_FILE, headers); 51 | writeFileSync(C_FILE, generated.c); 52 | -------------------------------------------------------------------------------- /docs/releasing.md: -------------------------------------------------------------------------------- 1 | # How to release a new version of llhttp 2 | 3 | ## What does releasing involves? 4 | 5 | These are the required steps to release a new version of llhttp: 6 | 7 | 1. Increase the version number. 8 | 2. Build it locally. 9 | 3. Create a new build and push it to GitHub. 10 | 4. Create a new release on GitHub release. 11 | 12 | > Do not try to execute the commands in the Makefile manually. This is really error-prone! 13 | 14 | ## Which commands to run? 15 | 16 | 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. 17 | 18 | As a preliminary check, lint the code, run the build command and execute the test suite locally: 19 | 20 | ``` 21 | npm run lint 22 | npm run build 23 | npm run test 24 | ``` 25 | 26 | If all goes good, you are ready to go! 27 | 28 | 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: 29 | 30 | ``` 31 | npm version [major|minor|patch] 32 | ``` 33 | 34 | The command will increase the version and then will create a new release branch on GitHub. 35 | 36 | > Even thought there is a package on NPM, it is not updated anymore. NEVER RUN `npm publish`! 37 | 38 | 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: 39 | 40 | ``` 41 | npm run github-release 42 | ``` 43 | 44 | This command will create a draft release on GitHub and then show it in your browser so you can review and publish it. 45 | 46 | Congratulation, you are all set! 47 | 48 | ## Create a GitHub release without GitHub CLI 49 | 50 | > From now on, `$VERSION` will be the new version you are trying to create, including the leading letter, for instance `v6.0.9`. 51 | 52 | If you don't want to or can't use GitHub CLI, you can still create the release on GitHub following this procedure. 53 | 54 | 1. Go on GitHub and start creating a new release which targets tag `$VERSION`. Generate the notes using the `Generate release notes` button. 55 | 56 | 2. At the bottom of the generated notes, make sure the previous and current version in the notes are correct. 57 | 58 | The last line should be something like this: `**Full Changelog**: https://github.com/nodejs/llhttp/compare/v6.0.8...v6.0.9` 59 | 60 | 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`. 61 | 62 | 3. Change the target of the release to point to tag `release/$VERSION`. 63 | 64 | 4. Review and then publish the release. 65 | 66 | Congratulation, you are all set! -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import stylistic from "@stylistic/eslint-plugin"; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | ...tseslint.configs.recommended, 10 | { 11 | ignores: ["build", "lib"], 12 | }, 13 | { 14 | files: [ 15 | "bin/**/*.ts", 16 | "bench/**/*.ts", 17 | "src/**/*.ts", 18 | "test/**/*.ts", 19 | "eslint.config.js", 20 | ], 21 | plugins: { 22 | "@stylistic": stylistic, 23 | }, 24 | languageOptions: { 25 | parser: tseslint.parser, 26 | parserOptions: { 27 | project: "./tsconfig.test.json", 28 | }, 29 | }, 30 | rules: { 31 | "@stylistic/max-len": [ 32 | 2, 33 | { 34 | code: 120, 35 | ignoreComments: true, 36 | }, 37 | ], 38 | "@stylistic/array-bracket-spacing": ["error", "always"], 39 | "@stylistic/operator-linebreak": ["error", "after"], 40 | "@stylistic/linebreak-style": ["error", "unix"], 41 | "@stylistic/brace-style": ["error", "1tbs", { allowSingleLine: true }], 42 | "@stylistic/indent": [ 43 | "error", 44 | 2, 45 | { 46 | SwitchCase: 1, 47 | FunctionDeclaration: { parameters: "first" }, 48 | FunctionExpression: { parameters: "first" }, 49 | }, 50 | ], 51 | }, 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /examples/wasm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A minimal Parser that mimicks a small fraction of the Node.js parser 3 | * API. 4 | * To run: 5 | * - `npm run build-wasm` 6 | * - `npx ts-node examples/wasm.ts` 7 | */ 8 | import { readFileSync } from 'fs'; 9 | import { resolve } from 'path'; 10 | import constants from '../build/wasm/constants'; 11 | 12 | const bin = readFileSync(resolve(__dirname, '../build/wasm/llhttp.wasm')); 13 | const mod = new WebAssembly.Module(bin); 14 | 15 | const REQUEST = constants.TYPE.REQUEST; 16 | const RESPONSE = constants.TYPE.RESPONSE; 17 | const kOnMessageBegin = 0; 18 | const kOnHeaders = 1; 19 | const kOnHeadersComplete = 2; 20 | const kOnBody = 3; 21 | const kOnMessageComplete = 4; 22 | const kOnExecute = 5; 23 | 24 | const kPtr = Symbol('kPtr'); 25 | const kUrl = Symbol('kUrl'); 26 | const kStatusMessage = Symbol('kStatusMessage'); 27 | const kHeadersFields = Symbol('kHeadersFields'); 28 | const kHeadersValues = Symbol('kHeadersValues'); 29 | const kBody = Symbol('kBody'); 30 | const kReset = Symbol('kReset'); 31 | const kCheckErr = Symbol('kCheckErr'); 32 | 33 | const cstr = (ptr: number, len: number): string => 34 | Buffer.from(memory.buffer, ptr, len).toString(); 35 | 36 | const wasm_on_message_begin = (p: number) => { 37 | const i = instMap.get(p); 38 | i[kReset](); 39 | return i[kOnMessageBegin](); 40 | }; 41 | 42 | const wasm_on_url = (p: number, at: number, length: number) => { 43 | instMap.get(p)[kUrl] = cstr(at, length); 44 | return 0; 45 | }; 46 | 47 | const wasm_on_status = (p: number, at: number, length: number) => { 48 | instMap.get(p)[kStatusMessage] = cstr(at, length); 49 | return 0; 50 | }; 51 | 52 | const wasm_on_header_field = (p: number, at: number, length: number) => { 53 | const i= instMap.get(p) 54 | i[kHeadersFields].push(cstr(at, length)); 55 | return 0; 56 | }; 57 | 58 | const wasm_on_header_value = (p: number, at: number, length: number) => { 59 | const i = instMap.get(p); 60 | i[kHeadersValues].push(cstr(at, length)); 61 | return 0; 62 | }; 63 | 64 | const wasm_on_headers_complete = (p: number) => { 65 | const i = instMap.get(p); 66 | const type = get_type(p); 67 | const versionMajor = get_version_major(p); 68 | const versionMinor = get_version_minor(p); 69 | const rawHeaders = []; 70 | let method; 71 | let url; 72 | let statusCode; 73 | let statusMessage; 74 | const upgrade = get_upgrade(p); 75 | const shouldKeepAlive = should_keep_alive(p); 76 | 77 | for (let c = 0; c < i[kHeadersFields].length; c++) { 78 | rawHeaders.push(i[kHeadersFields][c], i[kHeadersValues][c]) 79 | } 80 | 81 | if (type === HTTPParser.REQUEST) { 82 | method = constants.METHODS[get_method(p)]; 83 | url = i[kUrl]; 84 | } else if (type === HTTPParser.RESPONSE) { 85 | statusCode = get_status_code(p); 86 | statusMessage = i[kStatusMessage]; 87 | } 88 | return i[kOnHeadersComplete](versionMajor, versionMinor, rawHeaders, method, 89 | url, statusCode, statusMessage, upgrade, shouldKeepAlive); 90 | }; 91 | 92 | const wasm_on_body = (p: number, at: number, length: number) => { 93 | const i = instMap.get(p); 94 | const body = Buffer.from(memory.buffer, at, length); 95 | return i[kOnBody](body); 96 | }; 97 | 98 | const wasm_on_message_complete = (p: number) => { 99 | return instMap.get(p)[kOnMessageComplete](); 100 | }; 101 | 102 | const instMap = new Map(); 103 | 104 | const inst = new WebAssembly.Instance(mod, { 105 | env: { 106 | wasm_on_message_begin, 107 | wasm_on_url, 108 | wasm_on_status, 109 | wasm_on_header_field, 110 | wasm_on_header_value, 111 | wasm_on_headers_complete, 112 | wasm_on_body, 113 | wasm_on_message_complete, 114 | }, 115 | }); 116 | 117 | const memory = inst.exports.memory as WebAssembly.Memory; 118 | const alloc = inst.exports.llhttp_alloc as CallableFunction; 119 | const malloc = inst.exports.malloc as CallableFunction; 120 | const execute = inst.exports.llhttp_execute as CallableFunction; 121 | const get_type = inst.exports.llhttp_get_type as CallableFunction; 122 | const get_upgrade = inst.exports.llhttp_get_upgrade as CallableFunction; 123 | const should_keep_alive = inst.exports.llhttp_should_keep_alive as CallableFunction; 124 | const get_method = inst.exports.llhttp_get_method as CallableFunction; 125 | const get_status_code = inst.exports.llhttp_get_status_code as CallableFunction; 126 | const get_version_minor = inst.exports.llhttp_get_http_minor as CallableFunction; 127 | const get_version_major = inst.exports.llhttp_get_http_major as CallableFunction; 128 | const get_error_reason = inst.exports.llhttp_get_error_reason as CallableFunction; 129 | const free = inst.exports.free as CallableFunction; 130 | const initialize = inst.exports._initialize as CallableFunction; 131 | 132 | initialize(); // wasi reactor 133 | 134 | class HTTPParser { 135 | static REQUEST = REQUEST; 136 | static RESPONSE = RESPONSE; 137 | static kOnMessageBegin = kOnMessageBegin; 138 | static kOnHeaders = kOnHeaders; 139 | static kOnHeadersComplete = kOnHeadersComplete; 140 | static kOnBody = kOnBody; 141 | static kOnMessageComplete = kOnMessageComplete; 142 | static kOnExecute = kOnExecute; 143 | 144 | [kPtr]: number; 145 | [kUrl]: string; 146 | [kStatusMessage]: null|string; 147 | [kHeadersFields]: []|[string]; 148 | [kHeadersValues]: []|[string]; 149 | [kBody]: null|Buffer; 150 | 151 | constructor(type: constants.TYPE) { 152 | this[kPtr] = alloc(constants.TYPE[type]); 153 | instMap.set(this[kPtr], this); 154 | 155 | this[kUrl] = ''; 156 | this[kStatusMessage] = null; 157 | this[kHeadersFields] = []; 158 | this[kHeadersValues] = []; 159 | this[kBody] = null; 160 | } 161 | 162 | [kReset]() { 163 | this[kUrl] = ''; 164 | this[kStatusMessage] = null; 165 | this[kHeadersFields] = []; 166 | this[kHeadersValues] = []; 167 | this[kBody] = null; 168 | } 169 | 170 | [kOnMessageBegin]() { 171 | return 0; 172 | } 173 | 174 | /* eslint-disable @typescript-eslint/no-unused-vars */ 175 | [kOnHeaders](rawHeaders: [string]) {} 176 | 177 | [kOnHeadersComplete](versionMajor: number, versionMinor: number, rawHeaders: [string], method: string, 178 | url: string, statusCode: number, statusMessage: string, upgrade: boolean, shouldKeepAlive: boolean) { 179 | return 0; 180 | } 181 | /* eslint-enable @typescript-eslint/no-unused-vars */ 182 | 183 | [kOnBody](body: Buffer) { 184 | this[kBody] = body; 185 | return 0; 186 | } 187 | 188 | [kOnMessageComplete]() { 189 | return 0; 190 | } 191 | 192 | destroy() { 193 | instMap.delete(this[kPtr]); 194 | free(this[kPtr]); 195 | } 196 | 197 | execute(data: Buffer) { 198 | const ptr = malloc(data.byteLength); 199 | const u8 = new Uint8Array(memory.buffer); 200 | u8.set(data, ptr); 201 | const ret = execute(this[kPtr], ptr, data.length); 202 | free(ptr); 203 | this[kCheckErr](ret); 204 | return ret; 205 | } 206 | 207 | [kCheckErr](n: number) { 208 | if (n === constants.ERROR.OK) { 209 | return; 210 | } 211 | const ptr = get_error_reason(this[kPtr]); 212 | const u8 = new Uint8Array(memory.buffer); 213 | const len = u8.indexOf(0, ptr) - ptr; 214 | throw new Error(cstr(ptr, len)); 215 | } 216 | } 217 | 218 | 219 | { 220 | const p = new HTTPParser(HTTPParser.REQUEST); 221 | 222 | p.execute(Buffer.from([ 223 | 'POST /owo HTTP/1.1', 224 | 'X: Y', 225 | 'Content-Length: 9', 226 | '', 227 | 'uh, meow?', 228 | '', 229 | ].join('\r\n'))); 230 | 231 | console.log(p); 232 | 233 | p.destroy(); 234 | } 235 | 236 | { 237 | const p = new HTTPParser(HTTPParser.RESPONSE); 238 | 239 | p.execute(Buffer.from([ 240 | 'HTTP/1.1 200 OK', 241 | 'X: Y', 242 | 'Content-Length: 9', 243 | '', 244 | 'uh, meow?' 245 | ].join('\r\n'))); 246 | 247 | console.log(p); 248 | 249 | p.destroy(); 250 | } 251 | -------------------------------------------------------------------------------- /images/http-loose-none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodejs/llhttp/eaa71735458a020614b3898d8d97fc9091399594/images/http-loose-none.png -------------------------------------------------------------------------------- /images/http-strict-none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodejs/llhttp/eaa71735458a020614b3898d8d97fc9091399594/images/http-strict-none.png -------------------------------------------------------------------------------- /libllhttp.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_FULL_BINDIR@ 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: libllhttp 7 | Description: Node.js llhttp Library 8 | Version: @PROJECT_VERSION@ 9 | Libs: -L${libdir} -lllhttp 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llhttp", 3 | "version": "9.3.0", 4 | "description": "HTTP parser in LLVM IR", 5 | "main": "lib/llhttp.js", 6 | "types": "lib/llhttp.d.ts", 7 | "files": [ 8 | "lib", 9 | "src" 10 | ], 11 | "scripts": { 12 | "bench": "node --import tsx bench/index.ts", 13 | "build": "node --import tsx bin/generate.ts", 14 | "build-ts": "tsc", 15 | "prebuild-wasm": "npm run wasm -- --prebuild && npm run wasm -- --setup", 16 | "build-wasm": "npm run wasm -- --docker", 17 | "wasm": "node --import tsx bin/build_wasm.ts", 18 | "clean": "rm -rf lib && rm -rf test/tmp", 19 | "prepare": "npm run clean && npm run build-ts", 20 | "test": "node --import tsx ./test/md-test.ts", 21 | "lint": "eslint", 22 | "lint-fix": "eslint --fix", 23 | "postversion": "RELEASE=`node -e \"process.stdout.write(require('./package').version)\"` make -B postversion", 24 | "github-release": "RELEASE_V=`node -e \"process.stdout.write('v' + require('./package').version)\"` make github-release" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+ssh://git@github.com/nodejs/llhttp.git" 29 | }, 30 | "keywords": [ 31 | "http", 32 | "llvm", 33 | "ir", 34 | "llparse" 35 | ], 36 | "author": "Fedor Indutny (http://darksi.de/)", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/nodejs/llhttp/issues" 40 | }, 41 | "homepage": "https://github.com/nodejs/llhttp#readme", 42 | "devDependencies": { 43 | "@eslint/js": "^9.18.0", 44 | "@stylistic/eslint-plugin": "^4.1.0", 45 | "@types/node": "^22.10.5", 46 | "eslint": "^9.18.0", 47 | "llparse-dot": "^1.0.1", 48 | "llparse-test-fixture": "^5.0.2", 49 | "mdgator": "^1.1.2", 50 | "tsx": "^4.19.2", 51 | "typescript": "^5.7.3", 52 | "typescript-eslint": "^8.19.1" 53 | }, 54 | "dependencies": { 55 | "llparse": "^7.3.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common.gypi: -------------------------------------------------------------------------------- 1 | { 2 | 'target_defaults': { 3 | 'default_configuration': 'Debug', 4 | 'configurations': { 5 | # TODO: hoist these out and put them somewhere common, because 6 | # RuntimeLibrary MUST MATCH across the entire project 7 | 'Debug': { 8 | 'defines': [ 'DEBUG', '_DEBUG' ], 9 | 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], 10 | 'msvs_settings': { 11 | 'VCCLCompilerTool': { 12 | 'RuntimeLibrary': 1, # static debug 13 | }, 14 | }, 15 | }, 16 | 'Release': { 17 | 'defines': [ 'NDEBUG' ], 18 | 'cflags': [ '-Wall', '-Wextra', '-O3' ], 19 | 'msvs_settings': { 20 | 'VCCLCompilerTool': { 21 | 'RuntimeLibrary': 0, # static release 22 | }, 23 | }, 24 | } 25 | }, 26 | 'msvs_settings': { 27 | 'VCCLCompilerTool': { 28 | # Compile as C++. llhttp.c is actually C99, but C++ is 29 | # close enough in this case. 30 | 'CompileAs': 2, 31 | }, 32 | 'VCLibrarianTool': { 33 | }, 34 | 'VCLinkerTool': { 35 | 'GenerateDebugInformation': 'true', 36 | }, 37 | }, 38 | 'conditions': [ 39 | ['OS == "win"', { 40 | 'defines': [ 41 | 'WIN32' 42 | ], 43 | }] 44 | ], 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /src/llhttp.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'variables': { 3 | 'llhttp_sources': [ 4 | 'src/llhttp.c', 5 | 'src/api.c', 6 | 'src/http.c', 7 | ] 8 | }, 9 | 'targets': [ 10 | { 11 | 'target_name': 'llhttp', 12 | 'type': 'static_library', 13 | 'include_dirs': [ '.', 'include' ], 14 | 'direct_dependent_settings': { 15 | 'include_dirs': [ 'include' ], 16 | }, 17 | 'sources': [ 18 | '<@(llhttp_sources)', 19 | ], 20 | }, 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/llhttp.ts: -------------------------------------------------------------------------------- 1 | import constants from './llhttp/constants'; 2 | 3 | import { HTTP } from './llhttp/http'; 4 | import { URL } from './llhttp/url'; 5 | import { CHeaders } from './llhttp/c-headers'; 6 | 7 | export { 8 | constants, 9 | HTTP, 10 | URL, 11 | CHeaders, 12 | }; 13 | 14 | export default { 15 | constants, 16 | HTTP, 17 | URL, 18 | CHeaders, 19 | } 20 | -------------------------------------------------------------------------------- /src/llhttp/c-headers.ts: -------------------------------------------------------------------------------- 1 | import constants from './constants'; 2 | import type { IntDict } from './constants'; 3 | import { enumToMap } from './utils'; 4 | 5 | type Encoding = 'none' | 'hex'; 6 | 7 | export class CHeaders { 8 | public build(): string { 9 | let res = ''; 10 | 11 | res += '#ifndef LLLLHTTP_C_HEADERS_\n'; 12 | res += '#define LLLLHTTP_C_HEADERS_\n'; 13 | 14 | res += '#ifdef __cplusplus\n'; 15 | res += 'extern "C" {\n'; 16 | res += '#endif\n'; 17 | 18 | res += '\n'; 19 | 20 | const errorMap = enumToMap(constants.ERROR); 21 | const methodMap = enumToMap(constants.METHODS); 22 | const httpMethodMap = enumToMap(constants.METHODS, constants.METHODS_HTTP, [ 23 | constants.METHODS.PRI, 24 | ]); 25 | const rtspMethodMap = enumToMap(constants.METHODS, constants.METHODS_RTSP); 26 | const statusMap = enumToMap(constants.STATUSES, constants.STATUSES_HTTP); 27 | 28 | res += this.buildEnum('llhttp_errno', 'HPE', errorMap); 29 | res += '\n'; 30 | res += this.buildEnum('llhttp_flags', 'F', enumToMap(constants.FLAGS), 31 | 'hex'); 32 | res += '\n'; 33 | res += this.buildEnum('llhttp_lenient_flags', 'LENIENT', 34 | enumToMap(constants.LENIENT_FLAGS), 'hex'); 35 | res += '\n'; 36 | res += this.buildEnum('llhttp_type', 'HTTP', 37 | enumToMap(constants.TYPE)); 38 | res += '\n'; 39 | res += this.buildEnum('llhttp_finish', 'HTTP_FINISH', 40 | enumToMap(constants.FINISH)); 41 | res += '\n'; 42 | res += this.buildEnum('llhttp_method', 'HTTP', methodMap); 43 | res += '\n'; 44 | res += this.buildEnum('llhttp_status', 'HTTP_STATUS', statusMap); 45 | 46 | res += '\n'; 47 | 48 | res += this.buildMap('HTTP_ERRNO', errorMap); 49 | res += '\n'; 50 | res += this.buildMap('HTTP_METHOD', httpMethodMap); 51 | res += '\n'; 52 | res += this.buildMap('RTSP_METHOD', rtspMethodMap); 53 | res += '\n'; 54 | res += this.buildMap('HTTP_ALL_METHOD', methodMap); 55 | res += '\n'; 56 | res += this.buildMap('HTTP_STATUS', statusMap); 57 | 58 | res += '\n'; 59 | 60 | res += '#ifdef __cplusplus\n'; 61 | res += '} /* extern "C" */\n'; 62 | res += '#endif\n'; 63 | res += '#endif /* LLLLHTTP_C_HEADERS_ */\n'; 64 | 65 | return res; 66 | } 67 | 68 | private buildEnum(name: string, prefix: string, map: IntDict, 69 | encoding: Encoding = 'none'): string { 70 | let res = ''; 71 | 72 | res += `enum ${name} {\n`; 73 | const keys = Object.keys(map); 74 | const keysLength = keys.length; 75 | for (let i = 0; i < keysLength; i++) { 76 | const key = keys[i]; 77 | const isLast = i === keysLength - 1; 78 | 79 | let value: number | string = map[key]; 80 | 81 | if (encoding === 'hex') { 82 | value = `0x${value.toString(16)}`; 83 | } 84 | 85 | res += ` ${prefix}_${key.replace(/-/g, '')} = ${value}`; 86 | if (!isLast) { 87 | res += ',\n'; 88 | } 89 | } 90 | res += '\n};\n'; 91 | res += `typedef enum ${name} ${name}_t;\n`; 92 | 93 | return res; 94 | } 95 | 96 | private buildMap(name: string, map: IntDict): string { 97 | let res = ''; 98 | 99 | res += `#define ${name}_MAP(XX) \\\n`; 100 | for (const [ key, value ] of Object.entries(map)) { 101 | res += ` XX(${value}, ${key.replace(/-/g, '')}, ${key}) \\\n`; 102 | } 103 | res += '\n'; 104 | 105 | return res; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/llhttp/url.ts: -------------------------------------------------------------------------------- 1 | import { type LLParse, type source } from 'llparse'; 2 | 3 | type Node = source.node.Node; 4 | type Match = source.node.Match; 5 | 6 | import { 7 | ALPHA, 8 | type CharList, 9 | ERROR, 10 | URL_CHAR, 11 | USERINFO_CHARS, 12 | } from './constants'; 13 | 14 | type SpanName = 'schema' | 'host' | 'path' | 'query' | 'fragment' | 'url'; 15 | 16 | export interface IURLResult { 17 | readonly entry: { 18 | readonly normal: Node; 19 | readonly connect: Node; 20 | }; 21 | readonly exit: { 22 | readonly toHTTP: Node; 23 | readonly toHTTP09: Node; 24 | }; 25 | } 26 | 27 | type SpanTable = Map; 28 | 29 | export class URL { 30 | private readonly spanTable: SpanTable = new Map(); 31 | private readonly errorInvalid: Node; 32 | private readonly URL_CHAR: CharList; 33 | private readonly llparse: LLParse; 34 | 35 | constructor(llparse: LLParse, separateSpans = false) { 36 | this.llparse = llparse; 37 | const p = this.llparse; 38 | 39 | this.errorInvalid = p.error(ERROR.INVALID_URL, 'Invalid characters in url'); 40 | 41 | this.URL_CHAR = URL_CHAR; 42 | 43 | const table = this.spanTable; 44 | if (separateSpans) { 45 | table.set('schema', p.span(p.code.span('llhttp__on_url_schema'))); 46 | table.set('host', p.span(p.code.span('llhttp__on_url_host'))); 47 | table.set('path', p.span(p.code.span('llhttp__on_url_path'))); 48 | table.set('query', p.span(p.code.span('llhttp__on_url_query'))); 49 | table.set('fragment', 50 | p.span(p.code.span('llhttp__on_url_fragment'))); 51 | } else { 52 | table.set('url', p.span(p.code.span('llhttp__on_url'))); 53 | } 54 | } 55 | 56 | public build(): IURLResult { 57 | const p = this.llparse; 58 | 59 | const entry = { 60 | connect: this.node('entry_connect'), 61 | normal: this.node('entry_normal'), 62 | }; 63 | 64 | const start = this.node('start'); 65 | const path = this.node('path'); 66 | const queryOrFragment = this.node('query_or_fragment'); 67 | const schema = this.node('schema'); 68 | const schemaDelim = this.node('schema_delim'); 69 | const server = this.node('server'); 70 | const queryStart = this.node('query_start'); 71 | const query = this.node('query'); 72 | const fragment = this.node('fragment'); 73 | const serverWithAt = this.node('server_with_at'); 74 | 75 | entry.normal 76 | .otherwise(this.spanStart('url', start)); 77 | 78 | entry.connect 79 | .otherwise(this.spanStart('url', this.spanStart('host', server))); 80 | 81 | start 82 | .peek([ '/', '*' ], this.spanStart('path').skipTo(path)) 83 | .peek(ALPHA, this.spanStart('schema', schema)) 84 | .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected start char in url')); 85 | 86 | schema 87 | .match(ALPHA, schema) 88 | .peek(':', this.spanEnd('schema').skipTo(schemaDelim)) 89 | .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url schema')); 90 | 91 | schemaDelim 92 | .match('//', this.spanStart('host', server)) 93 | .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url schema')); 94 | 95 | for (const node of [ server, serverWithAt ]) { 96 | node 97 | .peek('/', this.spanEnd('host', this.spanStart('path').skipTo(path))) 98 | .match('?', this.spanEnd('host', this.spanStart('query', query))) 99 | .match(USERINFO_CHARS, server) 100 | .match([ '[', ']' ], server) 101 | .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url server')); 102 | 103 | if (node !== serverWithAt) { 104 | node.match('@', serverWithAt); 105 | } 106 | } 107 | 108 | serverWithAt 109 | .match('@', p.error(ERROR.INVALID_URL, 'Double @ in url')); 110 | 111 | path 112 | .match(this.URL_CHAR, path) 113 | .otherwise(this.spanEnd('path', queryOrFragment)); 114 | 115 | // Performance optimization, split `path` so that the fast case remains 116 | // there 117 | queryOrFragment 118 | .match('?', this.spanStart('query', query)) 119 | .match('#', this.spanStart('fragment', fragment)) 120 | .otherwise(p.error(ERROR.INVALID_URL, 'Invalid char in url path')); 121 | 122 | query 123 | .match(this.URL_CHAR, query) 124 | // Allow extra '?' in query string 125 | .match('?', query) 126 | .peek('#', this.spanEnd('query') 127 | .skipTo(this.spanStart('fragment', fragment))) 128 | .otherwise(p.error(ERROR.INVALID_URL, 'Invalid char in url query')); 129 | 130 | fragment 131 | .match(this.URL_CHAR, fragment) 132 | .match([ '?', '#' ], fragment) 133 | .otherwise( 134 | p.error(ERROR.INVALID_URL, 'Invalid char in url fragment start')); 135 | 136 | for (const node of [ start, schema, schemaDelim ]) { 137 | /* No whitespace allowed here */ 138 | node.match([ ' ', '\r', '\n' ], this.errorInvalid); 139 | } 140 | 141 | // Adaptors 142 | const toHTTP = this.node('to_http'); 143 | const toHTTP09 = this.node('to_http_09'); 144 | 145 | const skipToHTTP = this.node('skip_to_http') 146 | .skipTo(toHTTP); 147 | 148 | const skipToHTTP09 = this.node('skip_to_http09') 149 | .skipTo(toHTTP09); 150 | 151 | const skipCRLF = this.node('skip_lf_to_http09') 152 | .match('\r\n', toHTTP09) 153 | .otherwise(p.error(ERROR.INVALID_URL, 'Expected CRLF')); 154 | 155 | for (const node of [ server, serverWithAt, queryOrFragment, queryStart, query, fragment ]) { 156 | let spanName: SpanName | undefined; 157 | 158 | if (node === server || node === serverWithAt) { 159 | spanName = 'host'; 160 | } else if (node === queryStart || node === query) { 161 | spanName = 'query'; 162 | } else if (node === fragment) { 163 | spanName = 'fragment'; 164 | } 165 | 166 | const endTo = (target: Node): Node => { 167 | let res: Node = this.spanEnd('url', target); 168 | if (spanName !== undefined) { 169 | res = this.spanEnd(spanName, res); 170 | } 171 | return res; 172 | }; 173 | 174 | node.peek(' ', endTo(skipToHTTP)); 175 | 176 | node.peek('\r', endTo(skipCRLF)); 177 | node.peek('\n', endTo(skipToHTTP09)); 178 | } 179 | 180 | return { 181 | entry, 182 | exit: { 183 | toHTTP, 184 | toHTTP09, 185 | }, 186 | }; 187 | } 188 | 189 | private spanStart(name: SpanName, otherwise?: Node): Node { 190 | let res: Node; 191 | if (this.spanTable.has(name)) { 192 | res = this.spanTable.get(name)!.start(); 193 | } else { 194 | res = this.llparse.node('span_start_stub_' + name); 195 | } 196 | if (otherwise !== undefined) { 197 | res.otherwise(otherwise); 198 | } 199 | return res; 200 | } 201 | 202 | private spanEnd(name: SpanName, otherwise?: Node): Node { 203 | let res: Node; 204 | if (this.spanTable.has(name)) { 205 | res = this.spanTable.get(name)!.end(); 206 | } else { 207 | res = this.llparse.node('span_end_stub_' + name); 208 | } 209 | if (otherwise !== undefined) { 210 | res.otherwise(otherwise); 211 | } 212 | return res; 213 | } 214 | 215 | private node(name: string): Match { 216 | const res = this.llparse.node('url_' + name); 217 | 218 | res.match([ '\t', '\f' ], this.errorInvalid); 219 | 220 | return res; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/llhttp/utils.ts: -------------------------------------------------------------------------------- 1 | import type { IntDict } from './constants'; 2 | 3 | export function enumToMap( 4 | obj: IntDict, 5 | filter: readonly number[] = [], 6 | exceptions: readonly number[] = [], 7 | ): IntDict { 8 | const emptyFilter = (filter?.length ?? 0) === 0; 9 | const emptyExceptions = (exceptions?.length ?? 0) === 0; 10 | 11 | return Object.fromEntries(Object.entries(obj).filter(([ , value ]) => { 12 | return ( 13 | typeof value === 'number' && 14 | (emptyFilter || filter.includes(value)) && 15 | (emptyExceptions || !exceptions.includes(value)) 16 | ); 17 | })); 18 | } 19 | -------------------------------------------------------------------------------- /src/native/api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "llhttp.h" 6 | 7 | #define CALLBACK_MAYBE(PARSER, NAME) \ 8 | do { \ 9 | const llhttp_settings_t* settings; \ 10 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 11 | if (settings == NULL || settings->NAME == NULL) { \ 12 | err = 0; \ 13 | break; \ 14 | } \ 15 | err = settings->NAME((PARSER)); \ 16 | } while (0) 17 | 18 | #define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ 19 | do { \ 20 | const llhttp_settings_t* settings; \ 21 | settings = (const llhttp_settings_t*) (PARSER)->settings; \ 22 | if (settings == NULL || settings->NAME == NULL) { \ 23 | err = 0; \ 24 | break; \ 25 | } \ 26 | err = settings->NAME((PARSER), (START), (LEN)); \ 27 | if (err == -1) { \ 28 | err = HPE_USER; \ 29 | llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ 30 | } \ 31 | } while (0) 32 | 33 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 34 | const llhttp_settings_t* settings) { 35 | llhttp__internal_init(parser); 36 | 37 | parser->type = type; 38 | parser->settings = (void*) settings; 39 | } 40 | 41 | 42 | #if defined(__wasm__) 43 | 44 | extern int wasm_on_message_begin(llhttp_t * p); 45 | extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); 46 | extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); 47 | extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); 48 | extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); 49 | extern int wasm_on_headers_complete(llhttp_t * p, int status_code, 50 | uint8_t upgrade, int should_keep_alive); 51 | extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); 52 | extern int wasm_on_message_complete(llhttp_t * p); 53 | 54 | static int wasm_on_headers_complete_wrap(llhttp_t* p) { 55 | return wasm_on_headers_complete(p, p->status_code, p->upgrade, 56 | llhttp_should_keep_alive(p)); 57 | } 58 | 59 | const llhttp_settings_t wasm_settings = { 60 | .on_message_begin = wasm_on_message_begin, 61 | .on_url = wasm_on_url, 62 | .on_status = wasm_on_status, 63 | .on_header_field = wasm_on_header_field, 64 | .on_header_value = wasm_on_header_value, 65 | .on_headers_complete = wasm_on_headers_complete_wrap, 66 | .on_body = wasm_on_body, 67 | .on_message_complete = wasm_on_message_complete, 68 | }; 69 | 70 | 71 | llhttp_t* llhttp_alloc(llhttp_type_t type) { 72 | llhttp_t* parser = malloc(sizeof(llhttp_t)); 73 | llhttp_init(parser, type, &wasm_settings); 74 | return parser; 75 | } 76 | 77 | void llhttp_free(llhttp_t* parser) { 78 | free(parser); 79 | } 80 | 81 | #endif // defined(__wasm__) 82 | 83 | /* Some getters required to get stuff from the parser */ 84 | 85 | uint8_t llhttp_get_type(llhttp_t* parser) { 86 | return parser->type; 87 | } 88 | 89 | uint8_t llhttp_get_http_major(llhttp_t* parser) { 90 | return parser->http_major; 91 | } 92 | 93 | uint8_t llhttp_get_http_minor(llhttp_t* parser) { 94 | return parser->http_minor; 95 | } 96 | 97 | uint8_t llhttp_get_method(llhttp_t* parser) { 98 | return parser->method; 99 | } 100 | 101 | int llhttp_get_status_code(llhttp_t* parser) { 102 | return parser->status_code; 103 | } 104 | 105 | uint8_t llhttp_get_upgrade(llhttp_t* parser) { 106 | return parser->upgrade; 107 | } 108 | 109 | 110 | void llhttp_reset(llhttp_t* parser) { 111 | llhttp_type_t type = parser->type; 112 | const llhttp_settings_t* settings = parser->settings; 113 | void* data = parser->data; 114 | uint16_t lenient_flags = parser->lenient_flags; 115 | 116 | llhttp__internal_init(parser); 117 | 118 | parser->type = type; 119 | parser->settings = (void*) settings; 120 | parser->data = data; 121 | parser->lenient_flags = lenient_flags; 122 | } 123 | 124 | 125 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { 126 | return llhttp__internal_execute(parser, data, data + len); 127 | } 128 | 129 | 130 | void llhttp_settings_init(llhttp_settings_t* settings) { 131 | memset(settings, 0, sizeof(*settings)); 132 | } 133 | 134 | 135 | llhttp_errno_t llhttp_finish(llhttp_t* parser) { 136 | int err; 137 | 138 | /* We're in an error state. Don't bother doing anything. */ 139 | if (parser->error != 0) { 140 | return 0; 141 | } 142 | 143 | switch (parser->finish) { 144 | case HTTP_FINISH_SAFE_WITH_CB: 145 | CALLBACK_MAYBE(parser, on_message_complete); 146 | if (err != HPE_OK) return err; 147 | 148 | /* FALLTHROUGH */ 149 | case HTTP_FINISH_SAFE: 150 | return HPE_OK; 151 | case HTTP_FINISH_UNSAFE: 152 | parser->reason = "Invalid EOF state"; 153 | return HPE_INVALID_EOF_STATE; 154 | default: 155 | abort(); 156 | } 157 | } 158 | 159 | 160 | void llhttp_pause(llhttp_t* parser) { 161 | if (parser->error != HPE_OK) { 162 | return; 163 | } 164 | 165 | parser->error = HPE_PAUSED; 166 | parser->reason = "Paused"; 167 | } 168 | 169 | 170 | void llhttp_resume(llhttp_t* parser) { 171 | if (parser->error != HPE_PAUSED) { 172 | return; 173 | } 174 | 175 | parser->error = 0; 176 | } 177 | 178 | 179 | void llhttp_resume_after_upgrade(llhttp_t* parser) { 180 | if (parser->error != HPE_PAUSED_UPGRADE) { 181 | return; 182 | } 183 | 184 | parser->error = 0; 185 | } 186 | 187 | 188 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { 189 | return parser->error; 190 | } 191 | 192 | 193 | const char* llhttp_get_error_reason(const llhttp_t* parser) { 194 | return parser->reason; 195 | } 196 | 197 | 198 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { 199 | parser->reason = reason; 200 | } 201 | 202 | 203 | const char* llhttp_get_error_pos(const llhttp_t* parser) { 204 | return parser->error_pos; 205 | } 206 | 207 | 208 | const char* llhttp_errno_name(llhttp_errno_t err) { 209 | #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; 210 | switch (err) { 211 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 212 | default: abort(); 213 | } 214 | #undef HTTP_ERRNO_GEN 215 | } 216 | 217 | 218 | const char* llhttp_method_name(llhttp_method_t method) { 219 | #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; 220 | switch (method) { 221 | HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) 222 | default: abort(); 223 | } 224 | #undef HTTP_METHOD_GEN 225 | } 226 | 227 | const char* llhttp_status_name(llhttp_status_t status) { 228 | #define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; 229 | switch (status) { 230 | HTTP_STATUS_MAP(HTTP_STATUS_GEN) 231 | default: abort(); 232 | } 233 | #undef HTTP_STATUS_GEN 234 | } 235 | 236 | 237 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { 238 | if (enabled) { 239 | parser->lenient_flags |= LENIENT_HEADERS; 240 | } else { 241 | parser->lenient_flags &= ~LENIENT_HEADERS; 242 | } 243 | } 244 | 245 | 246 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { 247 | if (enabled) { 248 | parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; 249 | } else { 250 | parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; 251 | } 252 | } 253 | 254 | 255 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { 256 | if (enabled) { 257 | parser->lenient_flags |= LENIENT_KEEP_ALIVE; 258 | } else { 259 | parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; 260 | } 261 | } 262 | 263 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { 264 | if (enabled) { 265 | parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; 266 | } else { 267 | parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; 268 | } 269 | } 270 | 271 | void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { 272 | if (enabled) { 273 | parser->lenient_flags |= LENIENT_VERSION; 274 | } else { 275 | parser->lenient_flags &= ~LENIENT_VERSION; 276 | } 277 | } 278 | 279 | void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { 280 | if (enabled) { 281 | parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; 282 | } else { 283 | parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; 284 | } 285 | } 286 | 287 | void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { 288 | if (enabled) { 289 | parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; 290 | } else { 291 | parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; 292 | } 293 | } 294 | 295 | void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { 296 | if (enabled) { 297 | parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 298 | } else { 299 | parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 300 | } 301 | } 302 | 303 | void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) { 304 | if (enabled) { 305 | parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; 306 | } else { 307 | parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF; 308 | } 309 | } 310 | 311 | void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) { 312 | if (enabled) { 313 | parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; 314 | } else { 315 | parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE; 316 | } 317 | } 318 | 319 | /* Callbacks */ 320 | 321 | 322 | int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { 323 | int err; 324 | CALLBACK_MAYBE(s, on_message_begin); 325 | return err; 326 | } 327 | 328 | 329 | int llhttp__on_protocol(llhttp_t* s, const char* p, const char* endp) { 330 | int err; 331 | SPAN_CALLBACK_MAYBE(s, on_protocol, p, endp - p); 332 | return err; 333 | } 334 | 335 | 336 | int llhttp__on_protocol_complete(llhttp_t* s, const char* p, const char* endp) { 337 | int err; 338 | CALLBACK_MAYBE(s, on_protocol_complete); 339 | return err; 340 | } 341 | 342 | 343 | int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { 344 | int err; 345 | SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); 346 | return err; 347 | } 348 | 349 | 350 | int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { 351 | int err; 352 | CALLBACK_MAYBE(s, on_url_complete); 353 | return err; 354 | } 355 | 356 | 357 | int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { 358 | int err; 359 | SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); 360 | return err; 361 | } 362 | 363 | 364 | int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { 365 | int err; 366 | CALLBACK_MAYBE(s, on_status_complete); 367 | return err; 368 | } 369 | 370 | 371 | int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { 372 | int err; 373 | SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); 374 | return err; 375 | } 376 | 377 | 378 | int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { 379 | int err; 380 | CALLBACK_MAYBE(s, on_method_complete); 381 | return err; 382 | } 383 | 384 | 385 | int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { 386 | int err; 387 | SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); 388 | return err; 389 | } 390 | 391 | 392 | int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { 393 | int err; 394 | CALLBACK_MAYBE(s, on_version_complete); 395 | return err; 396 | } 397 | 398 | 399 | int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { 400 | int err; 401 | SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); 402 | return err; 403 | } 404 | 405 | 406 | int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { 407 | int err; 408 | CALLBACK_MAYBE(s, on_header_field_complete); 409 | return err; 410 | } 411 | 412 | 413 | int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { 414 | int err; 415 | SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); 416 | return err; 417 | } 418 | 419 | 420 | int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { 421 | int err; 422 | CALLBACK_MAYBE(s, on_header_value_complete); 423 | return err; 424 | } 425 | 426 | 427 | int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { 428 | int err; 429 | CALLBACK_MAYBE(s, on_headers_complete); 430 | return err; 431 | } 432 | 433 | 434 | int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { 435 | int err; 436 | CALLBACK_MAYBE(s, on_message_complete); 437 | return err; 438 | } 439 | 440 | 441 | int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { 442 | int err; 443 | SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); 444 | return err; 445 | } 446 | 447 | 448 | int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { 449 | int err; 450 | CALLBACK_MAYBE(s, on_chunk_header); 451 | return err; 452 | } 453 | 454 | 455 | int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { 456 | int err; 457 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); 458 | return err; 459 | } 460 | 461 | 462 | int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { 463 | int err; 464 | CALLBACK_MAYBE(s, on_chunk_extension_name_complete); 465 | return err; 466 | } 467 | 468 | 469 | int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { 470 | int err; 471 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); 472 | return err; 473 | } 474 | 475 | 476 | int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { 477 | int err; 478 | CALLBACK_MAYBE(s, on_chunk_extension_value_complete); 479 | return err; 480 | } 481 | 482 | 483 | int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { 484 | int err; 485 | CALLBACK_MAYBE(s, on_chunk_complete); 486 | return err; 487 | } 488 | 489 | 490 | int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { 491 | int err; 492 | CALLBACK_MAYBE(s, on_reset); 493 | return err; 494 | } 495 | 496 | 497 | /* Private */ 498 | 499 | 500 | void llhttp__debug(llhttp_t* s, const char* p, const char* endp, 501 | const char* msg) { 502 | if (p == endp) { 503 | fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, 504 | s->flags, msg); 505 | } else { 506 | fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, 507 | s->type, s->flags, *p, msg); 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /src/native/api.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_LLHTTP_API_H_ 2 | #define INCLUDE_LLHTTP_API_H_ 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | #include 7 | 8 | #if defined(__wasm__) 9 | #define LLHTTP_EXPORT __attribute__((visibility("default"))) 10 | #elif defined(_WIN32) 11 | #define LLHTTP_EXPORT __declspec(dllexport) 12 | #else 13 | #define LLHTTP_EXPORT 14 | #endif 15 | 16 | typedef llhttp__internal_t llhttp_t; 17 | typedef struct llhttp_settings_s llhttp_settings_t; 18 | 19 | typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); 20 | typedef int (*llhttp_cb)(llhttp_t*); 21 | 22 | struct llhttp_settings_s { 23 | /* Possible return values 0, -1, `HPE_PAUSED` */ 24 | llhttp_cb on_message_begin; 25 | 26 | /* Possible return values 0, -1, HPE_USER */ 27 | llhttp_data_cb on_protocol; 28 | llhttp_data_cb on_url; 29 | llhttp_data_cb on_status; 30 | llhttp_data_cb on_method; 31 | llhttp_data_cb on_version; 32 | llhttp_data_cb on_header_field; 33 | llhttp_data_cb on_header_value; 34 | llhttp_data_cb on_chunk_extension_name; 35 | llhttp_data_cb on_chunk_extension_value; 36 | 37 | /* Possible return values: 38 | * 0 - Proceed normally 39 | * 1 - Assume that request/response has no body, and proceed to parsing the 40 | * next message 41 | * 2 - Assume absence of body (as above) and make `llhttp_execute()` return 42 | * `HPE_PAUSED_UPGRADE` 43 | * -1 - Error 44 | * `HPE_PAUSED` 45 | */ 46 | llhttp_cb on_headers_complete; 47 | 48 | /* Possible return values 0, -1, HPE_USER */ 49 | llhttp_data_cb on_body; 50 | 51 | /* Possible return values 0, -1, `HPE_PAUSED` */ 52 | llhttp_cb on_message_complete; 53 | llhttp_cb on_protocol_complete; 54 | llhttp_cb on_url_complete; 55 | llhttp_cb on_status_complete; 56 | llhttp_cb on_method_complete; 57 | llhttp_cb on_version_complete; 58 | llhttp_cb on_header_field_complete; 59 | llhttp_cb on_header_value_complete; 60 | llhttp_cb on_chunk_extension_name_complete; 61 | llhttp_cb on_chunk_extension_value_complete; 62 | 63 | /* When on_chunk_header is called, the current chunk length is stored 64 | * in parser->content_length. 65 | * Possible return values 0, -1, `HPE_PAUSED` 66 | */ 67 | llhttp_cb on_chunk_header; 68 | llhttp_cb on_chunk_complete; 69 | llhttp_cb on_reset; 70 | }; 71 | 72 | /* Initialize the parser with specific type and user settings. 73 | * 74 | * NOTE: lifetime of `settings` has to be at least the same as the lifetime of 75 | * the `parser` here. In practice, `settings` has to be either a static 76 | * variable or be allocated with `malloc`, `new`, etc. 77 | */ 78 | LLHTTP_EXPORT 79 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 80 | const llhttp_settings_t* settings); 81 | 82 | LLHTTP_EXPORT 83 | llhttp_t* llhttp_alloc(llhttp_type_t type); 84 | 85 | LLHTTP_EXPORT 86 | void llhttp_free(llhttp_t* parser); 87 | 88 | LLHTTP_EXPORT 89 | uint8_t llhttp_get_type(llhttp_t* parser); 90 | 91 | LLHTTP_EXPORT 92 | uint8_t llhttp_get_http_major(llhttp_t* parser); 93 | 94 | LLHTTP_EXPORT 95 | uint8_t llhttp_get_http_minor(llhttp_t* parser); 96 | 97 | LLHTTP_EXPORT 98 | uint8_t llhttp_get_method(llhttp_t* parser); 99 | 100 | LLHTTP_EXPORT 101 | int llhttp_get_status_code(llhttp_t* parser); 102 | 103 | LLHTTP_EXPORT 104 | uint8_t llhttp_get_upgrade(llhttp_t* parser); 105 | 106 | /* Reset an already initialized parser back to the start state, preserving the 107 | * existing parser type, callback settings, user data, and lenient flags. 108 | */ 109 | LLHTTP_EXPORT 110 | void llhttp_reset(llhttp_t* parser); 111 | 112 | /* Initialize the settings object */ 113 | LLHTTP_EXPORT 114 | void llhttp_settings_init(llhttp_settings_t* settings); 115 | 116 | /* Parse full or partial request/response, invoking user callbacks along the 117 | * way. 118 | * 119 | * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing 120 | * interrupts, and such errno is returned from `llhttp_execute()`. If 121 | * `HPE_PAUSED` was used as a errno, the execution can be resumed with 122 | * `llhttp_resume()` call. 123 | * 124 | * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` 125 | * is returned after fully parsing the request/response. If the user wishes to 126 | * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. 127 | * 128 | * NOTE: if this function ever returns a non-pause type error, it will continue 129 | * to return the same error upon each successive call up until `llhttp_init()` 130 | * is called. 131 | */ 132 | LLHTTP_EXPORT 133 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); 134 | 135 | /* This method should be called when the other side has no further bytes to 136 | * send (e.g. shutdown of readable side of the TCP connection.) 137 | * 138 | * Requests without `Content-Length` and other messages might require treating 139 | * all incoming bytes as the part of the body, up to the last byte of the 140 | * connection. This method will invoke `on_message_complete()` callback if the 141 | * request was terminated safely. Otherwise a error code would be returned. 142 | */ 143 | LLHTTP_EXPORT 144 | llhttp_errno_t llhttp_finish(llhttp_t* parser); 145 | 146 | /* Returns `1` if the incoming message is parsed until the last byte, and has 147 | * to be completed by calling `llhttp_finish()` on EOF 148 | */ 149 | LLHTTP_EXPORT 150 | int llhttp_message_needs_eof(const llhttp_t* parser); 151 | 152 | /* Returns `1` if there might be any other messages following the last that was 153 | * successfully parsed. 154 | */ 155 | LLHTTP_EXPORT 156 | int llhttp_should_keep_alive(const llhttp_t* parser); 157 | 158 | /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set 159 | * appropriate error reason. 160 | * 161 | * Important: do not call this from user callbacks! User callbacks must return 162 | * `HPE_PAUSED` if pausing is required. 163 | */ 164 | LLHTTP_EXPORT 165 | void llhttp_pause(llhttp_t* parser); 166 | 167 | /* Might be called to resume the execution after the pause in user's callback. 168 | * See `llhttp_execute()` above for details. 169 | * 170 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. 171 | */ 172 | LLHTTP_EXPORT 173 | void llhttp_resume(llhttp_t* parser); 174 | 175 | /* Might be called to resume the execution after the pause in user's callback. 176 | * See `llhttp_execute()` above for details. 177 | * 178 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` 179 | */ 180 | LLHTTP_EXPORT 181 | void llhttp_resume_after_upgrade(llhttp_t* parser); 182 | 183 | /* Returns the latest return error */ 184 | LLHTTP_EXPORT 185 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); 186 | 187 | /* Returns the verbal explanation of the latest returned error. 188 | * 189 | * Note: User callback should set error reason when returning the error. See 190 | * `llhttp_set_error_reason()` for details. 191 | */ 192 | LLHTTP_EXPORT 193 | const char* llhttp_get_error_reason(const llhttp_t* parser); 194 | 195 | /* Assign verbal description to the returned error. Must be called in user 196 | * callbacks right before returning the errno. 197 | * 198 | * Note: `HPE_USER` error code might be useful in user callbacks. 199 | */ 200 | LLHTTP_EXPORT 201 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason); 202 | 203 | /* Returns the pointer to the last parsed byte before the returned error. The 204 | * pointer is relative to the `data` argument of `llhttp_execute()`. 205 | * 206 | * Note: this method might be useful for counting the number of parsed bytes. 207 | */ 208 | LLHTTP_EXPORT 209 | const char* llhttp_get_error_pos(const llhttp_t* parser); 210 | 211 | /* Returns textual name of error code */ 212 | LLHTTP_EXPORT 213 | const char* llhttp_errno_name(llhttp_errno_t err); 214 | 215 | /* Returns textual name of HTTP method */ 216 | LLHTTP_EXPORT 217 | const char* llhttp_method_name(llhttp_method_t method); 218 | 219 | /* Returns textual name of HTTP status */ 220 | LLHTTP_EXPORT 221 | const char* llhttp_status_name(llhttp_status_t status); 222 | 223 | /* Enables/disables lenient header value parsing (disabled by default). 224 | * 225 | * Lenient parsing disables header value token checks, extending llhttp's 226 | * protocol support to highly non-compliant clients/server. No 227 | * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when 228 | * lenient parsing is "on". 229 | * 230 | * **Enabling this flag can pose a security issue since you will be exposed to 231 | * request smuggling attacks. USE WITH CAUTION!** 232 | */ 233 | LLHTTP_EXPORT 234 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); 235 | 236 | 237 | /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and 238 | * `Content-Length` headers (disabled by default). 239 | * 240 | * Normally `llhttp` would error when `Transfer-Encoding` is present in 241 | * conjunction with `Content-Length`. This error is important to prevent HTTP 242 | * request smuggling, but may be less desirable for small number of cases 243 | * involving legacy servers. 244 | * 245 | * **Enabling this flag can pose a security issue since you will be exposed to 246 | * request smuggling attacks. USE WITH CAUTION!** 247 | */ 248 | LLHTTP_EXPORT 249 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); 250 | 251 | 252 | /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 253 | * requests responses. 254 | * 255 | * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) 256 | * the HTTP request/response after the request/response with `Connection: close` 257 | * and `Content-Length`. This is important to prevent cache poisoning attacks, 258 | * but might interact badly with outdated and insecure clients. With this flag 259 | * the extra request/response will be parsed normally. 260 | * 261 | * **Enabling this flag can pose a security issue since you will be exposed to 262 | * poisoning attacks. USE WITH CAUTION!** 263 | */ 264 | LLHTTP_EXPORT 265 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); 266 | 267 | /* Enables/disables lenient handling of `Transfer-Encoding` header. 268 | * 269 | * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value 270 | * and another value after it (either in a single header or in multiple 271 | * headers whose value are internally joined using `, `). 272 | * This is mandated by the spec to reliably determine request body size and thus 273 | * avoid request smuggling. 274 | * With this flag the extra value will be parsed normally. 275 | * 276 | * **Enabling this flag can pose a security issue since you will be exposed to 277 | * request smuggling attacks. USE WITH CAUTION!** 278 | */ 279 | LLHTTP_EXPORT 280 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); 281 | 282 | /* Enables/disables lenient handling of HTTP version. 283 | * 284 | * Normally `llhttp` would error when the HTTP version in the request or status line 285 | * is not `0.9`, `1.0`, `1.1` or `2.0`. 286 | * With this flag the invalid value will be parsed normally. 287 | * 288 | * **Enabling this flag can pose a security issue since you will allow unsupported 289 | * HTTP versions. USE WITH CAUTION!** 290 | */ 291 | LLHTTP_EXPORT 292 | void llhttp_set_lenient_version(llhttp_t* parser, int enabled); 293 | 294 | /* Enables/disables lenient handling of additional data received after a message ends 295 | * and keep-alive is disabled. 296 | * 297 | * Normally `llhttp` would error when additional unexpected data is received if the message 298 | * contains the `Connection` header with `close` value. 299 | * With this flag the extra data will discarded without throwing an error. 300 | * 301 | * **Enabling this flag can pose a security issue since you will be exposed to 302 | * poisoning attacks. USE WITH CAUTION!** 303 | */ 304 | LLHTTP_EXPORT 305 | void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled); 306 | 307 | /* Enables/disables lenient handling of incomplete CRLF sequences. 308 | * 309 | * Normally `llhttp` would error when a CR is not followed by LF when terminating the 310 | * request line, the status line, the headers or a chunk header. 311 | * With this flag only a CR is required to terminate such sections. 312 | * 313 | * **Enabling this flag can pose a security issue since you will be exposed to 314 | * request smuggling attacks. USE WITH CAUTION!** 315 | */ 316 | LLHTTP_EXPORT 317 | void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled); 318 | 319 | /* 320 | * Enables/disables lenient handling of line separators. 321 | * 322 | * Normally `llhttp` would error when a LF is not preceded by CR when terminating the 323 | * request line, the status line, the headers, a chunk header or a chunk data. 324 | * With this flag only a LF is required to terminate such sections. 325 | * 326 | * **Enabling this flag can pose a security issue since you will be exposed to 327 | * request smuggling attacks. USE WITH CAUTION!** 328 | */ 329 | LLHTTP_EXPORT 330 | void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled); 331 | 332 | /* Enables/disables lenient handling of chunks not separated via CRLF. 333 | * 334 | * Normally `llhttp` would error when after a chunk data a CRLF is missing before 335 | * starting a new chunk. 336 | * With this flag the new chunk can start immediately after the previous one. 337 | * 338 | * **Enabling this flag can pose a security issue since you will be exposed to 339 | * request smuggling attacks. USE WITH CAUTION!** 340 | */ 341 | LLHTTP_EXPORT 342 | void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled); 343 | 344 | /* Enables/disables lenient handling of spaces after chunk size. 345 | * 346 | * Normally `llhttp` would error when after a chunk size is followed by one or more 347 | * spaces are present instead of a CRLF or `;`. 348 | * With this flag this check is disabled. 349 | * 350 | * **Enabling this flag can pose a security issue since you will be exposed to 351 | * request smuggling attacks. USE WITH CAUTION!** 352 | */ 353 | LLHTTP_EXPORT 354 | void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled); 355 | 356 | #ifdef __cplusplus 357 | } /* extern "C" */ 358 | #endif 359 | #endif /* INCLUDE_LLHTTP_API_H_ */ 360 | -------------------------------------------------------------------------------- /src/native/http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef LLHTTP__TEST 3 | # include "llhttp.h" 4 | #else 5 | # define llhttp_t llparse_t 6 | #endif /* */ 7 | 8 | int llhttp_message_needs_eof(const llhttp_t* parser); 9 | int llhttp_should_keep_alive(const llhttp_t* parser); 10 | 11 | int llhttp__before_headers_complete(llhttp_t* parser, const char* p, 12 | const char* endp) { 13 | /* Set this here so that on_headers_complete() callbacks can see it */ 14 | if ((parser->flags & F_UPGRADE) && 15 | (parser->flags & F_CONNECTION_UPGRADE)) { 16 | /* For responses, "Upgrade: foo" and "Connection: upgrade" are 17 | * mandatory only when it is a 101 Switching Protocols response, 18 | * otherwise it is purely informational, to announce support. 19 | */ 20 | parser->upgrade = 21 | (parser->type == HTTP_REQUEST || parser->status_code == 101); 22 | } else { 23 | parser->upgrade = (parser->method == HTTP_CONNECT); 24 | } 25 | return 0; 26 | } 27 | 28 | 29 | /* Return values: 30 | * 0 - No body, `restart`, message_complete 31 | * 1 - CONNECT request, `restart`, message_complete, and pause 32 | * 2 - chunk_size_start 33 | * 3 - body_identity 34 | * 4 - body_identity_eof 35 | * 5 - invalid transfer-encoding for request 36 | */ 37 | int llhttp__after_headers_complete(llhttp_t* parser, const char* p, 38 | const char* endp) { 39 | int hasBody; 40 | 41 | hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; 42 | if ( 43 | (parser->upgrade && (parser->method == HTTP_CONNECT || 44 | (parser->flags & F_SKIPBODY) || !hasBody)) || 45 | /* See RFC 2616 section 4.4 - 1xx e.g. Continue */ 46 | (parser->type == HTTP_RESPONSE && parser->status_code == 101) 47 | ) { 48 | /* Exit, the rest of the message is in a different protocol. */ 49 | return 1; 50 | } 51 | 52 | if (parser->type == HTTP_RESPONSE && parser->status_code == 100) { 53 | /* No body, restart as the message is complete */ 54 | return 0; 55 | } 56 | 57 | /* See RFC 2616 section 4.4 */ 58 | if ( 59 | parser->flags & F_SKIPBODY || /* response to a HEAD request */ 60 | ( 61 | parser->type == HTTP_RESPONSE && ( 62 | parser->status_code == 102 || /* Processing */ 63 | parser->status_code == 103 || /* Early Hints */ 64 | parser->status_code == 204 || /* No Content */ 65 | parser->status_code == 304 /* Not Modified */ 66 | ) 67 | ) 68 | ) { 69 | return 0; 70 | } else if (parser->flags & F_CHUNKED) { 71 | /* chunked encoding - ignore Content-Length header, prepare for a chunk */ 72 | return 2; 73 | } else if (parser->flags & F_TRANSFER_ENCODING) { 74 | if (parser->type == HTTP_REQUEST && 75 | (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && 76 | (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { 77 | /* RFC 7230 3.3.3 */ 78 | 79 | /* If a Transfer-Encoding header field 80 | * is present in a request and the chunked transfer coding is not 81 | * the final encoding, the message body length cannot be determined 82 | * reliably; the server MUST respond with the 400 (Bad Request) 83 | * status code and then close the connection. 84 | */ 85 | return 5; 86 | } else { 87 | /* RFC 7230 3.3.3 */ 88 | 89 | /* If a Transfer-Encoding header field is present in a response and 90 | * the chunked transfer coding is not the final encoding, the 91 | * message body length is determined by reading the connection until 92 | * it is closed by the server. 93 | */ 94 | return 4; 95 | } 96 | } else { 97 | if (!(parser->flags & F_CONTENT_LENGTH)) { 98 | if (!llhttp_message_needs_eof(parser)) { 99 | /* Assume content-length 0 - read the next */ 100 | return 0; 101 | } else { 102 | /* Read body until EOF */ 103 | return 4; 104 | } 105 | } else if (parser->content_length == 0) { 106 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 107 | return 0; 108 | } else { 109 | /* Content-Length header given and non-zero */ 110 | return 3; 111 | } 112 | } 113 | } 114 | 115 | 116 | int llhttp__after_message_complete(llhttp_t* parser, const char* p, 117 | const char* endp) { 118 | int should_keep_alive; 119 | 120 | should_keep_alive = llhttp_should_keep_alive(parser); 121 | parser->finish = HTTP_FINISH_SAFE; 122 | parser->flags = 0; 123 | 124 | /* NOTE: this is ignored in loose parsing mode */ 125 | return should_keep_alive; 126 | } 127 | 128 | 129 | int llhttp_message_needs_eof(const llhttp_t* parser) { 130 | if (parser->type == HTTP_REQUEST) { 131 | return 0; 132 | } 133 | 134 | /* See RFC 2616 section 4.4 */ 135 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 136 | parser->status_code == 204 || /* No Content */ 137 | parser->status_code == 304 || /* Not Modified */ 138 | (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ 139 | return 0; 140 | } 141 | 142 | /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ 143 | if ((parser->flags & F_TRANSFER_ENCODING) && 144 | (parser->flags & F_CHUNKED) == 0) { 145 | return 1; 146 | } 147 | 148 | if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { 149 | return 0; 150 | } 151 | 152 | return 1; 153 | } 154 | 155 | 156 | int llhttp_should_keep_alive(const llhttp_t* parser) { 157 | if (parser->http_major > 0 && parser->http_minor > 0) { 158 | /* HTTP/1.1 */ 159 | if (parser->flags & F_CONNECTION_CLOSE) { 160 | return 0; 161 | } 162 | } else { 163 | /* HTTP/1.0 or earlier */ 164 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 165 | return 0; 166 | } 167 | } 168 | 169 | return !llhttp_message_needs_eof(parser); 170 | } 171 | -------------------------------------------------------------------------------- /test/fixtures/extra.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fixture.h" 4 | 5 | int llhttp__on_url(llparse_t* s, const char* p, const char* endp) { 6 | if (llparse__in_bench) 7 | return 0; 8 | 9 | return llparse__print_span("url", p, endp); 10 | } 11 | 12 | 13 | int llhttp__on_url_complete(llparse_t* s, const char* p, const char* endp) { 14 | if (llparse__in_bench) 15 | return 0; 16 | 17 | llparse__print(p, endp, "url complete"); 18 | 19 | #ifdef LLHTTP__TEST_PAUSE_ON_URL_COMPLETE 20 | return LLPARSE__ERROR_PAUSE; 21 | #else 22 | return 0; 23 | #endif 24 | } 25 | 26 | 27 | int llhttp__on_url_schema(llparse_t* s, const char* p, const char* endp) { 28 | if (llparse__in_bench) 29 | return 0; 30 | 31 | return llparse__print_span("url.schema", p, endp); 32 | } 33 | 34 | 35 | int llhttp__on_url_host(llparse_t* s, const char* p, const char* endp) { 36 | if (llparse__in_bench) 37 | return 0; 38 | 39 | return llparse__print_span("url.host", p, endp); 40 | } 41 | 42 | 43 | int llhttp__on_url_path(llparse_t* s, const char* p, const char* endp) { 44 | if (llparse__in_bench) 45 | return 0; 46 | 47 | return llparse__print_span("url.path", p, endp); 48 | } 49 | 50 | 51 | int llhttp__on_url_query(llparse_t* s, const char* p, const char* endp) { 52 | if (llparse__in_bench) 53 | return 0; 54 | 55 | return llparse__print_span("url.query", p, endp); 56 | } 57 | 58 | 59 | int llhttp__on_url_fragment(llparse_t* s, const char* p, const char* endp) { 60 | if (llparse__in_bench) 61 | return 0; 62 | 63 | return llparse__print_span("url.fragment", p, endp); 64 | } 65 | 66 | 67 | #ifdef LLHTTP__TEST_HTTP 68 | 69 | void llhttp__test_init_request(llparse_t* s) { 70 | s->type = HTTP_REQUEST; 71 | } 72 | 73 | 74 | void llhttp__test_init_response(llparse_t* s) { 75 | s->type = HTTP_RESPONSE; 76 | } 77 | 78 | 79 | void llhttp__test_init_request_lenient_all(llparse_t* s) { 80 | llhttp__test_init_request(s); 81 | s->lenient_flags |= 82 | LENIENT_HEADERS | LENIENT_CHUNKED_LENGTH | LENIENT_KEEP_ALIVE | 83 | LENIENT_TRANSFER_ENCODING | LENIENT_VERSION | LENIENT_DATA_AFTER_CLOSE | 84 | LENIENT_OPTIONAL_LF_AFTER_CR | LENIENT_OPTIONAL_CR_BEFORE_LF | 85 | LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 86 | } 87 | 88 | 89 | void llhttp__test_init_response_lenient_all(llparse_t* s) { 90 | llhttp__test_init_response(s); 91 | s->lenient_flags |= 92 | LENIENT_HEADERS | LENIENT_CHUNKED_LENGTH | LENIENT_KEEP_ALIVE | 93 | LENIENT_TRANSFER_ENCODING | LENIENT_VERSION | LENIENT_DATA_AFTER_CLOSE | 94 | LENIENT_OPTIONAL_LF_AFTER_CR | LENIENT_OPTIONAL_CR_BEFORE_LF | 95 | LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 96 | } 97 | 98 | 99 | void llhttp__test_init_request_lenient_headers(llparse_t* s) { 100 | llhttp__test_init_request(s); 101 | s->lenient_flags |= LENIENT_HEADERS; 102 | } 103 | 104 | 105 | void llhttp__test_init_request_lenient_chunked_length(llparse_t* s) { 106 | llhttp__test_init_request(s); 107 | s->lenient_flags |= LENIENT_CHUNKED_LENGTH; 108 | } 109 | 110 | 111 | void llhttp__test_init_request_lenient_keep_alive(llparse_t* s) { 112 | llhttp__test_init_request(s); 113 | s->lenient_flags |= LENIENT_KEEP_ALIVE; 114 | } 115 | 116 | void llhttp__test_init_request_lenient_transfer_encoding(llparse_t* s) { 117 | llhttp__test_init_request(s); 118 | s->lenient_flags |= LENIENT_TRANSFER_ENCODING; 119 | } 120 | 121 | 122 | void llhttp__test_init_request_lenient_version(llparse_t* s) { 123 | llhttp__test_init_request(s); 124 | s->lenient_flags |= LENIENT_VERSION; 125 | } 126 | 127 | 128 | void llhttp__test_init_response_lenient_keep_alive(llparse_t* s) { 129 | llhttp__test_init_response(s); 130 | s->lenient_flags |= LENIENT_KEEP_ALIVE; 131 | } 132 | 133 | void llhttp__test_init_response_lenient_version(llparse_t* s) { 134 | llhttp__test_init_response(s); 135 | s->lenient_flags |= LENIENT_VERSION; 136 | } 137 | 138 | 139 | void llhttp__test_init_response_lenient_headers(llparse_t* s) { 140 | llhttp__test_init_response(s); 141 | s->lenient_flags |= LENIENT_HEADERS; 142 | } 143 | 144 | void llhttp__test_init_request_lenient_data_after_close(llparse_t* s) { 145 | llhttp__test_init_request(s); 146 | s->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; 147 | } 148 | 149 | void llhttp__test_init_response_lenient_data_after_close(llparse_t* s) { 150 | llhttp__test_init_response(s); 151 | s->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; 152 | } 153 | 154 | void llhttp__test_init_request_lenient_optional_lf_after_cr(llparse_t* s) { 155 | llhttp__test_init_request(s); 156 | s->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; 157 | } 158 | 159 | void llhttp__test_init_response_lenient_optional_lf_after_cr(llparse_t* s) { 160 | llhttp__test_init_response(s); 161 | s->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; 162 | } 163 | 164 | void llhttp__test_init_request_lenient_optional_cr_before_lf(llparse_t* s) { 165 | llhttp__test_init_request(s); 166 | s->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; 167 | } 168 | 169 | void llhttp__test_init_response_lenient_optional_cr_before_lf(llparse_t* s) { 170 | llhttp__test_init_response(s); 171 | s->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; 172 | } 173 | 174 | void llhttp__test_init_request_lenient_optional_crlf_after_chunk(llparse_t* s) { 175 | llhttp__test_init_request(s); 176 | s->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 177 | } 178 | 179 | void llhttp__test_init_response_lenient_optional_crlf_after_chunk(llparse_t* s) { 180 | llhttp__test_init_response(s); 181 | s->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; 182 | } 183 | 184 | void llhttp__test_init_request_lenient_spaces_after_chunk_size(llparse_t* s) { 185 | llhttp__test_init_request(s); 186 | s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; 187 | } 188 | 189 | void llhttp__test_init_response_lenient_spaces_after_chunk_size(llparse_t* s) { 190 | llhttp__test_init_response(s); 191 | s->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; 192 | } 193 | 194 | 195 | void llhttp__test_finish(llparse_t* s) { 196 | llparse__print(NULL, NULL, "finish=%d", s->finish); 197 | } 198 | 199 | 200 | int llhttp__on_message_begin(llparse_t* s, const char* p, const char* endp) { 201 | if (llparse__in_bench) 202 | return 0; 203 | 204 | llparse__print(p, endp, "message begin"); 205 | 206 | #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_BEGIN 207 | return LLPARSE__ERROR_PAUSE; 208 | #else 209 | return 0; 210 | #endif 211 | } 212 | 213 | 214 | int llhttp__on_message_complete(llparse_t* s, const char* p, const char* endp) { 215 | if (llparse__in_bench) 216 | return 0; 217 | 218 | llparse__print(p, endp, "message complete"); 219 | 220 | #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_COMPLETE 221 | return LLPARSE__ERROR_PAUSE; 222 | #else 223 | return 0; 224 | #endif 225 | } 226 | 227 | 228 | int llhttp__on_protocol(llparse_t* s, const char* p, const char* endp) { 229 | if (llparse__in_bench) 230 | return 0; 231 | 232 | return llparse__print_span("protocol", p, endp); 233 | } 234 | 235 | 236 | int llhttp__on_protocol_complete(llparse_t* s, const char* p, const char* endp) { 237 | if (llparse__in_bench) 238 | return 0; 239 | 240 | llparse__print(p, endp, "protocol complete"); 241 | 242 | #ifdef LLHTTP__TEST_PAUSE_ON_PROTOCOL_COMPLETE 243 | return LLPARSE__ERROR_PAUSE; 244 | #else 245 | return 0; 246 | #endif 247 | } 248 | 249 | 250 | int llhttp__on_status(llparse_t* s, const char* p, const char* endp) { 251 | if (llparse__in_bench) 252 | return 0; 253 | 254 | return llparse__print_span("status", p, endp); 255 | } 256 | 257 | 258 | int llhttp__on_status_complete(llparse_t* s, const char* p, const char* endp) { 259 | if (llparse__in_bench) 260 | return 0; 261 | 262 | llparse__print(p, endp, "status complete"); 263 | 264 | #ifdef LLHTTP__TEST_PAUSE_ON_STATUS_COMPLETE 265 | return LLPARSE__ERROR_PAUSE; 266 | #else 267 | return 0; 268 | #endif 269 | } 270 | 271 | 272 | int llhttp__on_method(llparse_t* s, const char* p, const char* endp) { 273 | if (llparse__in_bench || s->type != HTTP_REQUEST) 274 | return 0; 275 | 276 | return llparse__print_span("method", p, endp); 277 | } 278 | 279 | 280 | int llhttp__on_method_complete(llparse_t* s, const char* p, const char* endp) { 281 | if (llparse__in_bench) 282 | return 0; 283 | 284 | llparse__print(p, endp, "method complete"); 285 | 286 | #ifdef LLHTTP__TEST_PAUSE_ON_METHOD_COMPLETE 287 | return LLPARSE__ERROR_PAUSE; 288 | #else 289 | return 0; 290 | #endif 291 | } 292 | 293 | 294 | int llhttp__on_version(llparse_t* s, const char* p, const char* endp) { 295 | if (llparse__in_bench) 296 | return 0; 297 | 298 | return llparse__print_span("version", p, endp); 299 | } 300 | 301 | 302 | int llhttp__on_version_complete(llparse_t* s, const char* p, const char* endp) { 303 | if (llparse__in_bench) 304 | return 0; 305 | 306 | llparse__print(p, endp, "version complete"); 307 | 308 | #ifdef LLHTTP__TEST_PAUSE_ON_VERSION_COMPLETE 309 | return LLPARSE__ERROR_PAUSE; 310 | #else 311 | return 0; 312 | #endif 313 | } 314 | 315 | int llhttp__on_header_field(llparse_t* s, const char* p, const char* endp) { 316 | if (llparse__in_bench) 317 | return 0; 318 | 319 | return llparse__print_span("header_field", p, endp); 320 | } 321 | 322 | 323 | int llhttp__on_header_field_complete(llparse_t* s, const char* p, const char* endp) { 324 | if (llparse__in_bench) 325 | return 0; 326 | 327 | llparse__print(p, endp, "header_field complete"); 328 | 329 | #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_FIELD_COMPLETE 330 | return LLPARSE__ERROR_PAUSE; 331 | #else 332 | return 0; 333 | #endif 334 | } 335 | 336 | 337 | int llhttp__on_header_value(llparse_t* s, const char* p, const char* endp) { 338 | if (llparse__in_bench) 339 | return 0; 340 | 341 | return llparse__print_span("header_value", p, endp); 342 | } 343 | 344 | 345 | int llhttp__on_header_value_complete(llparse_t* s, const char* p, const char* endp) { 346 | if (llparse__in_bench) 347 | return 0; 348 | 349 | llparse__print(p, endp, "header_value complete"); 350 | 351 | #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_VALUE_COMPLETE 352 | return LLPARSE__ERROR_PAUSE; 353 | #else 354 | return 0; 355 | #endif 356 | } 357 | 358 | 359 | int llhttp__on_headers_complete(llparse_t* s, const char* p, const char* endp) { 360 | if (llparse__in_bench) 361 | return 0; 362 | 363 | if (s->type == HTTP_REQUEST) { 364 | llparse__print(p, endp, 365 | "headers complete method=%d v=%d/%d flags=%x content_length=%llu", 366 | s->method, s->http_major, s->http_minor, s->flags, s->content_length); 367 | } else if (s->type == HTTP_RESPONSE) { 368 | llparse__print(p, endp, 369 | "headers complete status=%d v=%d/%d flags=%x content_length=%llu", 370 | s->status_code, s->http_major, s->http_minor, s->flags, 371 | s->content_length); 372 | } else { 373 | llparse__print(p, endp, "invalid headers complete"); 374 | } 375 | 376 | #ifdef LLHTTP__TEST_PAUSE_ON_HEADERS_COMPLETE 377 | return LLPARSE__ERROR_PAUSE; 378 | #elif defined(LLHTTP__TEST_SKIP_BODY) 379 | llparse__print(p, endp, "skip body"); 380 | return 1; 381 | #else 382 | return 0; 383 | #endif 384 | } 385 | 386 | 387 | int llhttp__on_body(llparse_t* s, const char* p, const char* endp) { 388 | if (llparse__in_bench) 389 | return 0; 390 | 391 | return llparse__print_span("body", p, endp); 392 | } 393 | 394 | 395 | int llhttp__on_chunk_header(llparse_t* s, const char* p, const char* endp) { 396 | if (llparse__in_bench) 397 | return 0; 398 | 399 | llparse__print(p, endp, "chunk header len=%d", (int) s->content_length); 400 | 401 | #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_HEADER 402 | return LLPARSE__ERROR_PAUSE; 403 | #else 404 | return 0; 405 | #endif 406 | } 407 | 408 | 409 | int llhttp__on_chunk_extension_name(llparse_t* s, const char* p, const char* endp) { 410 | if (llparse__in_bench) 411 | return 0; 412 | 413 | return llparse__print_span("chunk_extension_name", p, endp); 414 | } 415 | 416 | 417 | int llhttp__on_chunk_extension_name_complete(llparse_t* s, const char* p, const char* endp) { 418 | if (llparse__in_bench) 419 | return 0; 420 | 421 | llparse__print(p, endp, "chunk_extension_name complete"); 422 | 423 | #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_EXTENSION_NAME 424 | return LLPARSE__ERROR_PAUSE; 425 | #else 426 | return 0; 427 | #endif 428 | } 429 | 430 | 431 | int llhttp__on_chunk_extension_value(llparse_t* s, const char* p, const char* endp) { 432 | if (llparse__in_bench) 433 | return 0; 434 | 435 | return llparse__print_span("chunk_extension_value", p, endp); 436 | } 437 | 438 | 439 | int llhttp__on_chunk_extension_value_complete(llparse_t* s, const char* p, const char* endp) { 440 | if (llparse__in_bench) 441 | return 0; 442 | 443 | llparse__print(p, endp, "chunk_extension_value complete"); 444 | 445 | #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_EXTENSION_VALUE 446 | return LLPARSE__ERROR_PAUSE; 447 | #else 448 | return 0; 449 | #endif 450 | } 451 | 452 | 453 | int llhttp__on_chunk_complete(llparse_t* s, const char* p, const char* endp) { 454 | if (llparse__in_bench) 455 | return 0; 456 | 457 | llparse__print(p, endp, "chunk complete"); 458 | 459 | #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_COMPLETE 460 | return LLPARSE__ERROR_PAUSE; 461 | #else 462 | return 0; 463 | #endif 464 | } 465 | 466 | int llhttp__on_reset(llparse_t* s, const char* p, const char* endp) { 467 | if (llparse__in_bench) 468 | return 0; 469 | 470 | llparse__print(p, endp, "reset"); 471 | 472 | #ifdef LLHTTP__TEST_PAUSE_ON_RESET 473 | return LLPARSE__ERROR_PAUSE; 474 | #else 475 | return 0; 476 | #endif 477 | } 478 | 479 | #endif /* LLHTTP__TEST_HTTP */ 480 | -------------------------------------------------------------------------------- /test/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { ICompilerResult, LLParse } from 'llparse'; 3 | import { Dot } from 'llparse-dot'; 4 | import { 5 | Fixture, FixtureResult, IFixtureBuildOptions, 6 | } from 'llparse-test-fixture'; 7 | import path from 'path'; 8 | 9 | import llhttp from '../../src/llhttp'; 10 | 11 | export type Node = Parameters['0']; 12 | 13 | export { FixtureResult }; 14 | 15 | export type TestType = 'request' | 'response' | 'request-finish' | 'response-finish' | 16 | 'request-lenient-all' | 'response-lenient-all' | 17 | 'request-lenient-headers' | 'response-lenient-headers' | 18 | 'request-lenient-chunked-length' | 'request-lenient-transfer-encoding' | 19 | 'request-lenient-keep-alive' | 'response-lenient-keep-alive' | 20 | 'request-lenient-version' | 'response-lenient-version' | 21 | 'request-lenient-data-after-close' | 'response-lenient-data-after-close' | 22 | 'request-lenient-optional-lf-after-cr' | 'response-lenient-optional-lf-after-cr' | 23 | 'request-lenient-optional-cr-before-lf' | 'response-lenient-optional-cr-before-lf' | 24 | 'request-lenient-optional-crlf-after-chunk' | 'response-lenient-optional-crlf-after-chunk' | 25 | 'request-lenient-spaces-after-chunk-size' | 'response-lenient-spaces-after-chunk-size' | 26 | 'none' | 'url'; 27 | 28 | export const allowedTypes: TestType[] = [ 29 | 'request', 30 | 'response', 31 | 'request-finish', 32 | 'response-finish', 33 | 'request-lenient-all', 34 | 'response-lenient-all', 35 | 'request-lenient-headers', 36 | 'response-lenient-headers', 37 | 'request-lenient-keep-alive', 38 | 'response-lenient-keep-alive', 39 | 'request-lenient-chunked-length', 40 | 'request-lenient-transfer-encoding', 41 | 'request-lenient-version', 42 | 'response-lenient-version', 43 | 'request-lenient-data-after-close', 44 | 'response-lenient-data-after-close', 45 | 'request-lenient-optional-lf-after-cr', 46 | 'response-lenient-optional-lf-after-cr', 47 | 'request-lenient-optional-cr-before-lf', 48 | 'response-lenient-optional-cr-before-lf', 49 | 'request-lenient-optional-crlf-after-chunk', 50 | 'response-lenient-optional-crlf-after-chunk', 51 | 'request-lenient-spaces-after-chunk-size', 52 | 'response-lenient-spaces-after-chunk-size', 53 | ]; 54 | 55 | const BUILD_DIR = path.join(__dirname, '..', 'tmp'); 56 | const CHEADERS_FILE = path.join(BUILD_DIR, 'cheaders.h'); 57 | 58 | const cheaders = new llhttp.CHeaders().build(); 59 | try { 60 | fs.mkdirSync(BUILD_DIR); 61 | } catch { 62 | // no-op 63 | } 64 | fs.writeFileSync(CHEADERS_FILE, cheaders); 65 | 66 | const fixtures = new Fixture({ 67 | buildDir: path.join(__dirname, '..', 'tmp'), 68 | extra: [ 69 | '-msse4.2', 70 | '-DLLHTTP__TEST', 71 | '-DLLPARSE__ERROR_PAUSE=' + llhttp.constants.ERROR.PAUSED, 72 | '-include', CHEADERS_FILE, 73 | path.join(__dirname, 'extra.c'), 74 | ], 75 | maxParallel: process.env.LLPARSE_DEBUG ? 1 : undefined, 76 | }); 77 | 78 | const cache = new Map(); 79 | 80 | export async function build( 81 | llparse: LLParse, node: Node, outFile: string, 82 | options: IFixtureBuildOptions = {}, 83 | ty: TestType = 'none'): Promise { 84 | const dot = new Dot(); 85 | fs.writeFileSync(path.join(BUILD_DIR, outFile + '.dot'), 86 | dot.build(node)); 87 | 88 | let artifacts: ICompilerResult; 89 | if (cache.has(node)) { 90 | artifacts = cache.get(node)!; 91 | } else { 92 | artifacts = llparse.build(node, { 93 | c: { header: outFile }, 94 | debug: process.env.LLPARSE_DEBUG ? 'llparse__debug' : undefined, 95 | }); 96 | cache.set(node, artifacts); 97 | } 98 | 99 | const extra = options.extra === undefined ? [] : options.extra.slice(); 100 | 101 | if (allowedTypes.includes(ty)) { 102 | extra.push( 103 | `-DLLPARSE__TEST_INIT=llhttp__test_init_${ty.replace(/-/g, '_')}`); 104 | } 105 | 106 | if (ty === 'request-finish' || ty === 'response-finish') { 107 | if (ty === 'request-finish') { 108 | extra.push('-DLLPARSE__TEST_INIT=llhttp__test_init_request'); 109 | } else { 110 | extra.push('-DLLPARSE__TEST_INIT=llhttp__test_init_response'); 111 | } 112 | extra.push('-DLLPARSE__TEST_FINISH=llhttp__test_finish'); 113 | } 114 | 115 | return await fixtures.build(artifacts, outFile, Object.assign(options, { 116 | extra, 117 | })); 118 | } 119 | -------------------------------------------------------------------------------- /test/fuzzers/fuzz_parser.c: -------------------------------------------------------------------------------- 1 | #include "llhttp.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int handle_on_message_complete(llhttp_t *arg) { return 0; } 7 | 8 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 9 | llhttp_t parser; 10 | llhttp_settings_t settings; 11 | llhttp_type_t http_type; 12 | 13 | /* We need four bytes to determine variable parameters */ 14 | if (size < 4) { 15 | return 0; 16 | } 17 | 18 | int headers = (data[0] & 0x01) == 1; 19 | int chunked_length = (data[1] & 0x01) == 1; 20 | int keep_alive = (data[2] & 0x01) == 1; 21 | if (data[0] % 3 == 0) { 22 | http_type = HTTP_BOTH; 23 | } else if (data[0] % 3 == 1) { 24 | http_type = HTTP_REQUEST; 25 | } else { 26 | http_type = HTTP_RESPONSE; 27 | } 28 | data += 4; 29 | size -= 4; 30 | 31 | /* Initialize user callbacks and settings */ 32 | llhttp_settings_init(&settings); 33 | 34 | /* Set user callback */ 35 | settings.on_message_complete = handle_on_message_complete; 36 | 37 | llhttp_init(&parser, http_type, &settings); 38 | llhttp_set_lenient_headers(&parser, headers); 39 | llhttp_set_lenient_chunked_length(&parser, chunked_length); 40 | llhttp_set_lenient_keep_alive(&parser, keep_alive); 41 | 42 | llhttp_execute(&parser, data, size); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/md-test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { describe, test } from 'node:test'; 3 | import fs from 'fs'; 4 | import { LLParse } from 'llparse'; 5 | import { Group, MDGator, Metadata, Test } from 'mdgator'; 6 | import path from 'path'; 7 | import vm from 'vm'; 8 | 9 | import llhttp from '../src/llhttp'; 10 | import { allowedTypes, build, FixtureResult, Node, TestType } from './fixtures'; 11 | 12 | // 13 | // Cache nodes/llparse instances ahead of time 14 | // (different types of tests will re-use them) 15 | // 16 | 17 | const modeCache = new Map(); 18 | 19 | function buildNode() { 20 | const p = new LLParse(); 21 | const instance = new llhttp.HTTP(p); 22 | 23 | return { llparse: p, entry: instance.build().entry }; 24 | } 25 | 26 | function buildURL() { 27 | const p = new LLParse(); 28 | const instance = new llhttp.URL(p, true); 29 | 30 | const node = instance.build(); 31 | 32 | // Loop 33 | node.exit.toHTTP.otherwise(node.entry.normal); 34 | node.exit.toHTTP09.otherwise(node.entry.normal); 35 | 36 | return { llparse: p, entry: node.entry.normal }; 37 | } 38 | 39 | // 40 | // Build binaries using cached nodes/llparse 41 | // 42 | 43 | async function buildMode(ty: TestType, meta: Metadata): Promise { 44 | const cacheKey = `${ty}:${JSON.stringify(meta || {})}`; 45 | let entry = modeCache.get(cacheKey); 46 | 47 | if (entry) { 48 | return entry; 49 | } 50 | 51 | let node: { llparse: LLParse; entry: Node }; 52 | let prefix: string; 53 | let extra: string[]; 54 | if (ty === 'url') { 55 | node = buildURL(); 56 | prefix = 'url'; 57 | extra = []; 58 | } else { 59 | node = buildNode(); 60 | prefix = 'http'; 61 | extra = [ 62 | '-DLLHTTP__TEST_HTTP', 63 | path.join(__dirname, '..', 'src', 'native', 'http.c'), 64 | ]; 65 | } 66 | 67 | if (meta.pause) { 68 | extra.push(`-DLLHTTP__TEST_PAUSE_${meta.pause.toUpperCase()}=1`); 69 | } 70 | 71 | if (meta.skipBody) { 72 | extra.push('-DLLHTTP__TEST_SKIP_BODY=1'); 73 | } 74 | 75 | entry = await build(node.llparse, node.entry, `${prefix}-${ty}`, { 76 | extra, 77 | }, ty); 78 | 79 | modeCache.set(cacheKey, entry); 80 | return entry; 81 | } 82 | 83 | // 84 | // Run test suite 85 | // 86 | 87 | function run(name: string): void { 88 | const md = new MDGator(); 89 | 90 | const raw = fs.readFileSync(path.join(__dirname, name + '.md')).toString(); 91 | const groups = md.parse(raw); 92 | 93 | function runSingleTest( 94 | location: string, ty: TestType, meta: Metadata, 95 | input: string, expected: readonly (string | RegExp)[] 96 | ): void { 97 | test(`should pass for type="${ty}" (location=${location})`, { timeout: 60000 }, async () => { 98 | const binary = await buildMode(ty, meta); 99 | await binary.check(input, expected, { 100 | noScan: meta.noScan === true, 101 | }); 102 | }); 103 | } 104 | 105 | function runTest(test: Test) { 106 | describe(test.name + ` at ${name}.md:${test.line + 1}`, () => { 107 | const location = `${name}.md:${test.line + 1}` 108 | 109 | let types: TestType[] = []; 110 | 111 | const isURL = test.values.has('url'); 112 | const inputKey = isURL ? 'url' : 'http'; 113 | 114 | assert(test.values.has(inputKey), 115 | `Missing "${inputKey}" code in md file`); 116 | assert.strictEqual(test.values.get(inputKey)!.length, 1, 117 | `Expected just one "${inputKey}" input`); 118 | 119 | let meta: Metadata; 120 | if (test.meta.has(inputKey)) { 121 | meta = test.meta.get(inputKey)![0]!; 122 | } else { 123 | assert(isURL, 'Missing required http metadata'); 124 | meta = {}; 125 | } 126 | 127 | if (isURL) { 128 | types = [ 'url' ]; 129 | } else { 130 | assert(Object.prototype.hasOwnProperty.call(meta, 'type'), 'Missing required `type` metadata'); 131 | 132 | if (meta.type) { 133 | if (!allowedTypes.includes(meta.type)) { 134 | throw new Error(`Invalid value of \`type\` metadata: "${meta.type}"`); 135 | } 136 | 137 | types.push(meta.type); 138 | } 139 | } 140 | 141 | assert(test.values.has('log'), 'Missing `log` code in md file'); 142 | 143 | assert.strictEqual(test.values.get('log')!.length, 1, 144 | 'Expected just one output'); 145 | 146 | let input: string = test.values.get(inputKey)![0]; 147 | let expected: string = test.values.get('log')![0]; 148 | 149 | // Remove trailing newline 150 | input = input.replace(/\n$/, ''); 151 | 152 | // Remove escaped newlines 153 | input = input.replace(/\\(\r\n|\r|\n)/g, ''); 154 | 155 | // Normalize all newlines 156 | input = input.replace(/\r\n|\r|\n/g, '\r\n'); 157 | 158 | // Replace escaped CRLF, tabs, form-feed 159 | input = input.replace(/\\r/g, '\r'); 160 | input = input.replace(/\\n/g, '\n'); 161 | input = input.replace(/\\t/g, '\t'); 162 | input = input.replace(/\\f/g, '\f'); 163 | input = input.replace(/\\x([0-9a-fA-F]+)/g, (all, hex) => { 164 | return String.fromCharCode(parseInt(hex, 16)); 165 | }); 166 | 167 | // Useful in token tests 168 | input = input.replace(/\\([0-7]{1,3})/g, (_, digits) => { 169 | return String.fromCharCode(parseInt(digits, 8)); 170 | }); 171 | 172 | // Evaluate inline JavaScript 173 | input = input.replace(/\$\{(.+?)\}/g, (_, code) => { 174 | return vm.runInNewContext(code) + ''; 175 | }); 176 | 177 | // Escape first symbol `\r` or `\n`, `|`, `&` for Windows 178 | if (process.platform === 'win32') { 179 | const firstByte = Buffer.from(input)[0]; 180 | if (firstByte === 0x0a || firstByte === 0x0d) { 181 | input = '\\' + input; 182 | } 183 | 184 | input = input.replace(/\|/g, '^|'); 185 | input = input.replace(/&/g, '^&'); 186 | } 187 | 188 | // Replace escaped tabs/form-feed in expected too 189 | expected = expected.replace(/\\t/g, '\t'); 190 | expected = expected.replace(/\\f/g, '\f'); 191 | 192 | // Split 193 | const expectedLines = expected.split(/\n/g).slice(0, -1); 194 | 195 | const fullExpected = expectedLines.map((line) => { 196 | if (line.startsWith('/')) { 197 | return new RegExp(line.trim().slice(1, -1)); 198 | } else { 199 | return line; 200 | } 201 | }); 202 | 203 | for (const ty of types) { 204 | if (meta.skip === true || (process.env.ONLY === 'true' && !meta.only)) { 205 | continue; 206 | } 207 | 208 | runSingleTest(location, ty, meta, input, fullExpected); 209 | } 210 | }); 211 | } 212 | 213 | function runGroup(group: Group) { 214 | describe(group.name + ` at ${name}.md:${group.line + 1}`, function () { 215 | for (const child of group.children) { 216 | runGroup(child); 217 | } 218 | 219 | for (const test of group.tests) { 220 | runTest(test); 221 | } 222 | }); 223 | } 224 | 225 | for (const group of groups) { 226 | runGroup(group); 227 | } 228 | } 229 | 230 | run('request/sample'); 231 | run('request/lenient-headers'); 232 | run('request/lenient-version'); 233 | run('request/method'); 234 | run('request/uri'); 235 | run('request/connection'); 236 | run('request/content-length'); 237 | run('request/transfer-encoding'); 238 | run('request/invalid'); 239 | run('request/finish'); 240 | run('request/pausing'); 241 | run('request/pipelining'); 242 | 243 | run('response/sample'); 244 | run('response/connection'); 245 | run('response/content-length'); 246 | run('response/transfer-encoding'); 247 | run('response/invalid'); 248 | run('response/finish'); 249 | run('response/lenient-version'); 250 | run('response/pausing'); 251 | run('response/pipelining'); 252 | 253 | run('url'); 254 | -------------------------------------------------------------------------------- /test/request/content-length.md: -------------------------------------------------------------------------------- 1 | Content-Length header 2 | ===================== 3 | 4 | ## `Content-Length` with zeroes 5 | 6 | 7 | ```http 8 | PUT /url HTTP/1.1 9 | Content-Length: 003 10 | 11 | abc 12 | ``` 13 | 14 | ```log 15 | off=0 message begin 16 | off=0 len=3 span[method]="PUT" 17 | off=3 method complete 18 | off=4 len=4 span[url]="/url" 19 | off=9 url complete 20 | off=9 len=4 span[protocol]="HTTP" 21 | off=13 protocol complete 22 | off=14 len=3 span[version]="1.1" 23 | off=17 version complete 24 | off=19 len=14 span[header_field]="Content-Length" 25 | off=34 header_field complete 26 | off=35 len=3 span[header_value]="003" 27 | off=40 header_value complete 28 | off=42 headers complete method=4 v=1/1 flags=20 content_length=3 29 | off=42 len=3 span[body]="abc" 30 | off=45 message complete 31 | ``` 32 | 33 | ## `Content-Length` with follow-up headers 34 | 35 | The way the parser works is that special headers (like `Content-Length`) first 36 | set `header_state` to appropriate value, and then apply custom parsing using 37 | that value. For `Content-Length`, in particular, the `header_state` is used for 38 | setting the flag too. 39 | 40 | Make sure that `header_state` is reset to `0`, so that the flag won't be 41 | attempted to set twice (and error). 42 | 43 | 44 | ```http 45 | PUT /url HTTP/1.1 46 | Content-Length: 003 47 | Ohai: world 48 | 49 | abc 50 | ``` 51 | 52 | ```log 53 | off=0 message begin 54 | off=0 len=3 span[method]="PUT" 55 | off=3 method complete 56 | off=4 len=4 span[url]="/url" 57 | off=9 url complete 58 | off=9 len=4 span[protocol]="HTTP" 59 | off=13 protocol complete 60 | off=14 len=3 span[version]="1.1" 61 | off=17 version complete 62 | off=19 len=14 span[header_field]="Content-Length" 63 | off=34 header_field complete 64 | off=35 len=3 span[header_value]="003" 65 | off=40 header_value complete 66 | off=40 len=4 span[header_field]="Ohai" 67 | off=45 header_field complete 68 | off=46 len=5 span[header_value]="world" 69 | off=53 header_value complete 70 | off=55 headers complete method=4 v=1/1 flags=20 content_length=3 71 | off=55 len=3 span[body]="abc" 72 | off=58 message complete 73 | ``` 74 | 75 | ## Error on `Content-Length` overflow 76 | 77 | 78 | ```http 79 | PUT /url HTTP/1.1 80 | Content-Length: 1000000000000000000000 81 | 82 | ``` 83 | 84 | ```log 85 | off=0 message begin 86 | off=0 len=3 span[method]="PUT" 87 | off=3 method complete 88 | off=4 len=4 span[url]="/url" 89 | off=9 url complete 90 | off=9 len=4 span[protocol]="HTTP" 91 | off=13 protocol complete 92 | off=14 len=3 span[version]="1.1" 93 | off=17 version complete 94 | off=19 len=14 span[header_field]="Content-Length" 95 | off=34 header_field complete 96 | off=35 len=21 span[header_value]="100000000000000000000" 97 | off=56 error code=11 reason="Content-Length overflow" 98 | ``` 99 | 100 | ## Error on duplicate `Content-Length` 101 | 102 | 103 | ```http 104 | PUT /url HTTP/1.1 105 | Content-Length: 1 106 | Content-Length: 2 107 | 108 | ``` 109 | 110 | ```log 111 | off=0 message begin 112 | off=0 len=3 span[method]="PUT" 113 | off=3 method complete 114 | off=4 len=4 span[url]="/url" 115 | off=9 url complete 116 | off=9 len=4 span[protocol]="HTTP" 117 | off=13 protocol complete 118 | off=14 len=3 span[version]="1.1" 119 | off=17 version complete 120 | off=19 len=14 span[header_field]="Content-Length" 121 | off=34 header_field complete 122 | off=35 len=1 span[header_value]="1" 123 | off=38 header_value complete 124 | off=38 len=14 span[header_field]="Content-Length" 125 | off=53 header_field complete 126 | off=54 error code=4 reason="Duplicate Content-Length" 127 | ``` 128 | 129 | ## Error on simultaneous `Content-Length` and `Transfer-Encoding: identity` 130 | 131 | 132 | ```http 133 | PUT /url HTTP/1.1 134 | Content-Length: 1 135 | Transfer-Encoding: identity 136 | 137 | 138 | ``` 139 | 140 | ```log 141 | off=0 message begin 142 | off=0 len=3 span[method]="PUT" 143 | off=3 method complete 144 | off=4 len=4 span[url]="/url" 145 | off=9 url complete 146 | off=9 len=4 span[protocol]="HTTP" 147 | off=13 protocol complete 148 | off=14 len=3 span[version]="1.1" 149 | off=17 version complete 150 | off=19 len=14 span[header_field]="Content-Length" 151 | off=34 header_field complete 152 | off=35 len=1 span[header_value]="1" 153 | off=38 header_value complete 154 | off=38 len=17 span[header_field]="Transfer-Encoding" 155 | off=56 header_field complete 156 | off=56 error code=15 reason="Transfer-Encoding can't be present with Content-Length" 157 | ``` 158 | 159 | ## Invalid whitespace token with `Content-Length` header field 160 | 161 | 162 | ```http 163 | PUT /url HTTP/1.1 164 | Connection: upgrade 165 | Content-Length : 4 166 | Upgrade: ws 167 | 168 | abcdefgh 169 | ``` 170 | 171 | ```log 172 | off=0 message begin 173 | off=0 len=3 span[method]="PUT" 174 | off=3 method complete 175 | off=4 len=4 span[url]="/url" 176 | off=9 url complete 177 | off=9 len=4 span[protocol]="HTTP" 178 | off=13 protocol complete 179 | off=14 len=3 span[version]="1.1" 180 | off=17 version complete 181 | off=19 len=10 span[header_field]="Connection" 182 | off=30 header_field complete 183 | off=31 len=7 span[header_value]="upgrade" 184 | off=40 header_value complete 185 | off=40 len=14 span[header_field]="Content-Length" 186 | off=55 error code=10 reason="Invalid header field char" 187 | ``` 188 | 189 | ## Invalid whitespace token with `Content-Length` header field (lenient) 190 | 191 | 192 | ```http 193 | PUT /url HTTP/1.1 194 | Connection: upgrade 195 | Content-Length : 4 196 | Upgrade: ws 197 | 198 | abcdefgh 199 | ``` 200 | 201 | ```log 202 | off=0 message begin 203 | off=0 len=3 span[method]="PUT" 204 | off=3 method complete 205 | off=4 len=4 span[url]="/url" 206 | off=9 url complete 207 | off=9 len=4 span[protocol]="HTTP" 208 | off=13 protocol complete 209 | off=14 len=3 span[version]="1.1" 210 | off=17 version complete 211 | off=19 len=10 span[header_field]="Connection" 212 | off=30 header_field complete 213 | off=31 len=7 span[header_value]="upgrade" 214 | off=40 header_value complete 215 | off=40 len=15 span[header_field]="Content-Length " 216 | off=56 header_field complete 217 | off=57 len=1 span[header_value]="4" 218 | off=60 header_value complete 219 | off=60 len=7 span[header_field]="Upgrade" 220 | off=68 header_field complete 221 | off=69 len=2 span[header_value]="ws" 222 | off=73 header_value complete 223 | off=75 headers complete method=4 v=1/1 flags=34 content_length=4 224 | off=75 len=4 span[body]="abcd" 225 | off=79 message complete 226 | off=79 error code=22 reason="Pause on CONNECT/Upgrade" 227 | ``` 228 | 229 | ## No error on simultaneous `Content-Length` and `Transfer-Encoding: identity` (lenient) 230 | 231 | 232 | ```http 233 | PUT /url HTTP/1.1 234 | Content-Length: 1 235 | Transfer-Encoding: identity 236 | 237 | 238 | ``` 239 | 240 | ```log 241 | off=0 message begin 242 | off=0 len=3 span[method]="PUT" 243 | off=3 method complete 244 | off=4 len=4 span[url]="/url" 245 | off=9 url complete 246 | off=9 len=4 span[protocol]="HTTP" 247 | off=13 protocol complete 248 | off=14 len=3 span[version]="1.1" 249 | off=17 version complete 250 | off=19 len=14 span[header_field]="Content-Length" 251 | off=34 header_field complete 252 | off=35 len=1 span[header_value]="1" 253 | off=38 header_value complete 254 | off=38 len=17 span[header_field]="Transfer-Encoding" 255 | off=56 header_field complete 256 | off=57 len=8 span[header_value]="identity" 257 | off=67 header_value complete 258 | off=69 headers complete method=4 v=1/1 flags=220 content_length=1 259 | ``` 260 | 261 | ## Funky `Content-Length` with body 262 | 263 | 264 | ```http 265 | GET /get_funky_content_length_body_hello HTTP/1.0 266 | conTENT-Length: 5 267 | 268 | HELLO 269 | ``` 270 | 271 | ```log 272 | off=0 message begin 273 | off=0 len=3 span[method]="GET" 274 | off=3 method complete 275 | off=4 len=36 span[url]="/get_funky_content_length_body_hello" 276 | off=41 url complete 277 | off=41 len=4 span[protocol]="HTTP" 278 | off=45 protocol complete 279 | off=46 len=3 span[version]="1.0" 280 | off=49 version complete 281 | off=51 len=14 span[header_field]="conTENT-Length" 282 | off=66 header_field complete 283 | off=67 len=1 span[header_value]="5" 284 | off=70 header_value complete 285 | off=72 headers complete method=1 v=1/0 flags=20 content_length=5 286 | off=72 len=5 span[body]="HELLO" 287 | off=77 message complete 288 | ``` 289 | 290 | ## Spaces in `Content-Length` (surrounding) 291 | 292 | 293 | ```http 294 | POST / HTTP/1.1 295 | Content-Length: 42 296 | 297 | 298 | ``` 299 | 300 | ```log 301 | off=0 message begin 302 | off=0 len=4 span[method]="POST" 303 | off=4 method complete 304 | off=5 len=1 span[url]="/" 305 | off=7 url complete 306 | off=7 len=4 span[protocol]="HTTP" 307 | off=11 protocol complete 308 | off=12 len=3 span[version]="1.1" 309 | off=15 version complete 310 | off=17 len=14 span[header_field]="Content-Length" 311 | off=32 header_field complete 312 | off=34 len=3 span[header_value]="42 " 313 | off=39 header_value complete 314 | off=41 headers complete method=3 v=1/1 flags=20 content_length=42 315 | ``` 316 | 317 | ### Spaces in `Content-Length` #2 318 | 319 | 320 | ```http 321 | POST / HTTP/1.1 322 | Content-Length: 4 2 323 | 324 | 325 | ``` 326 | 327 | ```log 328 | off=0 message begin 329 | off=0 len=4 span[method]="POST" 330 | off=4 method complete 331 | off=5 len=1 span[url]="/" 332 | off=7 url complete 333 | off=7 len=4 span[protocol]="HTTP" 334 | off=11 protocol complete 335 | off=12 len=3 span[version]="1.1" 336 | off=15 version complete 337 | off=17 len=14 span[header_field]="Content-Length" 338 | off=32 header_field complete 339 | off=33 len=2 span[header_value]="4 " 340 | off=35 error code=11 reason="Invalid character in Content-Length" 341 | ``` 342 | 343 | ### Spaces in `Content-Length` #3 344 | 345 | 346 | ```http 347 | POST / HTTP/1.1 348 | Content-Length: 13 37 349 | 350 | 351 | ``` 352 | 353 | ```log 354 | off=0 message begin 355 | off=0 len=4 span[method]="POST" 356 | off=4 method complete 357 | off=5 len=1 span[url]="/" 358 | off=7 url complete 359 | off=7 len=4 span[protocol]="HTTP" 360 | off=11 protocol complete 361 | off=12 len=3 span[version]="1.1" 362 | off=15 version complete 363 | off=17 len=14 span[header_field]="Content-Length" 364 | off=32 header_field complete 365 | off=33 len=3 span[header_value]="13 " 366 | off=36 error code=11 reason="Invalid character in Content-Length" 367 | ``` 368 | 369 | ### Empty `Content-Length` 370 | 371 | 372 | ```http 373 | POST / HTTP/1.1 374 | Content-Length: 375 | 376 | 377 | ``` 378 | 379 | ```log 380 | off=0 message begin 381 | off=0 len=4 span[method]="POST" 382 | off=4 method complete 383 | off=5 len=1 span[url]="/" 384 | off=7 url complete 385 | off=7 len=4 span[protocol]="HTTP" 386 | off=11 protocol complete 387 | off=12 len=3 span[version]="1.1" 388 | off=15 version complete 389 | off=17 len=14 span[header_field]="Content-Length" 390 | off=32 header_field complete 391 | off=34 error code=11 reason="Empty Content-Length" 392 | ``` 393 | 394 | ## `Content-Length` with CR instead of dash 395 | 396 | 397 | ```http 398 | PUT /url HTTP/1.1 399 | Content\rLength: 003 400 | 401 | abc 402 | ``` 403 | 404 | ```log 405 | off=0 message begin 406 | off=0 len=3 span[method]="PUT" 407 | off=3 method complete 408 | off=4 len=4 span[url]="/url" 409 | off=9 url complete 410 | off=9 len=4 span[protocol]="HTTP" 411 | off=13 protocol complete 412 | off=14 len=3 span[version]="1.1" 413 | off=17 version complete 414 | off=26 error code=10 reason="Invalid header token" 415 | ``` 416 | 417 | ## Content-Length reset when no body is received 418 | 419 | 420 | ```http 421 | PUT /url HTTP/1.1 422 | Content-Length: 123 423 | 424 | POST /url HTTP/1.1 425 | Content-Length: 456 426 | 427 | 428 | ``` 429 | 430 | ```log 431 | off=0 message begin 432 | off=0 len=3 span[method]="PUT" 433 | off=3 method complete 434 | off=4 len=4 span[url]="/url" 435 | off=9 url complete 436 | off=9 len=4 span[protocol]="HTTP" 437 | off=13 protocol complete 438 | off=14 len=3 span[version]="1.1" 439 | off=17 version complete 440 | off=19 len=14 span[header_field]="Content-Length" 441 | off=34 header_field complete 442 | off=35 len=3 span[header_value]="123" 443 | off=40 header_value complete 444 | off=42 headers complete method=4 v=1/1 flags=20 content_length=123 445 | off=42 skip body 446 | off=42 message complete 447 | off=42 reset 448 | off=42 message begin 449 | off=42 len=4 span[method]="POST" 450 | off=46 method complete 451 | off=47 len=4 span[url]="/url" 452 | off=52 url complete 453 | off=52 len=4 span[protocol]="HTTP" 454 | off=56 protocol complete 455 | off=57 len=3 span[version]="1.1" 456 | off=60 version complete 457 | off=62 len=14 span[header_field]="Content-Length" 458 | off=77 header_field complete 459 | off=78 len=3 span[header_value]="456" 460 | off=83 header_value complete 461 | off=85 headers complete method=3 v=1/1 flags=20 content_length=456 462 | off=85 skip body 463 | off=85 message complete 464 | ``` 465 | 466 | ## Missing CRLF-CRLF before body 467 | 468 | 469 | ```http 470 | PUT /url HTTP/1.1 471 | Content-Length: 3 472 | \rabc 473 | ``` 474 | 475 | ```log 476 | off=0 message begin 477 | off=0 len=3 span[method]="PUT" 478 | off=3 method complete 479 | off=4 len=4 span[url]="/url" 480 | off=9 url complete 481 | off=9 len=4 span[protocol]="HTTP" 482 | off=13 protocol complete 483 | off=14 len=3 span[version]="1.1" 484 | off=17 version complete 485 | off=19 len=14 span[header_field]="Content-Length" 486 | off=34 header_field complete 487 | off=35 len=1 span[header_value]="3" 488 | off=38 header_value complete 489 | off=39 error code=2 reason="Expected LF after headers" 490 | ``` 491 | 492 | ## Missing CRLF-CRLF before body (lenient) 493 | 494 | 495 | ```http 496 | PUT /url HTTP/1.1 497 | Content-Length: 3 498 | \rabc 499 | ``` 500 | 501 | ```log 502 | off=0 message begin 503 | off=0 len=3 span[method]="PUT" 504 | off=3 method complete 505 | off=4 len=4 span[url]="/url" 506 | off=9 url complete 507 | off=9 len=4 span[protocol]="HTTP" 508 | off=13 protocol complete 509 | off=14 len=3 span[version]="1.1" 510 | off=17 version complete 511 | off=19 len=14 span[header_field]="Content-Length" 512 | off=34 header_field complete 513 | off=35 len=1 span[header_value]="3" 514 | off=38 header_value complete 515 | off=39 headers complete method=4 v=1/1 flags=20 content_length=3 516 | off=39 len=3 span[body]="abc" 517 | off=42 message complete 518 | ``` -------------------------------------------------------------------------------- /test/request/finish.md: -------------------------------------------------------------------------------- 1 | Finish 2 | ====== 3 | 4 | Those tests check the return codes and the behavior of `llhttp_finish()` C API. 5 | 6 | ## It should be safe to finish after GET request 7 | 8 | 9 | ```http 10 | GET / HTTP/1.1 11 | 12 | 13 | ``` 14 | 15 | ```log 16 | off=0 message begin 17 | off=0 len=3 span[method]="GET" 18 | off=3 method complete 19 | off=4 len=1 span[url]="/" 20 | off=6 url complete 21 | off=6 len=4 span[protocol]="HTTP" 22 | off=10 protocol complete 23 | off=11 len=3 span[version]="1.1" 24 | off=14 version complete 25 | off=18 headers complete method=1 v=1/1 flags=0 content_length=0 26 | off=18 message complete 27 | off=NULL finish=0 28 | ``` 29 | 30 | ## It should be unsafe to finish after incomplete PUT request 31 | 32 | 33 | ```http 34 | PUT / HTTP/1.1 35 | Content-Length: 100 36 | 37 | ``` 38 | 39 | ```log 40 | off=0 message begin 41 | off=0 len=3 span[method]="PUT" 42 | off=3 method complete 43 | off=4 len=1 span[url]="/" 44 | off=6 url complete 45 | off=6 len=4 span[protocol]="HTTP" 46 | off=10 protocol complete 47 | off=11 len=3 span[version]="1.1" 48 | off=14 version complete 49 | off=16 len=14 span[header_field]="Content-Length" 50 | off=31 header_field complete 51 | off=32 len=3 span[header_value]="100" 52 | off=NULL finish=2 53 | ``` 54 | 55 | ## It should be unsafe to finish inside of the header 56 | 57 | 58 | ```http 59 | PUT / HTTP/1.1 60 | Content-Leng 61 | ``` 62 | 63 | ```log 64 | off=0 message begin 65 | off=0 len=3 span[method]="PUT" 66 | off=3 method complete 67 | off=4 len=1 span[url]="/" 68 | off=6 url complete 69 | off=6 len=4 span[protocol]="HTTP" 70 | off=10 protocol complete 71 | off=11 len=3 span[version]="1.1" 72 | off=14 version complete 73 | off=16 len=12 span[header_field]="Content-Leng" 74 | off=NULL finish=2 75 | ``` 76 | -------------------------------------------------------------------------------- /test/request/lenient-headers.md: -------------------------------------------------------------------------------- 1 | Lenient header value parsing 2 | ============================ 3 | 4 | Parsing with header value token checks off. 5 | 6 | ## Header value (lenient) 7 | 8 | 9 | ```http 10 | GET /url HTTP/1.1 11 | Header1: \f 12 | 13 | 14 | ``` 15 | 16 | ```log 17 | off=0 message begin 18 | off=0 len=3 span[method]="GET" 19 | off=3 method complete 20 | off=4 len=4 span[url]="/url" 21 | off=9 url complete 22 | off=9 len=4 span[protocol]="HTTP" 23 | off=13 protocol complete 24 | off=14 len=3 span[version]="1.1" 25 | off=17 version complete 26 | off=19 len=7 span[header_field]="Header1" 27 | off=27 header_field complete 28 | off=28 len=1 span[header_value]="\f" 29 | off=31 header_value complete 30 | off=33 headers complete method=1 v=1/1 flags=0 content_length=0 31 | off=33 message complete 32 | ``` 33 | 34 | ## Second request header value (lenient) 35 | 36 | 37 | ```http 38 | GET /url HTTP/1.1 39 | Header1: Okay 40 | 41 | 42 | GET /url HTTP/1.1 43 | Header1: \f 44 | 45 | 46 | ``` 47 | 48 | ```log 49 | off=0 message begin 50 | off=0 len=3 span[method]="GET" 51 | off=3 method complete 52 | off=4 len=4 span[url]="/url" 53 | off=9 url complete 54 | off=9 len=4 span[protocol]="HTTP" 55 | off=13 protocol complete 56 | off=14 len=3 span[version]="1.1" 57 | off=17 version complete 58 | off=19 len=7 span[header_field]="Header1" 59 | off=27 header_field complete 60 | off=28 len=4 span[header_value]="Okay" 61 | off=34 header_value complete 62 | off=36 headers complete method=1 v=1/1 flags=0 content_length=0 63 | off=36 message complete 64 | off=38 reset 65 | off=38 message begin 66 | off=38 len=3 span[method]="GET" 67 | off=41 method complete 68 | off=42 len=4 span[url]="/url" 69 | off=47 url complete 70 | off=47 len=4 span[protocol]="HTTP" 71 | off=51 protocol complete 72 | off=52 len=3 span[version]="1.1" 73 | off=55 version complete 74 | off=57 len=7 span[header_field]="Header1" 75 | off=65 header_field complete 76 | off=66 len=1 span[header_value]="\f" 77 | off=69 header_value complete 78 | off=71 headers complete method=1 v=1/1 flags=0 content_length=0 79 | off=71 message complete 80 | ``` 81 | 82 | ## Header value 83 | 84 | 85 | ```http 86 | GET /url HTTP/1.1 87 | Header1: \f 88 | 89 | 90 | 91 | ``` 92 | 93 | ```log 94 | off=0 message begin 95 | off=0 len=3 span[method]="GET" 96 | off=3 method complete 97 | off=4 len=4 span[url]="/url" 98 | off=9 url complete 99 | off=9 len=4 span[protocol]="HTTP" 100 | off=13 protocol complete 101 | off=14 len=3 span[version]="1.1" 102 | off=17 version complete 103 | off=19 len=7 span[header_field]="Header1" 104 | off=27 header_field complete 105 | off=28 len=0 span[header_value]="" 106 | off=28 error code=10 reason="Invalid header value char" 107 | ``` 108 | 109 | ### Empty headers separated by CR (lenient) 110 | 111 | 112 | ```http 113 | POST / HTTP/1.1 114 | Connection: Close 115 | Host: localhost:5000 116 | x:\rTransfer-Encoding: chunked 117 | 118 | 1 119 | A 120 | 0 121 | 122 | ``` 123 | 124 | ```log 125 | off=0 message begin 126 | off=0 len=4 span[method]="POST" 127 | off=4 method complete 128 | off=5 len=1 span[url]="/" 129 | off=7 url complete 130 | off=7 len=4 span[protocol]="HTTP" 131 | off=11 protocol complete 132 | off=12 len=3 span[version]="1.1" 133 | off=15 version complete 134 | off=17 len=10 span[header_field]="Connection" 135 | off=28 header_field complete 136 | off=29 len=5 span[header_value]="Close" 137 | off=36 header_value complete 138 | off=36 len=4 span[header_field]="Host" 139 | off=41 header_field complete 140 | off=42 len=14 span[header_value]="localhost:5000" 141 | off=58 header_value complete 142 | off=58 len=1 span[header_field]="x" 143 | off=60 header_field complete 144 | off=61 len=0 span[header_value]="" 145 | off=61 header_value complete 146 | off=61 len=17 span[header_field]="Transfer-Encoding" 147 | off=79 header_field complete 148 | off=80 len=7 span[header_value]="chunked" 149 | off=89 header_value complete 150 | off=91 headers complete method=3 v=1/1 flags=20a content_length=0 151 | off=94 chunk header len=1 152 | off=94 len=1 span[body]="A" 153 | off=97 chunk complete 154 | off=100 chunk header len=0 155 | ``` -------------------------------------------------------------------------------- /test/request/lenient-version.md: -------------------------------------------------------------------------------- 1 | Lenient HTTP version parsing 2 | ============================ 3 | 4 | ### Invalid HTTP version (lenient) 5 | 6 | 7 | ```http 8 | GET / HTTP/5.6 9 | 10 | 11 | ``` 12 | 13 | ```log 14 | off=0 message begin 15 | off=0 len=3 span[method]="GET" 16 | off=3 method complete 17 | off=4 len=1 span[url]="/" 18 | off=6 url complete 19 | off=6 len=4 span[protocol]="HTTP" 20 | off=10 protocol complete 21 | off=11 len=3 span[version]="5.6" 22 | off=14 version complete 23 | off=18 headers complete method=1 v=5/6 flags=0 content_length=0 24 | off=18 message complete 25 | ``` -------------------------------------------------------------------------------- /test/request/method.md: -------------------------------------------------------------------------------- 1 | Methods 2 | ======= 3 | 4 | ### REPORT request 5 | 6 | 7 | ```http 8 | REPORT /test HTTP/1.1 9 | 10 | 11 | ``` 12 | 13 | ```log 14 | off=0 message begin 15 | off=0 len=6 span[method]="REPORT" 16 | off=6 method complete 17 | off=7 len=5 span[url]="/test" 18 | off=13 url complete 19 | off=13 len=4 span[protocol]="HTTP" 20 | off=17 protocol complete 21 | off=18 len=3 span[version]="1.1" 22 | off=21 version complete 23 | off=25 headers complete method=20 v=1/1 flags=0 content_length=0 24 | off=25 message complete 25 | ``` 26 | 27 | ### CONNECT request 28 | 29 | 30 | ```http 31 | CONNECT 0-home0.netscape.com:443 HTTP/1.0 32 | User-agent: Mozilla/1.1N 33 | Proxy-authorization: basic aGVsbG86d29ybGQ= 34 | 35 | some data 36 | and yet even more data 37 | ``` 38 | 39 | ```log 40 | off=0 message begin 41 | off=0 len=7 span[method]="CONNECT" 42 | off=7 method complete 43 | off=8 len=24 span[url]="0-home0.netscape.com:443" 44 | off=33 url complete 45 | off=33 len=4 span[protocol]="HTTP" 46 | off=37 protocol complete 47 | off=38 len=3 span[version]="1.0" 48 | off=41 version complete 49 | off=43 len=10 span[header_field]="User-agent" 50 | off=54 header_field complete 51 | off=55 len=12 span[header_value]="Mozilla/1.1N" 52 | off=69 header_value complete 53 | off=69 len=19 span[header_field]="Proxy-authorization" 54 | off=89 header_field complete 55 | off=90 len=22 span[header_value]="basic aGVsbG86d29ybGQ=" 56 | off=114 header_value complete 57 | off=116 headers complete method=5 v=1/0 flags=0 content_length=0 58 | off=116 message complete 59 | off=116 error code=22 reason="Pause on CONNECT/Upgrade" 60 | ``` 61 | 62 | ### CONNECT request with CAPS 63 | 64 | 65 | ```http 66 | CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0 67 | User-agent: Mozilla/1.1N 68 | Proxy-authorization: basic aGVsbG86d29ybGQ= 69 | 70 | 71 | ``` 72 | 73 | ```log 74 | off=0 message begin 75 | off=0 len=7 span[method]="CONNECT" 76 | off=7 method complete 77 | off=8 len=22 span[url]="HOME0.NETSCAPE.COM:443" 78 | off=31 url complete 79 | off=31 len=4 span[protocol]="HTTP" 80 | off=35 protocol complete 81 | off=36 len=3 span[version]="1.0" 82 | off=39 version complete 83 | off=41 len=10 span[header_field]="User-agent" 84 | off=52 header_field complete 85 | off=53 len=12 span[header_value]="Mozilla/1.1N" 86 | off=67 header_value complete 87 | off=67 len=19 span[header_field]="Proxy-authorization" 88 | off=87 header_field complete 89 | off=88 len=22 span[header_value]="basic aGVsbG86d29ybGQ=" 90 | off=112 header_value complete 91 | off=114 headers complete method=5 v=1/0 flags=0 content_length=0 92 | off=114 message complete 93 | off=114 error code=22 reason="Pause on CONNECT/Upgrade" 94 | ``` 95 | 96 | ### CONNECT with body 97 | 98 | 99 | ```http 100 | CONNECT foo.bar.com:443 HTTP/1.0 101 | User-agent: Mozilla/1.1N 102 | Proxy-authorization: basic aGVsbG86d29ybGQ= 103 | Content-Length: 10 104 | 105 | blarfcicle" 106 | ``` 107 | 108 | ```log 109 | off=0 message begin 110 | off=0 len=7 span[method]="CONNECT" 111 | off=7 method complete 112 | off=8 len=15 span[url]="foo.bar.com:443" 113 | off=24 url complete 114 | off=24 len=4 span[protocol]="HTTP" 115 | off=28 protocol complete 116 | off=29 len=3 span[version]="1.0" 117 | off=32 version complete 118 | off=34 len=10 span[header_field]="User-agent" 119 | off=45 header_field complete 120 | off=46 len=12 span[header_value]="Mozilla/1.1N" 121 | off=60 header_value complete 122 | off=60 len=19 span[header_field]="Proxy-authorization" 123 | off=80 header_field complete 124 | off=81 len=22 span[header_value]="basic aGVsbG86d29ybGQ=" 125 | off=105 header_value complete 126 | off=105 len=14 span[header_field]="Content-Length" 127 | off=120 header_field complete 128 | off=121 len=2 span[header_value]="10" 129 | off=125 header_value complete 130 | off=127 headers complete method=5 v=1/0 flags=20 content_length=10 131 | off=127 message complete 132 | off=127 error code=22 reason="Pause on CONNECT/Upgrade" 133 | ``` 134 | 135 | ### M-SEARCH request 136 | 137 | 138 | ```http 139 | M-SEARCH * HTTP/1.1 140 | HOST: 239.255.255.250:1900 141 | MAN: "ssdp:discover" 142 | ST: "ssdp:all" 143 | 144 | 145 | ``` 146 | 147 | ```log 148 | off=0 message begin 149 | off=0 len=8 span[method]="M-SEARCH" 150 | off=8 method complete 151 | off=9 len=1 span[url]="*" 152 | off=11 url complete 153 | off=11 len=4 span[protocol]="HTTP" 154 | off=15 protocol complete 155 | off=16 len=3 span[version]="1.1" 156 | off=19 version complete 157 | off=21 len=4 span[header_field]="HOST" 158 | off=26 header_field complete 159 | off=27 len=20 span[header_value]="239.255.255.250:1900" 160 | off=49 header_value complete 161 | off=49 len=3 span[header_field]="MAN" 162 | off=53 header_field complete 163 | off=54 len=15 span[header_value]=""ssdp:discover"" 164 | off=71 header_value complete 165 | off=71 len=2 span[header_field]="ST" 166 | off=74 header_field complete 167 | off=75 len=10 span[header_value]=""ssdp:all"" 168 | off=87 header_value complete 169 | off=89 headers complete method=24 v=1/1 flags=0 content_length=0 170 | off=89 message complete 171 | ``` 172 | 173 | ### PATCH request 174 | 175 | 176 | ```http 177 | PATCH /file.txt HTTP/1.1 178 | Host: www.example.com 179 | Content-Type: application/example 180 | If-Match: "e0023aa4e" 181 | Content-Length: 10 182 | 183 | cccccccccc 184 | ``` 185 | 186 | ```log 187 | off=0 message begin 188 | off=0 len=5 span[method]="PATCH" 189 | off=5 method complete 190 | off=6 len=9 span[url]="/file.txt" 191 | off=16 url complete 192 | off=16 len=4 span[protocol]="HTTP" 193 | off=20 protocol complete 194 | off=21 len=3 span[version]="1.1" 195 | off=24 version complete 196 | off=26 len=4 span[header_field]="Host" 197 | off=31 header_field complete 198 | off=32 len=15 span[header_value]="www.example.com" 199 | off=49 header_value complete 200 | off=49 len=12 span[header_field]="Content-Type" 201 | off=62 header_field complete 202 | off=63 len=19 span[header_value]="application/example" 203 | off=84 header_value complete 204 | off=84 len=8 span[header_field]="If-Match" 205 | off=93 header_field complete 206 | off=94 len=11 span[header_value]=""e0023aa4e"" 207 | off=107 header_value complete 208 | off=107 len=14 span[header_field]="Content-Length" 209 | off=122 header_field complete 210 | off=123 len=2 span[header_value]="10" 211 | off=127 header_value complete 212 | off=129 headers complete method=28 v=1/1 flags=20 content_length=10 213 | off=129 len=10 span[body]="cccccccccc" 214 | off=139 message complete 215 | ``` 216 | 217 | ### PURGE request 218 | 219 | 220 | ```http 221 | PURGE /file.txt HTTP/1.1 222 | Host: www.example.com 223 | 224 | 225 | ``` 226 | 227 | ```log 228 | off=0 message begin 229 | off=0 len=5 span[method]="PURGE" 230 | off=5 method complete 231 | off=6 len=9 span[url]="/file.txt" 232 | off=16 url complete 233 | off=16 len=4 span[protocol]="HTTP" 234 | off=20 protocol complete 235 | off=21 len=3 span[version]="1.1" 236 | off=24 version complete 237 | off=26 len=4 span[header_field]="Host" 238 | off=31 header_field complete 239 | off=32 len=15 span[header_value]="www.example.com" 240 | off=49 header_value complete 241 | off=51 headers complete method=29 v=1/1 flags=0 content_length=0 242 | off=51 message complete 243 | ``` 244 | 245 | ### SEARCH request 246 | 247 | 248 | ```http 249 | SEARCH / HTTP/1.1 250 | Host: www.example.com 251 | 252 | 253 | ``` 254 | 255 | ```log 256 | off=0 message begin 257 | off=0 len=6 span[method]="SEARCH" 258 | off=6 method complete 259 | off=7 len=1 span[url]="/" 260 | off=9 url complete 261 | off=9 len=4 span[protocol]="HTTP" 262 | off=13 protocol complete 263 | off=14 len=3 span[version]="1.1" 264 | off=17 version complete 265 | off=19 len=4 span[header_field]="Host" 266 | off=24 header_field complete 267 | off=25 len=15 span[header_value]="www.example.com" 268 | off=42 header_value complete 269 | off=44 headers complete method=14 v=1/1 flags=0 content_length=0 270 | off=44 message complete 271 | ``` 272 | 273 | ### LINK request 274 | 275 | 276 | ```http 277 | LINK /images/my_dog.jpg HTTP/1.1 278 | Host: example.com 279 | Link: ; rel="tag" 280 | Link: ; rel="tag" 281 | 282 | 283 | ``` 284 | 285 | ```log 286 | off=0 message begin 287 | off=0 len=4 span[method]="LINK" 288 | off=4 method complete 289 | off=5 len=18 span[url]="/images/my_dog.jpg" 290 | off=24 url complete 291 | off=24 len=4 span[protocol]="HTTP" 292 | off=28 protocol complete 293 | off=29 len=3 span[version]="1.1" 294 | off=32 version complete 295 | off=34 len=4 span[header_field]="Host" 296 | off=39 header_field complete 297 | off=40 len=11 span[header_value]="example.com" 298 | off=53 header_value complete 299 | off=53 len=4 span[header_field]="Link" 300 | off=58 header_field complete 301 | off=59 len=44 span[header_value]="; rel="tag"" 302 | off=105 header_value complete 303 | off=105 len=4 span[header_field]="Link" 304 | off=110 header_field complete 305 | off=111 len=46 span[header_value]="; rel="tag"" 306 | off=159 header_value complete 307 | off=161 headers complete method=31 v=1/1 flags=0 content_length=0 308 | off=161 message complete 309 | ``` 310 | 311 | ### LINK request 312 | 313 | 314 | ```http 315 | UNLINK /images/my_dog.jpg HTTP/1.1 316 | Host: example.com 317 | Link: ; rel="tag" 318 | 319 | 320 | ``` 321 | 322 | ```log 323 | off=0 message begin 324 | off=0 len=6 span[method]="UNLINK" 325 | off=6 method complete 326 | off=7 len=18 span[url]="/images/my_dog.jpg" 327 | off=26 url complete 328 | off=26 len=4 span[protocol]="HTTP" 329 | off=30 protocol complete 330 | off=31 len=3 span[version]="1.1" 331 | off=34 version complete 332 | off=36 len=4 span[header_field]="Host" 333 | off=41 header_field complete 334 | off=42 len=11 span[header_value]="example.com" 335 | off=55 header_value complete 336 | off=55 len=4 span[header_field]="Link" 337 | off=60 header_field complete 338 | off=61 len=46 span[header_value]="; rel="tag"" 339 | off=109 header_value complete 340 | off=111 headers complete method=32 v=1/1 flags=0 content_length=0 341 | off=111 message complete 342 | ``` 343 | 344 | ### SOURCE request 345 | 346 | 347 | ```http 348 | SOURCE /music/sweet/music HTTP/1.1 349 | Host: example.com 350 | 351 | 352 | ``` 353 | 354 | ```log 355 | off=0 message begin 356 | off=0 len=6 span[method]="SOURCE" 357 | off=6 method complete 358 | off=7 len=18 span[url]="/music/sweet/music" 359 | off=26 url complete 360 | off=26 len=4 span[protocol]="HTTP" 361 | off=30 protocol complete 362 | off=31 len=3 span[version]="1.1" 363 | off=34 version complete 364 | off=36 len=4 span[header_field]="Host" 365 | off=41 header_field complete 366 | off=42 len=11 span[header_value]="example.com" 367 | off=55 header_value complete 368 | off=57 headers complete method=33 v=1/1 flags=0 content_length=0 369 | off=57 message complete 370 | ``` 371 | 372 | ### SOURCE request with ICE 373 | 374 | 375 | ```http 376 | SOURCE /music/sweet/music ICE/1.0 377 | Host: example.com 378 | 379 | 380 | ``` 381 | 382 | ```log 383 | off=0 message begin 384 | off=0 len=6 span[method]="SOURCE" 385 | off=6 method complete 386 | off=7 len=18 span[url]="/music/sweet/music" 387 | off=26 url complete 388 | off=26 len=3 span[protocol]="ICE" 389 | off=29 protocol complete 390 | off=30 len=3 span[version]="1.0" 391 | off=33 version complete 392 | off=35 len=4 span[header_field]="Host" 393 | off=40 header_field complete 394 | off=41 len=11 span[header_value]="example.com" 395 | off=54 header_value complete 396 | off=56 headers complete method=33 v=1/0 flags=0 content_length=0 397 | off=56 message complete 398 | ``` 399 | 400 | ### OPTIONS request with RTSP 401 | 402 | NOTE: `OPTIONS` is a valid HTTP metho too. 403 | 404 | 405 | ```http 406 | OPTIONS /music/sweet/music RTSP/1.0 407 | Host: example.com 408 | 409 | 410 | ``` 411 | 412 | ```log 413 | off=0 message begin 414 | off=0 len=7 span[method]="OPTIONS" 415 | off=7 method complete 416 | off=8 len=18 span[url]="/music/sweet/music" 417 | off=27 url complete 418 | off=27 len=4 span[protocol]="RTSP" 419 | off=31 protocol complete 420 | off=32 len=3 span[version]="1.0" 421 | off=35 version complete 422 | off=37 len=4 span[header_field]="Host" 423 | off=42 header_field complete 424 | off=43 len=11 span[header_value]="example.com" 425 | off=56 header_value complete 426 | off=58 headers complete method=6 v=1/0 flags=0 content_length=0 427 | off=58 message complete 428 | ``` 429 | 430 | ### ANNOUNCE request with RTSP 431 | 432 | 433 | ```http 434 | ANNOUNCE /music/sweet/music RTSP/1.0 435 | Host: example.com 436 | 437 | 438 | ``` 439 | 440 | ```log 441 | off=0 message begin 442 | off=0 len=8 span[method]="ANNOUNCE" 443 | off=8 method complete 444 | off=9 len=18 span[url]="/music/sweet/music" 445 | off=28 url complete 446 | off=28 len=4 span[protocol]="RTSP" 447 | off=32 protocol complete 448 | off=33 len=3 span[version]="1.0" 449 | off=36 version complete 450 | off=38 len=4 span[header_field]="Host" 451 | off=43 header_field complete 452 | off=44 len=11 span[header_value]="example.com" 453 | off=57 header_value complete 454 | off=59 headers complete method=36 v=1/0 flags=0 content_length=0 455 | off=59 message complete 456 | ``` 457 | 458 | ### PRI request HTTP2 459 | 460 | 461 | ```http 462 | PRI * HTTP/1.1 463 | 464 | SM 465 | 466 | 467 | ``` 468 | 469 | ```log 470 | off=0 message begin 471 | off=0 len=3 span[method]="PRI" 472 | off=3 method complete 473 | off=4 len=1 span[url]="*" 474 | off=6 url complete 475 | off=6 len=4 span[protocol]="HTTP" 476 | off=10 protocol complete 477 | off=11 len=3 span[version]="1.1" 478 | off=14 version complete 479 | off=24 error code=23 reason="Pause on PRI/Upgrade" 480 | ``` 481 | 482 | ### QUERY request 483 | 484 | 485 | ```http 486 | QUERY /contacts HTTP/1.1 487 | Host: example.org 488 | Content-Type: example/query 489 | Accept: text/csv 490 | Content-Length: 41 491 | 492 | select surname, givenname, email limit 10 493 | ``` 494 | 495 | ```log 496 | off=0 message begin 497 | off=0 len=5 span[method]="QUERY" 498 | off=5 method complete 499 | off=6 len=9 span[url]="/contacts" 500 | off=16 url complete 501 | off=16 len=4 span[protocol]="HTTP" 502 | off=20 protocol complete 503 | off=21 len=3 span[version]="1.1" 504 | off=24 version complete 505 | off=26 len=4 span[header_field]="Host" 506 | off=31 header_field complete 507 | off=32 len=11 span[header_value]="example.org" 508 | off=45 header_value complete 509 | off=45 len=12 span[header_field]="Content-Type" 510 | off=58 header_field complete 511 | off=59 len=13 span[header_value]="example/query" 512 | off=74 header_value complete 513 | off=74 len=6 span[header_field]="Accept" 514 | off=81 header_field complete 515 | off=82 len=8 span[header_value]="text/csv" 516 | off=92 header_value complete 517 | off=92 len=14 span[header_field]="Content-Length" 518 | off=107 header_field complete 519 | off=108 len=2 span[header_value]="41" 520 | off=112 header_value complete 521 | off=114 headers complete method=46 v=1/1 flags=20 content_length=41 522 | off=114 len=41 span[body]="select surname, givenname, email limit 10" 523 | off=155 message complete 524 | ``` 525 | -------------------------------------------------------------------------------- /test/request/pausing.md: -------------------------------------------------------------------------------- 1 | Pausing 2 | ======= 3 | 4 | ### on_message_begin 5 | 6 | 7 | ```http 8 | POST / HTTP/1.1 9 | Content-Length: 3 10 | 11 | abc 12 | ``` 13 | 14 | ```log 15 | off=0 message begin 16 | off=0 pause 17 | off=0 len=4 span[method]="POST" 18 | off=4 method complete 19 | off=5 len=1 span[url]="/" 20 | off=7 url complete 21 | off=7 len=4 span[protocol]="HTTP" 22 | off=11 protocol complete 23 | off=12 len=3 span[version]="1.1" 24 | off=15 version complete 25 | off=17 len=14 span[header_field]="Content-Length" 26 | off=32 header_field complete 27 | off=33 len=1 span[header_value]="3" 28 | off=36 header_value complete 29 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 30 | off=38 len=3 span[body]="abc" 31 | off=41 message complete 32 | ``` 33 | 34 | ### on_message_complete 35 | 36 | 37 | ```http 38 | POST / HTTP/1.1 39 | Content-Length: 3 40 | 41 | abc 42 | ``` 43 | 44 | ```log 45 | off=0 message begin 46 | off=0 len=4 span[method]="POST" 47 | off=4 method complete 48 | off=5 len=1 span[url]="/" 49 | off=7 url complete 50 | off=7 len=4 span[protocol]="HTTP" 51 | off=11 protocol complete 52 | off=12 len=3 span[version]="1.1" 53 | off=15 version complete 54 | off=17 len=14 span[header_field]="Content-Length" 55 | off=32 header_field complete 56 | off=33 len=1 span[header_value]="3" 57 | off=36 header_value complete 58 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 59 | off=38 len=3 span[body]="abc" 60 | off=41 message complete 61 | off=41 pause 62 | ``` 63 | 64 | ### on_protocol_complete 65 | 66 | 67 | ```http 68 | POST / HTTP/1.1 69 | Content-Length: 3 70 | 71 | abc 72 | ``` 73 | 74 | ```log 75 | off=0 message begin 76 | off=0 len=4 span[method]="POST" 77 | off=4 method complete 78 | off=5 len=1 span[url]="/" 79 | off=7 url complete 80 | off=7 len=4 span[protocol]="HTTP" 81 | off=11 protocol complete 82 | off=11 pause 83 | off=12 len=3 span[version]="1.1" 84 | off=15 version complete 85 | off=17 len=14 span[header_field]="Content-Length" 86 | off=32 header_field complete 87 | off=33 len=1 span[header_value]="3" 88 | off=36 header_value complete 89 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 90 | off=38 len=3 span[body]="abc" 91 | off=41 message complete 92 | ``` 93 | 94 | ### on_method_complete 95 | 96 | 97 | ```http 98 | POST / HTTP/1.1 99 | Content-Length: 3 100 | 101 | abc 102 | ``` 103 | 104 | ```log 105 | off=0 message begin 106 | off=0 len=4 span[method]="POST" 107 | off=4 method complete 108 | off=4 pause 109 | off=5 len=1 span[url]="/" 110 | off=7 url complete 111 | off=7 len=4 span[protocol]="HTTP" 112 | off=11 protocol complete 113 | off=12 len=3 span[version]="1.1" 114 | off=15 version complete 115 | off=17 len=14 span[header_field]="Content-Length" 116 | off=32 header_field complete 117 | off=33 len=1 span[header_value]="3" 118 | off=36 header_value complete 119 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 120 | off=38 len=3 span[body]="abc" 121 | off=41 message complete 122 | ``` 123 | 124 | ### on_url_complete 125 | 126 | 127 | ```http 128 | POST / HTTP/1.1 129 | Content-Length: 3 130 | 131 | abc 132 | ``` 133 | 134 | ```log 135 | off=0 message begin 136 | off=0 len=4 span[method]="POST" 137 | off=4 method complete 138 | off=5 len=1 span[url]="/" 139 | off=7 url complete 140 | off=7 pause 141 | off=7 len=4 span[protocol]="HTTP" 142 | off=11 protocol complete 143 | off=12 len=3 span[version]="1.1" 144 | off=15 version complete 145 | off=17 len=14 span[header_field]="Content-Length" 146 | off=32 header_field complete 147 | off=33 len=1 span[header_value]="3" 148 | off=36 header_value complete 149 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 150 | off=38 len=3 span[body]="abc" 151 | off=41 message complete 152 | ``` 153 | 154 | ### on_version_complete 155 | 156 | 157 | ```http 158 | POST / HTTP/1.1 159 | Content-Length: 3 160 | 161 | abc 162 | ``` 163 | 164 | ```log 165 | off=0 message begin 166 | off=0 len=4 span[method]="POST" 167 | off=4 method complete 168 | off=5 len=1 span[url]="/" 169 | off=7 url complete 170 | off=7 len=4 span[protocol]="HTTP" 171 | off=11 protocol complete 172 | off=12 len=3 span[version]="1.1" 173 | off=15 version complete 174 | off=15 pause 175 | off=17 len=14 span[header_field]="Content-Length" 176 | off=32 header_field complete 177 | off=33 len=1 span[header_value]="3" 178 | off=36 header_value complete 179 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 180 | off=38 len=3 span[body]="abc" 181 | off=41 message complete 182 | ``` 183 | 184 | ### on_header_field_complete 185 | 186 | 187 | ```http 188 | POST / HTTP/1.1 189 | Content-Length: 3 190 | 191 | abc 192 | ``` 193 | 194 | ```log 195 | off=0 message begin 196 | off=0 len=4 span[method]="POST" 197 | off=4 method complete 198 | off=5 len=1 span[url]="/" 199 | off=7 url complete 200 | off=7 len=4 span[protocol]="HTTP" 201 | off=11 protocol complete 202 | off=12 len=3 span[version]="1.1" 203 | off=15 version complete 204 | off=17 len=14 span[header_field]="Content-Length" 205 | off=32 header_field complete 206 | off=32 pause 207 | off=33 len=1 span[header_value]="3" 208 | off=36 header_value complete 209 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 210 | off=38 len=3 span[body]="abc" 211 | off=41 message complete 212 | ``` 213 | 214 | ### on_header_value_complete 215 | 216 | 217 | ```http 218 | POST / HTTP/1.1 219 | Content-Length: 3 220 | 221 | abc 222 | ``` 223 | 224 | ```log 225 | off=0 message begin 226 | off=0 len=4 span[method]="POST" 227 | off=4 method complete 228 | off=5 len=1 span[url]="/" 229 | off=7 url complete 230 | off=7 len=4 span[protocol]="HTTP" 231 | off=11 protocol complete 232 | off=12 len=3 span[version]="1.1" 233 | off=15 version complete 234 | off=17 len=14 span[header_field]="Content-Length" 235 | off=32 header_field complete 236 | off=33 len=1 span[header_value]="3" 237 | off=36 header_value complete 238 | off=36 pause 239 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 240 | off=38 len=3 span[body]="abc" 241 | off=41 message complete 242 | ``` 243 | 244 | ### on_headers_complete 245 | 246 | 247 | ```http 248 | POST / HTTP/1.1 249 | Content-Length: 3 250 | 251 | abc 252 | ``` 253 | 254 | ```log 255 | off=0 message begin 256 | off=0 len=4 span[method]="POST" 257 | off=4 method complete 258 | off=5 len=1 span[url]="/" 259 | off=7 url complete 260 | off=7 len=4 span[protocol]="HTTP" 261 | off=11 protocol complete 262 | off=12 len=3 span[version]="1.1" 263 | off=15 version complete 264 | off=17 len=14 span[header_field]="Content-Length" 265 | off=32 header_field complete 266 | off=33 len=1 span[header_value]="3" 267 | off=36 header_value complete 268 | off=38 headers complete method=3 v=1/1 flags=20 content_length=3 269 | off=38 pause 270 | off=38 len=3 span[body]="abc" 271 | off=41 message complete 272 | ``` 273 | 274 | ### on_chunk_header 275 | 276 | 277 | ```http 278 | PUT / HTTP/1.1 279 | Transfer-Encoding: chunked 280 | 281 | a 282 | 0123456789 283 | 0 284 | 285 | 286 | ``` 287 | 288 | ```log 289 | off=0 message begin 290 | off=0 len=3 span[method]="PUT" 291 | off=3 method complete 292 | off=4 len=1 span[url]="/" 293 | off=6 url complete 294 | off=6 len=4 span[protocol]="HTTP" 295 | off=10 protocol complete 296 | off=11 len=3 span[version]="1.1" 297 | off=14 version complete 298 | off=16 len=17 span[header_field]="Transfer-Encoding" 299 | off=34 header_field complete 300 | off=35 len=7 span[header_value]="chunked" 301 | off=44 header_value complete 302 | off=46 headers complete method=4 v=1/1 flags=208 content_length=0 303 | off=49 chunk header len=10 304 | off=49 pause 305 | off=49 len=10 span[body]="0123456789" 306 | off=61 chunk complete 307 | off=64 chunk header len=0 308 | off=64 pause 309 | off=66 chunk complete 310 | off=66 message complete 311 | ``` 312 | 313 | ### on_chunk_extension_name 314 | 315 | 316 | ```http 317 | PUT / HTTP/1.1 318 | Transfer-Encoding: chunked 319 | 320 | a;foo=bar 321 | 0123456789 322 | 0 323 | 324 | 325 | ``` 326 | 327 | ```log 328 | off=0 message begin 329 | off=0 len=3 span[method]="PUT" 330 | off=3 method complete 331 | off=4 len=1 span[url]="/" 332 | off=6 url complete 333 | off=6 len=4 span[protocol]="HTTP" 334 | off=10 protocol complete 335 | off=11 len=3 span[version]="1.1" 336 | off=14 version complete 337 | off=16 len=17 span[header_field]="Transfer-Encoding" 338 | off=34 header_field complete 339 | off=35 len=7 span[header_value]="chunked" 340 | off=44 header_value complete 341 | off=46 headers complete method=4 v=1/1 flags=208 content_length=0 342 | off=48 len=3 span[chunk_extension_name]="foo" 343 | off=52 chunk_extension_name complete 344 | off=52 pause 345 | off=52 len=3 span[chunk_extension_value]="bar" 346 | off=56 chunk_extension_value complete 347 | off=57 chunk header len=10 348 | off=57 len=10 span[body]="0123456789" 349 | off=69 chunk complete 350 | off=72 chunk header len=0 351 | off=74 chunk complete 352 | off=74 message complete 353 | ``` 354 | 355 | ### on_chunk_extension_value 356 | 357 | 358 | ```http 359 | PUT / HTTP/1.1 360 | Transfer-Encoding: chunked 361 | 362 | a;foo=bar 363 | 0123456789 364 | 0 365 | 366 | 367 | ``` 368 | 369 | ```log 370 | off=0 message begin 371 | off=0 len=3 span[method]="PUT" 372 | off=3 method complete 373 | off=4 len=1 span[url]="/" 374 | off=6 url complete 375 | off=6 len=4 span[protocol]="HTTP" 376 | off=10 protocol complete 377 | off=11 len=3 span[version]="1.1" 378 | off=14 version complete 379 | off=16 len=17 span[header_field]="Transfer-Encoding" 380 | off=34 header_field complete 381 | off=35 len=7 span[header_value]="chunked" 382 | off=44 header_value complete 383 | off=46 headers complete method=4 v=1/1 flags=208 content_length=0 384 | off=48 len=3 span[chunk_extension_name]="foo" 385 | off=52 chunk_extension_name complete 386 | off=52 len=3 span[chunk_extension_value]="bar" 387 | off=56 chunk_extension_value complete 388 | off=56 pause 389 | off=57 chunk header len=10 390 | off=57 len=10 span[body]="0123456789" 391 | off=69 chunk complete 392 | off=72 chunk header len=0 393 | off=74 chunk complete 394 | off=74 message complete 395 | ``` 396 | 397 | 398 | ### on_chunk_complete 399 | 400 | 401 | ```http 402 | PUT / HTTP/1.1 403 | Transfer-Encoding: chunked 404 | 405 | a 406 | 0123456789 407 | 0 408 | 409 | 410 | ``` 411 | 412 | ```log 413 | off=0 message begin 414 | off=0 len=3 span[method]="PUT" 415 | off=3 method complete 416 | off=4 len=1 span[url]="/" 417 | off=6 url complete 418 | off=6 len=4 span[protocol]="HTTP" 419 | off=10 protocol complete 420 | off=11 len=3 span[version]="1.1" 421 | off=14 version complete 422 | off=16 len=17 span[header_field]="Transfer-Encoding" 423 | off=34 header_field complete 424 | off=35 len=7 span[header_value]="chunked" 425 | off=44 header_value complete 426 | off=46 headers complete method=4 v=1/1 flags=208 content_length=0 427 | off=49 chunk header len=10 428 | off=49 len=10 span[body]="0123456789" 429 | off=61 chunk complete 430 | off=61 pause 431 | off=64 chunk header len=0 432 | off=66 chunk complete 433 | off=66 pause 434 | off=66 message complete 435 | ``` 436 | -------------------------------------------------------------------------------- /test/request/pipelining.md: -------------------------------------------------------------------------------- 1 | Pipelining 2 | ========== 3 | 4 | ## Should parse multiple events 5 | 6 | 7 | ```http 8 | POST /aaa HTTP/1.1 9 | Content-Length: 3 10 | 11 | AAA 12 | PUT /bbb HTTP/1.1 13 | Content-Length: 4 14 | 15 | BBBB 16 | PATCH /ccc HTTP/1.1 17 | Content-Length: 5 18 | 19 | CCCC 20 | ``` 21 | 22 | ```log 23 | off=0 message begin 24 | off=0 len=4 span[method]="POST" 25 | off=4 method complete 26 | off=5 len=4 span[url]="/aaa" 27 | off=10 url complete 28 | off=10 len=4 span[protocol]="HTTP" 29 | off=14 protocol complete 30 | off=15 len=3 span[version]="1.1" 31 | off=18 version complete 32 | off=20 len=14 span[header_field]="Content-Length" 33 | off=35 header_field complete 34 | off=36 len=1 span[header_value]="3" 35 | off=39 header_value complete 36 | off=41 headers complete method=3 v=1/1 flags=20 content_length=3 37 | off=41 len=3 span[body]="AAA" 38 | off=44 message complete 39 | off=46 reset 40 | off=46 message begin 41 | off=46 len=3 span[method]="PUT" 42 | off=49 method complete 43 | off=50 len=4 span[url]="/bbb" 44 | off=55 url complete 45 | off=55 len=4 span[protocol]="HTTP" 46 | off=59 protocol complete 47 | off=60 len=3 span[version]="1.1" 48 | off=63 version complete 49 | off=65 len=14 span[header_field]="Content-Length" 50 | off=80 header_field complete 51 | off=81 len=1 span[header_value]="4" 52 | off=84 header_value complete 53 | off=86 headers complete method=4 v=1/1 flags=20 content_length=4 54 | off=86 len=4 span[body]="BBBB" 55 | off=90 message complete 56 | off=92 reset 57 | off=92 message begin 58 | off=92 len=5 span[method]="PATCH" 59 | off=97 method complete 60 | off=98 len=4 span[url]="/ccc" 61 | off=103 url complete 62 | off=103 len=4 span[protocol]="HTTP" 63 | off=107 protocol complete 64 | off=108 len=3 span[version]="1.1" 65 | off=111 version complete 66 | off=113 len=14 span[header_field]="Content-Length" 67 | off=128 header_field complete 68 | off=129 len=1 span[header_value]="5" 69 | off=132 header_value complete 70 | off=134 headers complete method=28 v=1/1 flags=20 content_length=5 71 | off=134 len=4 span[body]="CCCC" 72 | ``` -------------------------------------------------------------------------------- /test/request/uri.md: -------------------------------------------------------------------------------- 1 | URI 2 | === 3 | 4 | ## Quotes in URI 5 | 6 | 7 | ```http 8 | GET /with_"lovely"_quotes?foo=\"bar\" HTTP/1.1 9 | 10 | 11 | ``` 12 | 13 | ```log 14 | off=0 message begin 15 | off=0 len=3 span[method]="GET" 16 | off=3 method complete 17 | off=4 len=33 span[url]="/with_"lovely"_quotes?foo=\"bar\"" 18 | off=38 url complete 19 | off=38 len=4 span[protocol]="HTTP" 20 | off=42 protocol complete 21 | off=43 len=3 span[version]="1.1" 22 | off=46 version complete 23 | off=50 headers complete method=1 v=1/1 flags=0 content_length=0 24 | off=50 message complete 25 | ``` 26 | 27 | ## Query URL with question mark 28 | 29 | Some clients include `?` characters in query strings. 30 | 31 | 32 | ```http 33 | GET /test.cgi?foo=bar?baz HTTP/1.1 34 | 35 | 36 | ``` 37 | 38 | ```log 39 | off=0 message begin 40 | off=0 len=3 span[method]="GET" 41 | off=3 method complete 42 | off=4 len=21 span[url]="/test.cgi?foo=bar?baz" 43 | off=26 url complete 44 | off=26 len=4 span[protocol]="HTTP" 45 | off=30 protocol complete 46 | off=31 len=3 span[version]="1.1" 47 | off=34 version complete 48 | off=38 headers complete method=1 v=1/1 flags=0 content_length=0 49 | off=38 message complete 50 | ``` 51 | 52 | ## Host terminated by a query string 53 | 54 | 55 | ```http 56 | GET http://hypnotoad.org?hail=all HTTP/1.1\r\n 57 | 58 | 59 | ``` 60 | 61 | ```log 62 | off=0 message begin 63 | off=0 len=3 span[method]="GET" 64 | off=3 method complete 65 | off=4 len=29 span[url]="http://hypnotoad.org?hail=all" 66 | off=34 url complete 67 | off=34 len=4 span[protocol]="HTTP" 68 | off=38 protocol complete 69 | off=39 len=3 span[version]="1.1" 70 | off=42 version complete 71 | off=46 headers complete method=1 v=1/1 flags=0 content_length=0 72 | off=46 message complete 73 | ``` 74 | 75 | ## `host:port` terminated by a query string 76 | 77 | 78 | ```http 79 | GET http://hypnotoad.org:1234?hail=all HTTP/1.1 80 | 81 | 82 | ``` 83 | 84 | ```log 85 | off=0 message begin 86 | off=0 len=3 span[method]="GET" 87 | off=3 method complete 88 | off=4 len=34 span[url]="http://hypnotoad.org:1234?hail=all" 89 | off=39 url complete 90 | off=39 len=4 span[protocol]="HTTP" 91 | off=43 protocol complete 92 | off=44 len=3 span[version]="1.1" 93 | off=47 version complete 94 | off=51 headers complete method=1 v=1/1 flags=0 content_length=0 95 | off=51 message complete 96 | ``` 97 | 98 | ## Query URL with vertical bar character 99 | 100 | It should be allowed to have vertical bar symbol in URI: `|`. 101 | 102 | See: https://github.com/nodejs/node/issues/27584 103 | 104 | 105 | ```http 106 | GET /test.cgi?query=| HTTP/1.1 107 | 108 | 109 | ``` 110 | 111 | ```log 112 | off=0 message begin 113 | off=0 len=3 span[method]="GET" 114 | off=3 method complete 115 | off=4 len=17 span[url]="/test.cgi?query=|" 116 | off=22 url complete 117 | off=22 len=4 span[protocol]="HTTP" 118 | off=26 protocol complete 119 | off=27 len=3 span[version]="1.1" 120 | off=30 version complete 121 | off=34 headers complete method=1 v=1/1 flags=0 content_length=0 122 | off=34 message complete 123 | ``` 124 | 125 | ## `host:port` terminated by a space 126 | 127 | 128 | ```http 129 | GET http://hypnotoad.org:1234 HTTP/1.1 130 | 131 | 132 | ``` 133 | 134 | ```log 135 | off=0 message begin 136 | off=0 len=3 span[method]="GET" 137 | off=3 method complete 138 | off=4 len=25 span[url]="http://hypnotoad.org:1234" 139 | off=30 url complete 140 | off=30 len=4 span[protocol]="HTTP" 141 | off=34 protocol complete 142 | off=35 len=3 span[version]="1.1" 143 | off=38 version complete 144 | off=42 headers complete method=1 v=1/1 flags=0 content_length=0 145 | off=42 message complete 146 | ``` 147 | 148 | ## Disallow UTF-8 in URI path in strict mode 149 | 150 | 151 | ```http 152 | GET /δ¶/δt/pope?q=1#narf HTTP/1.1 153 | Host: github.com 154 | 155 | 156 | ``` 157 | 158 | ```log 159 | off=0 message begin 160 | off=0 len=3 span[method]="GET" 161 | off=3 method complete 162 | off=5 error code=7 reason="Invalid char in url path" 163 | ``` 164 | 165 | ## Fragment in URI 166 | 167 | 168 | ```http 169 | GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1 170 | 171 | 172 | ``` 173 | 174 | ```log 175 | off=0 message begin 176 | off=0 len=3 span[method]="GET" 177 | off=3 method complete 178 | off=4 len=40 span[url]="/forums/1/topics/2375?page=1#posts-17408" 179 | off=45 url complete 180 | off=45 len=4 span[protocol]="HTTP" 181 | off=49 protocol complete 182 | off=50 len=3 span[version]="1.1" 183 | off=53 version complete 184 | off=57 headers complete method=1 v=1/1 flags=0 content_length=0 185 | off=57 message complete 186 | ``` 187 | 188 | ## Underscore in hostname 189 | 190 | 191 | ```http 192 | CONNECT home_0.netscape.com:443 HTTP/1.0 193 | User-agent: Mozilla/1.1N 194 | Proxy-authorization: basic aGVsbG86d29ybGQ= 195 | 196 | 197 | ``` 198 | 199 | ```log 200 | off=0 message begin 201 | off=0 len=7 span[method]="CONNECT" 202 | off=7 method complete 203 | off=8 len=23 span[url]="home_0.netscape.com:443" 204 | off=32 url complete 205 | off=32 len=4 span[protocol]="HTTP" 206 | off=36 protocol complete 207 | off=37 len=3 span[version]="1.0" 208 | off=40 version complete 209 | off=42 len=10 span[header_field]="User-agent" 210 | off=53 header_field complete 211 | off=54 len=12 span[header_value]="Mozilla/1.1N" 212 | off=68 header_value complete 213 | off=68 len=19 span[header_field]="Proxy-authorization" 214 | off=88 header_field complete 215 | off=89 len=22 span[header_value]="basic aGVsbG86d29ybGQ=" 216 | off=113 header_value complete 217 | off=115 headers complete method=5 v=1/0 flags=0 content_length=0 218 | off=115 message complete 219 | off=115 error code=22 reason="Pause on CONNECT/Upgrade" 220 | ``` 221 | 222 | ## `host:port` and basic auth 223 | 224 | 225 | ```http 226 | GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1 227 | 228 | 229 | ``` 230 | 231 | ```log 232 | off=0 message begin 233 | off=0 len=3 span[method]="GET" 234 | off=3 method complete 235 | off=4 len=41 span[url]="http://a%12:b!&*$@hypnotoad.org:1234/toto" 236 | off=46 url complete 237 | off=46 len=4 span[protocol]="HTTP" 238 | off=50 protocol complete 239 | off=51 len=3 span[version]="1.1" 240 | off=54 version complete 241 | off=58 headers complete method=1 v=1/1 flags=0 content_length=0 242 | off=58 message complete 243 | ``` 244 | 245 | ## Space in URI 246 | 247 | 248 | ```http 249 | GET /foo bar/ HTTP/1.1 250 | 251 | 252 | ``` 253 | 254 | ```log 255 | off=0 message begin 256 | off=0 len=3 span[method]="GET" 257 | off=3 method complete 258 | off=4 len=4 span[url]="/foo" 259 | off=9 url complete 260 | off=9 len=0 span[protocol]="" 261 | off=9 error code=8 reason="Expected HTTP/, RTSP/ or ICE/" 262 | ``` 263 | -------------------------------------------------------------------------------- /test/response/content-length.md: -------------------------------------------------------------------------------- 1 | Content-Length header 2 | ===================== 3 | 4 | ## Response without `Content-Length`, but with body 5 | 6 | The client should wait for the server's EOF. That is, when 7 | `Content-Length` is not specified, and `Connection: close`, the end of body is 8 | specified by the EOF. 9 | 10 | _(Compare with APACHEBENCH_GET)_ 11 | 12 | 13 | ```http 14 | HTTP/1.1 200 OK 15 | Date: Tue, 04 Aug 2009 07:59:32 GMT 16 | Server: Apache 17 | X-Powered-By: Servlet/2.5 JSP/2.1 18 | Content-Type: text/xml; charset=utf-8 19 | Connection: close 20 | 21 | \n\ 22 | \n\ 23 | \n\ 24 | \n\ 25 | SOAP-ENV:Client\n\ 26 | Client Error\n\ 27 | \n\ 28 | \n\ 29 | 30 | ``` 31 | 32 | ```log 33 | off=0 message begin 34 | off=0 len=4 span[protocol]="HTTP" 35 | off=4 protocol complete 36 | off=5 len=3 span[version]="1.1" 37 | off=8 version complete 38 | off=13 len=2 span[status]="OK" 39 | off=17 status complete 40 | off=17 len=4 span[header_field]="Date" 41 | off=22 header_field complete 42 | off=23 len=29 span[header_value]="Tue, 04 Aug 2009 07:59:32 GMT" 43 | off=54 header_value complete 44 | off=54 len=6 span[header_field]="Server" 45 | off=61 header_field complete 46 | off=62 len=6 span[header_value]="Apache" 47 | off=70 header_value complete 48 | off=70 len=12 span[header_field]="X-Powered-By" 49 | off=83 header_field complete 50 | off=84 len=19 span[header_value]="Servlet/2.5 JSP/2.1" 51 | off=105 header_value complete 52 | off=105 len=12 span[header_field]="Content-Type" 53 | off=118 header_field complete 54 | off=119 len=23 span[header_value]="text/xml; charset=utf-8" 55 | off=144 header_value complete 56 | off=144 len=10 span[header_field]="Connection" 57 | off=155 header_field complete 58 | off=156 len=5 span[header_value]="close" 59 | off=163 header_value complete 60 | off=165 headers complete status=200 v=1/1 flags=2 content_length=0 61 | off=165 len=42 span[body]="" 62 | off=207 len=1 span[body]=lf 63 | off=208 len=80 span[body]="" 64 | off=288 len=1 span[body]=lf 65 | off=289 len=17 span[body]=" " 66 | off=306 len=1 span[body]=lf 67 | off=307 len=20 span[body]=" " 68 | off=327 len=1 span[body]=lf 69 | off=328 len=45 span[body]=" SOAP-ENV:Client" 70 | off=373 len=1 span[body]=lf 71 | off=374 len=46 span[body]=" Client Error" 72 | off=420 len=1 span[body]=lf 73 | off=421 len=21 span[body]=" " 74 | off=442 len=1 span[body]=lf 75 | off=443 len=18 span[body]=" " 76 | off=461 len=1 span[body]=lf 77 | off=462 len=20 span[body]="" 78 | ``` 79 | 80 | ## Content-Length-X 81 | 82 | The header that starts with `Content-Length*` should not be treated as 83 | `Content-Length`. 84 | 85 | 86 | ```http 87 | HTTP/1.1 200 OK 88 | Content-Length-X: 0 89 | Transfer-Encoding: chunked 90 | 91 | 2 92 | OK 93 | 0 94 | 95 | 96 | ``` 97 | 98 | ```log 99 | off=0 message begin 100 | off=0 len=4 span[protocol]="HTTP" 101 | off=4 protocol complete 102 | off=5 len=3 span[version]="1.1" 103 | off=8 version complete 104 | off=13 len=2 span[status]="OK" 105 | off=17 status complete 106 | off=17 len=16 span[header_field]="Content-Length-X" 107 | off=34 header_field complete 108 | off=35 len=1 span[header_value]="0" 109 | off=38 header_value complete 110 | off=38 len=17 span[header_field]="Transfer-Encoding" 111 | off=56 header_field complete 112 | off=57 len=7 span[header_value]="chunked" 113 | off=66 header_value complete 114 | off=68 headers complete status=200 v=1/1 flags=208 content_length=0 115 | off=71 chunk header len=2 116 | off=71 len=2 span[body]="OK" 117 | off=75 chunk complete 118 | off=78 chunk header len=0 119 | off=80 chunk complete 120 | off=80 message complete 121 | ``` 122 | 123 | ## Content-Length reset when no body is received 124 | 125 | 126 | ```http 127 | HTTP/1.1 200 OK 128 | Content-Length: 123 129 | 130 | HTTP/1.1 200 OK 131 | Content-Length: 456 132 | 133 | 134 | ``` 135 | 136 | ```log 137 | off=0 message begin 138 | off=0 len=4 span[protocol]="HTTP" 139 | off=4 protocol complete 140 | off=5 len=3 span[version]="1.1" 141 | off=8 version complete 142 | off=13 len=2 span[status]="OK" 143 | off=17 status complete 144 | off=17 len=14 span[header_field]="Content-Length" 145 | off=32 header_field complete 146 | off=33 len=3 span[header_value]="123" 147 | off=38 header_value complete 148 | off=40 headers complete status=200 v=1/1 flags=20 content_length=123 149 | off=40 skip body 150 | off=40 message complete 151 | off=40 reset 152 | off=40 message begin 153 | off=40 len=4 span[protocol]="HTTP" 154 | off=44 protocol complete 155 | off=45 len=3 span[version]="1.1" 156 | off=48 version complete 157 | off=53 len=2 span[status]="OK" 158 | off=57 status complete 159 | off=57 len=14 span[header_field]="Content-Length" 160 | off=72 header_field complete 161 | off=73 len=3 span[header_value]="456" 162 | off=78 header_value complete 163 | off=80 headers complete status=200 v=1/1 flags=20 content_length=456 164 | off=80 skip body 165 | off=80 message complete 166 | ``` 167 | -------------------------------------------------------------------------------- /test/response/finish.md: -------------------------------------------------------------------------------- 1 | Finish 2 | ====== 3 | 4 | Those tests check the return codes and the behavior of `llhttp_finish()` C API. 5 | 6 | ## It should be safe to finish with cb after empty response 7 | 8 | 9 | ```http 10 | HTTP/1.1 200 OK 11 | 12 | 13 | ``` 14 | 15 | ```log 16 | off=0 message begin 17 | off=0 len=4 span[protocol]="HTTP" 18 | off=4 protocol complete 19 | off=5 len=3 span[version]="1.1" 20 | off=8 version complete 21 | off=13 len=2 span[status]="OK" 22 | off=17 status complete 23 | off=19 headers complete status=200 v=1/1 flags=0 content_length=0 24 | off=NULL finish=1 25 | ``` 26 | -------------------------------------------------------------------------------- /test/response/invalid.md: -------------------------------------------------------------------------------- 1 | Invalid responses 2 | ================= 3 | 4 | ### Incomplete HTTP protocol 5 | 6 | 7 | ```http 8 | HTP/1.1 200 OK 9 | 10 | 11 | ``` 12 | 13 | ```log 14 | off=0 message begin 15 | off=0 len=2 span[protocol]="HT" 16 | off=2 error code=8 reason="Expected HTTP/, RTSP/ or ICE/" 17 | ``` 18 | 19 | ### Extra digit in HTTP major version 20 | 21 | 22 | ```http 23 | HTTP/01.1 200 OK 24 | 25 | 26 | ``` 27 | 28 | ```log 29 | off=0 message begin 30 | off=0 len=4 span[protocol]="HTTP" 31 | off=4 protocol complete 32 | off=5 len=1 span[version]="0" 33 | off=6 error code=9 reason="Expected dot" 34 | ``` 35 | 36 | ### Extra digit in HTTP major version #2 37 | 38 | 39 | ```http 40 | HTTP/11.1 200 OK 41 | 42 | 43 | ``` 44 | 45 | ```log 46 | off=0 message begin 47 | off=0 len=4 span[protocol]="HTTP" 48 | off=4 protocol complete 49 | off=5 len=1 span[version]="1" 50 | off=6 error code=9 reason="Expected dot" 51 | ``` 52 | 53 | ### Extra digit in HTTP minor version 54 | 55 | 56 | ```http 57 | HTTP/1.01 200 OK 58 | 59 | 60 | ``` 61 | 62 | ```log 63 | off=0 message begin 64 | off=0 len=4 span[protocol]="HTTP" 65 | off=4 protocol complete 66 | off=5 len=3 span[version]="1.0" 67 | off=8 version complete 68 | off=8 error code=9 reason="Expected space after version" 69 | ``` 70 | --> 71 | 72 | ### Tab after HTTP version 73 | 74 | 75 | ```http 76 | HTTP/1.1\t200 OK 77 | 78 | 79 | ``` 80 | 81 | ```log 82 | off=0 message begin 83 | off=0 len=4 span[protocol]="HTTP" 84 | off=4 protocol complete 85 | off=5 len=3 span[version]="1.1" 86 | off=8 version complete 87 | off=8 error code=9 reason="Expected space after version" 88 | ``` 89 | 90 | ### CR before response and tab after HTTP version 91 | 92 | 93 | ```http 94 | \rHTTP/1.1\t200 OK 95 | 96 | 97 | ``` 98 | 99 | ```log 100 | off=1 message begin 101 | off=1 len=4 span[protocol]="HTTP" 102 | off=5 protocol complete 103 | off=6 len=3 span[version]="1.1" 104 | off=9 version complete 105 | off=9 error code=9 reason="Expected space after version" 106 | ``` 107 | 108 | ### Headers separated by CR 109 | 110 | 111 | ```http 112 | HTTP/1.1 200 OK 113 | Foo: 1\rBar: 2 114 | 115 | 116 | ``` 117 | 118 | ```log 119 | off=0 message begin 120 | off=0 len=4 span[protocol]="HTTP" 121 | off=4 protocol complete 122 | off=5 len=3 span[version]="1.1" 123 | off=8 version complete 124 | off=13 len=2 span[status]="OK" 125 | off=17 status complete 126 | off=17 len=3 span[header_field]="Foo" 127 | off=21 header_field complete 128 | off=22 len=1 span[header_value]="1" 129 | off=24 error code=3 reason="Missing expected LF after header value" 130 | ``` 131 | 132 | ### Invalid HTTP version 133 | 134 | 135 | ```http 136 | HTTP/5.6 200 OK 137 | 138 | 139 | ``` 140 | 141 | ```log 142 | off=0 message begin 143 | off=0 len=4 span[protocol]="HTTP" 144 | off=4 protocol complete 145 | off=5 len=3 span[version]="5.6" 146 | off=8 error code=9 reason="Invalid HTTP version" 147 | ``` 148 | 149 | ## Invalid space after start line 150 | 151 | 152 | ```http 153 | HTTP/1.1 200 OK 154 | Host: foo 155 | ``` 156 | 157 | ```log 158 | off=0 message begin 159 | off=0 len=4 span[protocol]="HTTP" 160 | off=4 protocol complete 161 | off=5 len=3 span[version]="1.1" 162 | off=8 version complete 163 | off=13 len=2 span[status]="OK" 164 | off=17 status complete 165 | off=18 error code=30 reason="Unexpected space after start line" 166 | ``` 167 | 168 | ### Extra space between HTTP version and status code 169 | 170 | 171 | ```http 172 | HTTP/1.1 200 OK 173 | 174 | 175 | ``` 176 | 177 | ```log 178 | off=0 message begin 179 | off=0 len=4 span[protocol]="HTTP" 180 | off=4 protocol complete 181 | off=5 len=3 span[version]="1.1" 182 | off=8 version complete 183 | off=9 error code=13 reason="Invalid status code" 184 | ``` 185 | 186 | ### Extra space between status code and reason 187 | 188 | 189 | ```http 190 | HTTP/1.1 200 OK 191 | 192 | 193 | ``` 194 | 195 | ```log 196 | off=0 message begin 197 | off=0 len=4 span[protocol]="HTTP" 198 | off=4 protocol complete 199 | off=5 len=3 span[version]="1.1" 200 | off=8 version complete 201 | off=13 len=3 span[status]=" OK" 202 | off=18 status complete 203 | off=20 headers complete status=200 v=1/1 flags=0 content_length=0 204 | ``` 205 | 206 | ### One-digit status code 207 | 208 | 209 | ```http 210 | HTTP/1.1 2 OK 211 | 212 | 213 | ``` 214 | 215 | ```log 216 | off=0 message begin 217 | off=0 len=4 span[protocol]="HTTP" 218 | off=4 protocol complete 219 | off=5 len=3 span[version]="1.1" 220 | off=8 version complete 221 | off=10 error code=13 reason="Invalid status code" 222 | ``` 223 | 224 | ### Only LFs present and no body 225 | 226 | 227 | ```http 228 | HTTP/1.1 200 OK\nContent-Length: 0\n\n 229 | ``` 230 | 231 | ```log 232 | off=0 message begin 233 | off=0 len=4 span[protocol]="HTTP" 234 | off=4 protocol complete 235 | off=5 len=3 span[version]="1.1" 236 | off=8 version complete 237 | off=13 len=2 span[status]="OK" 238 | off=16 error code=25 reason="Missing expected CR after response line" 239 | ``` 240 | 241 | ### Only LFs present and no body (lenient) 242 | 243 | 244 | ```http 245 | HTTP/1.1 200 OK\nContent-Length: 0\n\n 246 | ``` 247 | 248 | ```log 249 | off=0 message begin 250 | off=0 len=4 span[protocol]="HTTP" 251 | off=4 protocol complete 252 | off=5 len=3 span[version]="1.1" 253 | off=8 version complete 254 | off=13 len=2 span[status]="OK" 255 | off=16 status complete 256 | off=16 len=14 span[header_field]="Content-Length" 257 | off=31 header_field complete 258 | off=32 len=1 span[header_value]="0" 259 | off=34 header_value complete 260 | off=35 headers complete status=200 v=1/1 flags=20 content_length=0 261 | off=35 message complete 262 | ``` 263 | 264 | ### Only LFs present 265 | 266 | 267 | ```http 268 | HTTP/1.1 200 OK\n\ 269 | Foo: abc\n\ 270 | Bar: def\n\ 271 | \n\ 272 | BODY\n\ 273 | ``` 274 | 275 | ```log 276 | off=0 message begin 277 | off=0 len=4 span[protocol]="HTTP" 278 | off=4 protocol complete 279 | off=5 len=3 span[version]="1.1" 280 | off=8 version complete 281 | off=13 len=2 span[status]="OK" 282 | off=16 error code=25 reason="Missing expected CR after response line" 283 | ``` 284 | 285 | ### Only LFs present (lenient) 286 | 287 | 288 | ```http 289 | HTTP/1.1 200 OK\n\ 290 | Foo: abc\n\ 291 | Bar: def\n\ 292 | \n\ 293 | BODY\n\ 294 | ``` 295 | 296 | ```log 297 | off=0 message begin 298 | off=0 len=4 span[protocol]="HTTP" 299 | off=4 protocol complete 300 | off=5 len=3 span[version]="1.1" 301 | off=8 version complete 302 | off=13 len=2 span[status]="OK" 303 | off=16 status complete 304 | off=16 len=3 span[header_field]="Foo" 305 | off=20 header_field complete 306 | off=21 len=3 span[header_value]="abc" 307 | off=25 header_value complete 308 | off=25 len=3 span[header_field]="Bar" 309 | off=29 header_field complete 310 | off=30 len=3 span[header_value]="def" 311 | off=34 header_value complete 312 | off=35 headers complete status=200 v=1/1 flags=0 content_length=0 313 | off=35 len=4 span[body]="BODY" 314 | off=39 len=1 span[body]=lf 315 | off=40 len=1 span[body]="\" 316 | ``` -------------------------------------------------------------------------------- /test/response/lenient-version.md: -------------------------------------------------------------------------------- 1 | Lenient HTTP version parsing 2 | ============================ 3 | 4 | ### Invalid HTTP version (lenient) 5 | 6 | 7 | ```http 8 | HTTP/5.6 200 OK 9 | 10 | 11 | ``` 12 | 13 | ```log 14 | off=0 message begin 15 | off=0 len=4 span[protocol]="HTTP" 16 | off=4 protocol complete 17 | off=5 len=3 span[version]="5.6" 18 | off=8 version complete 19 | off=13 len=2 span[status]="OK" 20 | off=17 status complete 21 | off=19 headers complete status=200 v=5/6 flags=0 content_length=0 22 | ``` 23 | -------------------------------------------------------------------------------- /test/response/pausing.md: -------------------------------------------------------------------------------- 1 | Pausing 2 | ======= 3 | 4 | ### on_message_begin 5 | 6 | 7 | ```http 8 | HTTP/1.1 200 OK 9 | Content-Length: 3 10 | 11 | abc 12 | ``` 13 | 14 | ```log 15 | off=0 message begin 16 | off=0 pause 17 | off=0 len=4 span[protocol]="HTTP" 18 | off=4 protocol complete 19 | off=5 len=3 span[version]="1.1" 20 | off=8 version complete 21 | off=13 len=2 span[status]="OK" 22 | off=17 status complete 23 | off=17 len=14 span[header_field]="Content-Length" 24 | off=32 header_field complete 25 | off=33 len=1 span[header_value]="3" 26 | off=36 header_value complete 27 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 28 | off=38 len=3 span[body]="abc" 29 | off=41 message complete 30 | ``` 31 | 32 | ### on_message_complete 33 | 34 | 35 | ```http 36 | HTTP/1.1 200 OK 37 | Content-Length: 3 38 | 39 | abc 40 | ``` 41 | 42 | ```log 43 | off=0 message begin 44 | off=0 len=4 span[protocol]="HTTP" 45 | off=4 protocol complete 46 | off=5 len=3 span[version]="1.1" 47 | off=8 version complete 48 | off=13 len=2 span[status]="OK" 49 | off=17 status complete 50 | off=17 len=14 span[header_field]="Content-Length" 51 | off=32 header_field complete 52 | off=33 len=1 span[header_value]="3" 53 | off=36 header_value complete 54 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 55 | off=38 len=3 span[body]="abc" 56 | off=41 message complete 57 | off=41 pause 58 | ``` 59 | 60 | ### on_version_complete 61 | 62 | 63 | ```http 64 | HTTP/1.1 200 OK 65 | Content-Length: 3 66 | 67 | abc 68 | ``` 69 | 70 | ```log 71 | off=0 message begin 72 | off=0 len=4 span[protocol]="HTTP" 73 | off=4 protocol complete 74 | off=5 len=3 span[version]="1.1" 75 | off=8 version complete 76 | off=8 pause 77 | off=13 len=2 span[status]="OK" 78 | off=17 status complete 79 | off=17 len=14 span[header_field]="Content-Length" 80 | off=32 header_field complete 81 | off=33 len=1 span[header_value]="3" 82 | off=36 header_value complete 83 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 84 | off=38 len=3 span[body]="abc" 85 | off=41 message complete 86 | ``` 87 | 88 | ### on_status_complete 89 | 90 | 91 | ```http 92 | HTTP/1.1 200 OK 93 | Content-Length: 3 94 | 95 | abc 96 | ``` 97 | 98 | ```log 99 | off=0 message begin 100 | off=0 len=4 span[protocol]="HTTP" 101 | off=4 protocol complete 102 | off=5 len=3 span[version]="1.1" 103 | off=8 version complete 104 | off=13 len=2 span[status]="OK" 105 | off=17 status complete 106 | off=17 pause 107 | off=17 len=14 span[header_field]="Content-Length" 108 | off=32 header_field complete 109 | off=33 len=1 span[header_value]="3" 110 | off=36 header_value complete 111 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 112 | off=38 len=3 span[body]="abc" 113 | off=41 message complete 114 | ``` 115 | 116 | ### on_header_field_complete 117 | 118 | 119 | ```http 120 | HTTP/1.1 200 OK 121 | Content-Length: 3 122 | 123 | abc 124 | ``` 125 | 126 | ```log 127 | off=0 message begin 128 | off=0 len=4 span[protocol]="HTTP" 129 | off=4 protocol complete 130 | off=5 len=3 span[version]="1.1" 131 | off=8 version complete 132 | off=13 len=2 span[status]="OK" 133 | off=17 status complete 134 | off=17 len=14 span[header_field]="Content-Length" 135 | off=32 header_field complete 136 | off=32 pause 137 | off=33 len=1 span[header_value]="3" 138 | off=36 header_value complete 139 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 140 | off=38 len=3 span[body]="abc" 141 | off=41 message complete 142 | ``` 143 | 144 | ### on_header_value_complete 145 | 146 | 147 | ```http 148 | HTTP/1.1 200 OK 149 | Content-Length: 3 150 | 151 | abc 152 | ``` 153 | 154 | ```log 155 | off=0 message begin 156 | off=0 len=4 span[protocol]="HTTP" 157 | off=4 protocol complete 158 | off=5 len=3 span[version]="1.1" 159 | off=8 version complete 160 | off=13 len=2 span[status]="OK" 161 | off=17 status complete 162 | off=17 len=14 span[header_field]="Content-Length" 163 | off=32 header_field complete 164 | off=33 len=1 span[header_value]="3" 165 | off=36 header_value complete 166 | off=36 pause 167 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 168 | off=38 len=3 span[body]="abc" 169 | off=41 message complete 170 | ``` 171 | 172 | ### on_headers_complete 173 | 174 | 175 | ```http 176 | HTTP/1.1 200 OK 177 | Content-Length: 3 178 | 179 | abc 180 | ``` 181 | 182 | ```log 183 | off=0 message begin 184 | off=0 len=4 span[protocol]="HTTP" 185 | off=4 protocol complete 186 | off=5 len=3 span[version]="1.1" 187 | off=8 version complete 188 | off=13 len=2 span[status]="OK" 189 | off=17 status complete 190 | off=17 len=14 span[header_field]="Content-Length" 191 | off=32 header_field complete 192 | off=33 len=1 span[header_value]="3" 193 | off=36 header_value complete 194 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 195 | off=38 pause 196 | off=38 len=3 span[body]="abc" 197 | off=41 message complete 198 | ``` 199 | 200 | ### on_chunk_header 201 | 202 | 203 | ```http 204 | HTTP/1.1 200 OK 205 | Transfer-Encoding: chunked 206 | 207 | a 208 | 0123456789 209 | 0 210 | 211 | 212 | ``` 213 | 214 | ```log 215 | off=0 message begin 216 | off=0 len=4 span[protocol]="HTTP" 217 | off=4 protocol complete 218 | off=5 len=3 span[version]="1.1" 219 | off=8 version complete 220 | off=13 len=2 span[status]="OK" 221 | off=17 status complete 222 | off=17 len=17 span[header_field]="Transfer-Encoding" 223 | off=35 header_field complete 224 | off=36 len=7 span[header_value]="chunked" 225 | off=45 header_value complete 226 | off=47 headers complete status=200 v=1/1 flags=208 content_length=0 227 | off=50 chunk header len=10 228 | off=50 pause 229 | off=50 len=10 span[body]="0123456789" 230 | off=62 chunk complete 231 | off=65 chunk header len=0 232 | off=65 pause 233 | off=67 chunk complete 234 | off=67 message complete 235 | ``` 236 | 237 | ### on_chunk_extension_name 238 | 239 | 240 | ```http 241 | HTTP/1.1 200 OK 242 | Transfer-Encoding: chunked 243 | 244 | a;foo=bar 245 | 0123456789 246 | 0 247 | 248 | 249 | ``` 250 | 251 | ```log 252 | off=0 message begin 253 | off=0 len=4 span[protocol]="HTTP" 254 | off=4 protocol complete 255 | off=5 len=3 span[version]="1.1" 256 | off=8 version complete 257 | off=13 len=2 span[status]="OK" 258 | off=17 status complete 259 | off=17 len=17 span[header_field]="Transfer-Encoding" 260 | off=35 header_field complete 261 | off=36 len=7 span[header_value]="chunked" 262 | off=45 header_value complete 263 | off=47 headers complete status=200 v=1/1 flags=208 content_length=0 264 | off=49 len=3 span[chunk_extension_name]="foo" 265 | off=53 chunk_extension_name complete 266 | off=53 pause 267 | off=53 len=3 span[chunk_extension_value]="bar" 268 | off=57 chunk_extension_value complete 269 | off=58 chunk header len=10 270 | off=58 len=10 span[body]="0123456789" 271 | off=70 chunk complete 272 | off=73 chunk header len=0 273 | off=75 chunk complete 274 | off=75 message complete 275 | ``` 276 | 277 | ### on_chunk_extension_value 278 | 279 | 280 | ```http 281 | HTTP/1.1 200 OK 282 | Transfer-Encoding: chunked 283 | 284 | a;foo=bar 285 | 0123456789 286 | 0 287 | 288 | 289 | ``` 290 | 291 | ```log 292 | off=0 message begin 293 | off=0 len=4 span[protocol]="HTTP" 294 | off=4 protocol complete 295 | off=5 len=3 span[version]="1.1" 296 | off=8 version complete 297 | off=13 len=2 span[status]="OK" 298 | off=17 status complete 299 | off=17 len=17 span[header_field]="Transfer-Encoding" 300 | off=35 header_field complete 301 | off=36 len=7 span[header_value]="chunked" 302 | off=45 header_value complete 303 | off=47 headers complete status=200 v=1/1 flags=208 content_length=0 304 | off=49 len=3 span[chunk_extension_name]="foo" 305 | off=53 chunk_extension_name complete 306 | off=53 len=3 span[chunk_extension_value]="bar" 307 | off=57 chunk_extension_value complete 308 | off=57 pause 309 | off=58 chunk header len=10 310 | off=58 len=10 span[body]="0123456789" 311 | off=70 chunk complete 312 | off=73 chunk header len=0 313 | off=75 chunk complete 314 | off=75 message complete 315 | ``` 316 | 317 | ### on_chunk_complete 318 | 319 | 320 | ```http 321 | HTTP/1.1 200 OK 322 | Transfer-Encoding: chunked 323 | 324 | a 325 | 0123456789 326 | 0 327 | 328 | 329 | ``` 330 | 331 | ```log 332 | off=0 message begin 333 | off=0 len=4 span[protocol]="HTTP" 334 | off=4 protocol complete 335 | off=5 len=3 span[version]="1.1" 336 | off=8 version complete 337 | off=13 len=2 span[status]="OK" 338 | off=17 status complete 339 | off=17 len=17 span[header_field]="Transfer-Encoding" 340 | off=35 header_field complete 341 | off=36 len=7 span[header_value]="chunked" 342 | off=45 header_value complete 343 | off=47 headers complete status=200 v=1/1 flags=208 content_length=0 344 | off=50 chunk header len=10 345 | off=50 len=10 span[body]="0123456789" 346 | off=62 chunk complete 347 | off=62 pause 348 | off=65 chunk header len=0 349 | off=67 chunk complete 350 | off=67 pause 351 | off=67 message complete 352 | ``` 353 | -------------------------------------------------------------------------------- /test/response/pipelining.md: -------------------------------------------------------------------------------- 1 | Pipelining 2 | ========== 3 | 4 | ## Should parse multiple events 5 | 6 | 7 | ```http 8 | HTTP/1.1 200 OK 9 | Content-Length: 3 10 | 11 | AAA 12 | HTTP/1.1 201 Created 13 | Content-Length: 4 14 | 15 | BBBB 16 | HTTP/1.1 202 Accepted 17 | Content-Length: 5 18 | 19 | CCCC 20 | ``` 21 | 22 | ```log 23 | off=0 message begin 24 | off=0 len=4 span[protocol]="HTTP" 25 | off=4 protocol complete 26 | off=5 len=3 span[version]="1.1" 27 | off=8 version complete 28 | off=13 len=2 span[status]="OK" 29 | off=17 status complete 30 | off=17 len=14 span[header_field]="Content-Length" 31 | off=32 header_field complete 32 | off=33 len=1 span[header_value]="3" 33 | off=36 header_value complete 34 | off=38 headers complete status=200 v=1/1 flags=20 content_length=3 35 | off=38 len=3 span[body]="AAA" 36 | off=41 message complete 37 | off=43 reset 38 | off=43 message begin 39 | off=43 len=4 span[protocol]="HTTP" 40 | off=47 protocol complete 41 | off=48 len=3 span[version]="1.1" 42 | off=51 version complete 43 | off=56 len=7 span[status]="Created" 44 | off=65 status complete 45 | off=65 len=14 span[header_field]="Content-Length" 46 | off=80 header_field complete 47 | off=81 len=1 span[header_value]="4" 48 | off=84 header_value complete 49 | off=86 headers complete status=201 v=1/1 flags=20 content_length=4 50 | off=86 len=4 span[body]="BBBB" 51 | off=90 message complete 52 | off=92 reset 53 | off=92 message begin 54 | off=92 len=4 span[protocol]="HTTP" 55 | off=96 protocol complete 56 | off=97 len=3 span[version]="1.1" 57 | off=100 version complete 58 | off=105 len=8 span[status]="Accepted" 59 | off=115 status complete 60 | off=115 len=14 span[header_field]="Content-Length" 61 | off=130 header_field complete 62 | off=131 len=1 span[header_value]="5" 63 | off=134 header_value complete 64 | off=136 headers complete status=202 v=1/1 flags=20 content_length=5 65 | off=136 len=4 span[body]="CCCC" 66 | ``` -------------------------------------------------------------------------------- /test/response/transfer-encoding.md: -------------------------------------------------------------------------------- 1 | Transfer-Encoding header 2 | ======================== 3 | 4 | ## Trailing space on chunked body 5 | 6 | 7 | ```http 8 | HTTP/1.1 200 OK 9 | Content-Type: text/plain 10 | Transfer-Encoding: chunked 11 | 12 | 25 \r\n\ 13 | This is the data in the first chunk 14 | 15 | 1C 16 | and this is the second one 17 | 18 | 0 \r\n\ 19 | 20 | 21 | ``` 22 | 23 | ```log 24 | off=0 message begin 25 | off=0 len=4 span[protocol]="HTTP" 26 | off=4 protocol complete 27 | off=5 len=3 span[version]="1.1" 28 | off=8 version complete 29 | off=13 len=2 span[status]="OK" 30 | off=17 status complete 31 | off=17 len=12 span[header_field]="Content-Type" 32 | off=30 header_field complete 33 | off=31 len=10 span[header_value]="text/plain" 34 | off=43 header_value complete 35 | off=43 len=17 span[header_field]="Transfer-Encoding" 36 | off=61 header_field complete 37 | off=62 len=7 span[header_value]="chunked" 38 | off=71 header_value complete 39 | off=73 headers complete status=200 v=1/1 flags=208 content_length=0 40 | off=76 error code=12 reason="Invalid character in chunk size" 41 | ``` 42 | 43 | ## `chunked` before other transfer-encoding 44 | 45 | 46 | ```http 47 | HTTP/1.1 200 OK 48 | Accept: */* 49 | Transfer-Encoding: chunked, deflate 50 | 51 | World 52 | ``` 53 | 54 | ```log 55 | off=0 message begin 56 | off=0 len=4 span[protocol]="HTTP" 57 | off=4 protocol complete 58 | off=5 len=3 span[version]="1.1" 59 | off=8 version complete 60 | off=13 len=2 span[status]="OK" 61 | off=17 status complete 62 | off=17 len=6 span[header_field]="Accept" 63 | off=24 header_field complete 64 | off=25 len=3 span[header_value]="*/*" 65 | off=30 header_value complete 66 | off=30 len=17 span[header_field]="Transfer-Encoding" 67 | off=48 header_field complete 68 | off=49 len=16 span[header_value]="chunked, deflate" 69 | off=67 header_value complete 70 | off=69 headers complete status=200 v=1/1 flags=200 content_length=0 71 | off=69 len=5 span[body]="World" 72 | ``` 73 | 74 | ## multiple transfer-encoding where chunked is not the last one 75 | 76 | 77 | ```http 78 | HTTP/1.1 200 OK 79 | Accept: */* 80 | Transfer-Encoding: chunked 81 | Transfer-Encoding: identity 82 | 83 | World 84 | ``` 85 | 86 | ```log 87 | off=0 message begin 88 | off=0 len=4 span[protocol]="HTTP" 89 | off=4 protocol complete 90 | off=5 len=3 span[version]="1.1" 91 | off=8 version complete 92 | off=13 len=2 span[status]="OK" 93 | off=17 status complete 94 | off=17 len=6 span[header_field]="Accept" 95 | off=24 header_field complete 96 | off=25 len=3 span[header_value]="*/*" 97 | off=30 header_value complete 98 | off=30 len=17 span[header_field]="Transfer-Encoding" 99 | off=48 header_field complete 100 | off=49 len=7 span[header_value]="chunked" 101 | off=58 header_value complete 102 | off=58 len=17 span[header_field]="Transfer-Encoding" 103 | off=76 header_field complete 104 | off=77 len=8 span[header_value]="identity" 105 | off=87 header_value complete 106 | off=89 headers complete status=200 v=1/1 flags=200 content_length=0 107 | off=89 len=5 span[body]="World" 108 | ``` 109 | 110 | ## `chunkedchunked` transfer-encoding does not enable chunked enconding 111 | 112 | This check that the word `chunked` repeat more than once (with or without spaces) does not mistakenly enables chunked encoding. 113 | 114 | 115 | ```http 116 | HTTP/1.1 200 OK 117 | Accept: */* 118 | Transfer-Encoding: chunkedchunked 119 | 120 | 2 121 | OK 122 | 0 123 | 124 | 125 | ``` 126 | 127 | ```log 128 | off=0 message begin 129 | off=0 len=4 span[protocol]="HTTP" 130 | off=4 protocol complete 131 | off=5 len=3 span[version]="1.1" 132 | off=8 version complete 133 | off=13 len=2 span[status]="OK" 134 | off=17 status complete 135 | off=17 len=6 span[header_field]="Accept" 136 | off=24 header_field complete 137 | off=25 len=3 span[header_value]="*/*" 138 | off=30 header_value complete 139 | off=30 len=17 span[header_field]="Transfer-Encoding" 140 | off=48 header_field complete 141 | off=49 len=14 span[header_value]="chunkedchunked" 142 | off=65 header_value complete 143 | off=67 headers complete status=200 v=1/1 flags=200 content_length=0 144 | off=67 len=1 span[body]="2" 145 | off=68 len=1 span[body]=cr 146 | off=69 len=1 span[body]=lf 147 | off=70 len=2 span[body]="OK" 148 | off=72 len=1 span[body]=cr 149 | off=73 len=1 span[body]=lf 150 | off=74 len=1 span[body]="0" 151 | off=75 len=1 span[body]=cr 152 | off=76 len=1 span[body]=lf 153 | off=77 len=1 span[body]=cr 154 | off=78 len=1 span[body]=lf 155 | ``` 156 | 157 | ## Chunk extensions 158 | 159 | 160 | ```http 161 | HTTP/1.1 200 OK 162 | Host: localhost 163 | Transfer-encoding: chunked 164 | 165 | 5;ilovew3;somuchlove=aretheseparametersfor 166 | hello 167 | 6;blahblah;blah 168 | world 169 | 0 170 | 171 | 172 | ``` 173 | 174 | ```log 175 | off=0 message begin 176 | off=0 len=4 span[protocol]="HTTP" 177 | off=4 protocol complete 178 | off=5 len=3 span[version]="1.1" 179 | off=8 version complete 180 | off=13 len=2 span[status]="OK" 181 | off=17 status complete 182 | off=17 len=4 span[header_field]="Host" 183 | off=22 header_field complete 184 | off=23 len=9 span[header_value]="localhost" 185 | off=34 header_value complete 186 | off=34 len=17 span[header_field]="Transfer-encoding" 187 | off=52 header_field complete 188 | off=53 len=7 span[header_value]="chunked" 189 | off=62 header_value complete 190 | off=64 headers complete status=200 v=1/1 flags=208 content_length=0 191 | off=66 len=7 span[chunk_extension_name]="ilovew3" 192 | off=74 chunk_extension_name complete 193 | off=74 len=10 span[chunk_extension_name]="somuchlove" 194 | off=85 chunk_extension_name complete 195 | off=85 len=21 span[chunk_extension_value]="aretheseparametersfor" 196 | off=107 chunk_extension_value complete 197 | off=108 chunk header len=5 198 | off=108 len=5 span[body]="hello" 199 | off=115 chunk complete 200 | off=117 len=8 span[chunk_extension_name]="blahblah" 201 | off=126 chunk_extension_name complete 202 | off=126 len=4 span[chunk_extension_name]="blah" 203 | off=131 chunk_extension_name complete 204 | off=132 chunk header len=6 205 | off=132 len=6 span[body]=" world" 206 | off=140 chunk complete 207 | off=143 chunk header len=0 208 | off=145 chunk complete 209 | off=145 message complete 210 | ``` 211 | 212 | ## No semicolon before chunk extensions 213 | 214 | 215 | ```http 216 | HTTP/1.1 200 OK 217 | Host: localhost 218 | Transfer-encoding: chunked 219 | 220 | 2 erfrferferf 221 | aa 222 | 0 rrrr 223 | 224 | 225 | ``` 226 | 227 | ```log 228 | off=0 message begin 229 | off=0 len=4 span[protocol]="HTTP" 230 | off=4 protocol complete 231 | off=5 len=3 span[version]="1.1" 232 | off=8 version complete 233 | off=13 len=2 span[status]="OK" 234 | off=17 status complete 235 | off=17 len=4 span[header_field]="Host" 236 | off=22 header_field complete 237 | off=23 len=9 span[header_value]="localhost" 238 | off=34 header_value complete 239 | off=34 len=17 span[header_field]="Transfer-encoding" 240 | off=52 header_field complete 241 | off=53 len=7 span[header_value]="chunked" 242 | off=62 header_value complete 243 | off=64 headers complete status=200 v=1/1 flags=208 content_length=0 244 | off=66 error code=12 reason="Invalid character in chunk size" 245 | ``` 246 | 247 | 248 | ## No extension after semicolon 249 | 250 | 251 | ```http 252 | HTTP/1.1 200 OK 253 | Host: localhost 254 | Transfer-encoding: chunked 255 | 256 | 2; 257 | aa 258 | 0 259 | 260 | 261 | ``` 262 | 263 | ```log 264 | off=0 message begin 265 | off=0 len=4 span[protocol]="HTTP" 266 | off=4 protocol complete 267 | off=5 len=3 span[version]="1.1" 268 | off=8 version complete 269 | off=13 len=2 span[status]="OK" 270 | off=17 status complete 271 | off=17 len=4 span[header_field]="Host" 272 | off=22 header_field complete 273 | off=23 len=9 span[header_value]="localhost" 274 | off=34 header_value complete 275 | off=34 len=17 span[header_field]="Transfer-encoding" 276 | off=52 header_field complete 277 | off=53 len=7 span[header_value]="chunked" 278 | off=62 header_value complete 279 | off=64 headers complete status=200 v=1/1 flags=208 content_length=0 280 | off=67 error code=2 reason="Invalid character in chunk extensions" 281 | ``` 282 | 283 | 284 | ## Chunk extensions quoting 285 | 286 | 287 | ```http 288 | HTTP/1.1 200 OK 289 | Host: localhost 290 | Transfer-Encoding: chunked 291 | 292 | 5;ilovew3="I love; extensions";somuchlove="aretheseparametersfor";blah;foo=bar 293 | hello 294 | 6;blahblah;blah 295 | world 296 | 0 297 | 298 | ``` 299 | 300 | ```log 301 | off=0 message begin 302 | off=0 len=4 span[protocol]="HTTP" 303 | off=4 protocol complete 304 | off=5 len=3 span[version]="1.1" 305 | off=8 version complete 306 | off=13 len=2 span[status]="OK" 307 | off=17 status complete 308 | off=17 len=4 span[header_field]="Host" 309 | off=22 header_field complete 310 | off=23 len=9 span[header_value]="localhost" 311 | off=34 header_value complete 312 | off=34 len=17 span[header_field]="Transfer-Encoding" 313 | off=52 header_field complete 314 | off=53 len=7 span[header_value]="chunked" 315 | off=62 header_value complete 316 | off=64 headers complete status=200 v=1/1 flags=208 content_length=0 317 | off=66 len=7 span[chunk_extension_name]="ilovew3" 318 | off=74 chunk_extension_name complete 319 | off=74 len=20 span[chunk_extension_value]=""I love; extensions"" 320 | off=94 chunk_extension_value complete 321 | off=95 len=10 span[chunk_extension_name]="somuchlove" 322 | off=106 chunk_extension_name complete 323 | off=106 len=23 span[chunk_extension_value]=""aretheseparametersfor"" 324 | off=129 chunk_extension_value complete 325 | off=130 len=4 span[chunk_extension_name]="blah" 326 | off=135 chunk_extension_name complete 327 | off=135 len=3 span[chunk_extension_name]="foo" 328 | off=139 chunk_extension_name complete 329 | off=139 len=3 span[chunk_extension_value]="bar" 330 | off=143 chunk_extension_value complete 331 | off=144 chunk header len=5 332 | off=144 len=5 span[body]="hello" 333 | off=151 chunk complete 334 | off=153 len=8 span[chunk_extension_name]="blahblah" 335 | off=162 chunk_extension_name complete 336 | off=162 len=4 span[chunk_extension_name]="blah" 337 | off=167 chunk_extension_name complete 338 | off=168 chunk header len=6 339 | off=168 len=6 span[body]=" world" 340 | off=176 chunk complete 341 | off=179 chunk header len=0 342 | ``` 343 | 344 | 345 | ## Unbalanced chunk extensions quoting 346 | 347 | 348 | ```http 349 | HTTP/1.1 200 OK 350 | Host: localhost 351 | Transfer-Encoding: chunked 352 | 353 | 5;ilovew3="abc";somuchlove="def; ghi 354 | hello 355 | 6;blahblah;blah 356 | world 357 | 0 358 | 359 | ``` 360 | 361 | ```log 362 | off=0 message begin 363 | off=0 len=4 span[protocol]="HTTP" 364 | off=4 protocol complete 365 | off=5 len=3 span[version]="1.1" 366 | off=8 version complete 367 | off=13 len=2 span[status]="OK" 368 | off=17 status complete 369 | off=17 len=4 span[header_field]="Host" 370 | off=22 header_field complete 371 | off=23 len=9 span[header_value]="localhost" 372 | off=34 header_value complete 373 | off=34 len=17 span[header_field]="Transfer-Encoding" 374 | off=52 header_field complete 375 | off=53 len=7 span[header_value]="chunked" 376 | off=62 header_value complete 377 | off=64 headers complete status=200 v=1/1 flags=208 content_length=0 378 | off=66 len=7 span[chunk_extension_name]="ilovew3" 379 | off=74 chunk_extension_name complete 380 | off=74 len=5 span[chunk_extension_value]=""abc"" 381 | off=79 chunk_extension_value complete 382 | off=80 len=10 span[chunk_extension_name]="somuchlove" 383 | off=91 chunk_extension_name complete 384 | off=91 len=9 span[chunk_extension_value]=""def; ghi" 385 | off=101 error code=2 reason="Invalid character in chunk extensions quoted value" 386 | ``` 387 | 388 | 389 | ## Invalid OBS fold after chunked value 390 | 391 | 392 | ```http 393 | HTTP/1.1 200 OK 394 | Transfer-Encoding: chunked 395 | abc 396 | 397 | 5 398 | World 399 | 0 400 | 401 | 402 | ``` 403 | 404 | ```log 405 | off=0 message begin 406 | off=0 len=4 span[protocol]="HTTP" 407 | off=4 protocol complete 408 | off=5 len=3 span[version]="1.1" 409 | off=8 version complete 410 | off=13 len=2 span[status]="OK" 411 | off=17 status complete 412 | off=17 len=17 span[header_field]="Transfer-Encoding" 413 | off=35 header_field complete 414 | off=36 len=7 span[header_value]="chunked" 415 | off=45 len=5 span[header_value]=" abc" 416 | off=52 header_value complete 417 | off=54 headers complete status=200 v=1/1 flags=200 content_length=0 418 | off=54 len=1 span[body]="5" 419 | off=55 len=1 span[body]=cr 420 | off=56 len=1 span[body]=lf 421 | off=57 len=5 span[body]="World" 422 | off=62 len=1 span[body]=cr 423 | off=63 len=1 span[body]=lf 424 | off=64 len=1 span[body]="0" 425 | off=65 len=1 span[body]=cr 426 | off=66 len=1 span[body]=lf 427 | off=67 len=1 span[body]=cr 428 | off=68 len=1 span[body]=lf 429 | ``` 430 | 431 | -------------------------------------------------------------------------------- /test/url.md: -------------------------------------------------------------------------------- 1 | # URL tests 2 | 3 | ## Absolute URL 4 | 5 | ```url 6 | http://example.com/path?query=value#schema 7 | ``` 8 | 9 | ```log 10 | off=0 len=4 span[url.schema]="http" 11 | off=7 len=11 span[url.host]="example.com" 12 | off=18 len=5 span[url.path]="/path" 13 | off=24 len=11 span[url.query]="query=value" 14 | off=36 len=6 span[url.fragment]="schema" 15 | ``` 16 | 17 | ## Relative URL 18 | 19 | ```url 20 | /path?query=value#schema 21 | ``` 22 | 23 | ```log 24 | off=0 len=5 span[url.path]="/path" 25 | off=6 len=11 span[url.query]="query=value" 26 | off=18 len=6 span[url.fragment]="schema" 27 | ``` 28 | 29 | ## Failing on broken schema 30 | 31 | 32 | ```url 33 | schema:/path?query=value#schema 34 | ``` 35 | 36 | ```log 37 | off=0 len=6 span[url.schema]="schema" 38 | off=8 error code=7 reason="Unexpected char in url schema" 39 | ``` 40 | 41 | ## Proxy request 42 | 43 | ```url 44 | http://hostname/ 45 | ``` 46 | 47 | ```log 48 | off=0 len=4 span[url.schema]="http" 49 | off=7 len=8 span[url.host]="hostname" 50 | off=15 len=1 span[url.path]="/" 51 | ``` 52 | 53 | ## Proxy request with port 54 | 55 | ```url 56 | http://hostname:444/ 57 | ``` 58 | 59 | ```log 60 | off=0 len=4 span[url.schema]="http" 61 | off=7 len=12 span[url.host]="hostname:444" 62 | off=19 len=1 span[url.path]="/" 63 | ``` 64 | 65 | ## Proxy IPv6 request 66 | 67 | ```url 68 | http://[1:2::3:4]/ 69 | ``` 70 | 71 | ```log 72 | off=0 len=4 span[url.schema]="http" 73 | off=7 len=10 span[url.host]="[1:2::3:4]" 74 | off=17 len=1 span[url.path]="/" 75 | ``` 76 | 77 | ## Proxy IPv6 request with port 78 | 79 | ```url 80 | http://[1:2::3:4]:67/ 81 | ``` 82 | 83 | ```log 84 | off=0 len=4 span[url.schema]="http" 85 | off=7 len=13 span[url.host]="[1:2::3:4]:67" 86 | off=20 len=1 span[url.path]="/" 87 | ``` 88 | 89 | ## IPv4 in IPv6 address 90 | 91 | ```url 92 | http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/ 93 | ``` 94 | 95 | ```log 96 | off=0 len=4 span[url.schema]="http" 97 | off=7 len=39 span[url.host]="[2001:0000:0000:0000:0000:0000:1.9.1.1]" 98 | off=46 len=1 span[url.path]="/" 99 | ``` 100 | 101 | ## Extra `?` in query string 102 | 103 | ```url 104 | http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,\ 105 | fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,\ 106 | fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css 107 | ``` 108 | 109 | ```log 110 | off=0 len=4 span[url.schema]="http" 111 | off=7 len=10 span[url.host]="a.tbcdn.cn" 112 | off=17 len=12 span[url.path]="/p/fp/2010c/" 113 | 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" 114 | ``` 115 | 116 | ## URL encoded space 117 | 118 | ```url 119 | /toto.html?toto=a%20b 120 | ``` 121 | 122 | ```log 123 | off=0 len=10 span[url.path]="/toto.html" 124 | off=11 len=10 span[url.query]="toto=a%20b" 125 | ``` 126 | 127 | ## URL fragment 128 | 129 | ```url 130 | /toto.html#titi 131 | ``` 132 | 133 | ```log 134 | off=0 len=10 span[url.path]="/toto.html" 135 | off=11 len=4 span[url.fragment]="titi" 136 | ``` 137 | 138 | ## Complex URL fragment 139 | 140 | ```url 141 | http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=\ 142 | http://www.example.com/index.html?foo=bar&hello=world#midpage 143 | ``` 144 | 145 | ```log 146 | off=0 len=4 span[url.schema]="http" 147 | off=7 len=22 span[url.host]="www.webmasterworld.com" 148 | off=29 len=6 span[url.path]="/r.cgi" 149 | off=36 len=69 span[url.query]="f=21&d=8405&url=http://www.example.com/index.html?foo=bar&hello=world" 150 | off=106 len=7 span[url.fragment]="midpage" 151 | ``` 152 | 153 | ## Complex URL from node.js url parser doc 154 | 155 | ```url 156 | http://host.com:8080/p/a/t/h?query=string#hash 157 | ``` 158 | 159 | ```log 160 | off=0 len=4 span[url.schema]="http" 161 | off=7 len=13 span[url.host]="host.com:8080" 162 | off=20 len=8 span[url.path]="/p/a/t/h" 163 | off=29 len=12 span[url.query]="query=string" 164 | off=42 len=4 span[url.fragment]="hash" 165 | ``` 166 | 167 | ## Complex URL with basic auth from node.js url parser doc 168 | 169 | ```url 170 | http://a:b@host.com:8080/p/a/t/h?query=string#hash 171 | ``` 172 | 173 | ```log 174 | off=0 len=4 span[url.schema]="http" 175 | off=7 len=17 span[url.host]="a:b@host.com:8080" 176 | off=24 len=8 span[url.path]="/p/a/t/h" 177 | off=33 len=12 span[url.query]="query=string" 178 | off=46 len=4 span[url.fragment]="hash" 179 | ``` 180 | 181 | ## Double `@` 182 | 183 | 184 | ```url 185 | http://a:b@@hostname:443/ 186 | ``` 187 | 188 | ```log 189 | off=0 len=4 span[url.schema]="http" 190 | off=12 error code=7 reason="Double @ in url" 191 | ``` 192 | 193 | ## Proxy basic auth with url encoded space 194 | 195 | ```url 196 | http://a%20:b@host.com/ 197 | ``` 198 | 199 | ```log 200 | off=0 len=4 span[url.schema]="http" 201 | off=7 len=15 span[url.host]="a%20:b@host.com" 202 | off=22 len=1 span[url.path]="/" 203 | ``` 204 | 205 | ## Proxy basic auth with unreserved chars 206 | 207 | ```url 208 | http://a!;-_!=+$@host.com/ 209 | ``` 210 | 211 | ```log 212 | off=0 len=4 span[url.schema]="http" 213 | off=7 len=18 span[url.host]="a!;-_!=+$@host.com" 214 | off=25 len=1 span[url.path]="/" 215 | ``` 216 | 217 | ## IPv6 address with Zone ID 218 | 219 | ```url 220 | http://[fe80::a%25eth0]/ 221 | ``` 222 | 223 | ```log 224 | off=0 len=4 span[url.schema]="http" 225 | off=7 len=16 span[url.host]="[fe80::a%25eth0]" 226 | off=23 len=1 span[url.path]="/" 227 | ``` 228 | 229 | ## IPv6 address with Zone ID, but `%` is not percent-encoded 230 | 231 | ```url 232 | http://[fe80::a%eth0]/ 233 | ``` 234 | 235 | ```log 236 | off=0 len=4 span[url.schema]="http" 237 | off=7 len=14 span[url.host]="[fe80::a%eth0]" 238 | off=21 len=1 span[url.path]="/" 239 | ``` 240 | 241 | ## Disallow tab in URL 242 | 243 | 244 | ```url 245 | /foo\tbar/ 246 | ``` 247 | 248 | ```log 249 | off=5 error code=7 reason="Invalid characters in url" 250 | ``` 251 | 252 | ## Disallow form-feed in URL 253 | 254 | 255 | ```url 256 | /foo\fbar/ 257 | ``` 258 | 259 | ```log 260 | off=5 error code=7 reason="Invalid characters in url" 261 | ``` 262 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2023"], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "target": "es2022", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "outDir": "./lib", 11 | "declaration": true, 12 | "pretty": true, 13 | "sourceMap": true, 14 | "erasableSyntaxOnly": true 15 | }, 16 | "include": ["src/**/*.ts"], 17 | } -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true 6 | }, 7 | "include": ["bench/**/*.ts", "bin/**/*.ts", "src/**/*.ts", "test/**/*.ts", "eslint.config.mjs"], 8 | } 9 | --------------------------------------------------------------------------------