├── .standard.yml ├── ffi ├── .rspec ├── ext │ ├── Rakefile │ └── llhttp │ │ ├── extconf.rb │ │ ├── http.c │ │ ├── llhttp_ext.c │ │ ├── api.c │ │ └── llhttp.h ├── lib │ ├── llhttp │ │ ├── error.rb │ │ ├── version.rb │ │ ├── delegate.rb │ │ └── parser.rb │ └── llhttp.rb ├── Gemfile ├── Rakefile ├── README.md ├── llhttp-ffi.gemspec ├── .commit │ ├── config.yml │ └── data │ │ └── releases.yml ├── CHANGELOG.md └── LICENSE ├── mri ├── .rspec ├── ext │ └── llhttp │ │ ├── extconf.rb │ │ ├── http.c │ │ ├── llhttp_ext.c │ │ ├── api.c │ │ └── llhttp.h ├── lib │ ├── llhttp │ │ ├── error.rb │ │ ├── version.rb │ │ ├── parser.rb │ │ └── delegate.rb │ └── llhttp.rb ├── Gemfile ├── Rakefile ├── README.md ├── llhttp.gemspec ├── .commit │ ├── config.yml │ └── data │ │ └── releases.yml ├── CHANGELOG.md └── LICENSE ├── .gitignore ├── benchmarks ├── ffi │ ├── run.rb │ └── Gemfile ├── mri │ ├── run.rb │ └── Gemfile └── shared.rb ├── Gemfile ├── .editorconfig ├── spec ├── initialize.rb ├── initializers │ ├── matchers.rb │ └── configure.rb ├── integration │ ├── callbacks │ │ ├── url_spec.rb │ │ ├── status_spec.rb │ │ ├── url_complete_spec.rb │ │ ├── status_complete_spec.rb │ │ ├── message_begin_spec.rb │ │ ├── headers_complete_spec.rb │ │ ├── message_complete_spec.rb │ │ ├── body_spec.rb │ │ ├── header_value_spec.rb │ │ ├── header_field_spec.rb │ │ ├── header_field_complete_spec.rb │ │ ├── header_value_complete_spec.rb │ │ ├── chunk_header_spec.rb │ │ └── chunk_complete_spec.rb │ ├── introspection │ │ ├── method_name_spec.rb │ │ ├── status_code_spec.rb │ │ ├── http_major_spec.rb │ │ ├── http_minor_spec.rb │ │ └── content_length_spec.rb │ ├── parser_errors_spec.rb │ ├── no_content_length_spec.rb │ ├── reusing_spec.rb │ ├── callback_errors_spec.rb │ └── support │ │ └── context │ │ └── parsing.rb ├── acceptance │ ├── server_spec.rb │ └── support │ │ └── server.rb └── unit │ └── parser_spec.rb ├── .commit └── config.yml ├── README.md └── .github └── workflows ├── commit-tools.yml └── ci.yml /.standard.yml: -------------------------------------------------------------------------------- 1 | format: progress 2 | parallel: true 3 | 4 | 5 | -------------------------------------------------------------------------------- /ffi/.rspec: -------------------------------------------------------------------------------- 1 | --default-path ../spec --require ../spec/initialize 2 | -------------------------------------------------------------------------------- /mri/.rspec: -------------------------------------------------------------------------------- 1 | --default-path ../spec --require ../spec/initialize 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .commit/tools 2 | tmp 3 | *.bundle 4 | *.gem 5 | Gemfile.lock 6 | .ruby-version 7 | 8 | -------------------------------------------------------------------------------- /benchmarks/ffi/run.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../shared" 4 | 5 | benchmark 6 | -------------------------------------------------------------------------------- /benchmarks/mri/run.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../shared" 4 | 5 | benchmark 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | group :development do 6 | gem "standardrb" 7 | end 8 | -------------------------------------------------------------------------------- /ffi/ext/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "ffi-compiler/compile_task" 4 | 5 | FFI::Compiler::CompileTask.new("llhttp-ext") 6 | -------------------------------------------------------------------------------- /ffi/ext/llhttp/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "mkmf" 4 | 5 | dir_config("llhttp_ext") 6 | create_makefile("llhttp_ext") 7 | -------------------------------------------------------------------------------- /mri/ext/llhttp/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "mkmf" 4 | 5 | dir_config("llhttp_ext") 6 | create_makefile("llhttp_ext") 7 | -------------------------------------------------------------------------------- /benchmarks/mri/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "benchmark-ips" 6 | gem "llhttp", path: "../../mri" 7 | -------------------------------------------------------------------------------- /benchmarks/ffi/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "benchmark-ips" 6 | gem "llhttp-ffi", path: "../../ffi" 7 | -------------------------------------------------------------------------------- /ffi/lib/llhttp/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] LLHttp's standard error object. 5 | # 6 | class Error < StandardError 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /mri/lib/llhttp/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] LLHttp's standard error object. 5 | # 6 | class Error < StandardError 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [Makefile] 10 | indent_style = tab 11 | -------------------------------------------------------------------------------- /ffi/lib/llhttp/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | VERSION = "0.5.0" 5 | 6 | # [public] LLHttp's current version. 7 | # 8 | def self.version 9 | VERSION 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /mri/lib/llhttp/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | VERSION = "0.6.0" 5 | 6 | # [public] LLHttp's current version. 7 | # 8 | def self.version 9 | VERSION 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /mri/lib/llhttp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | require_relative "llhttp/delegate" 5 | require_relative "llhttp/error" 6 | require_relative "llhttp/parser" 7 | require_relative "llhttp/version" 8 | end 9 | -------------------------------------------------------------------------------- /ffi/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | group :development do 8 | gem "rake-compiler" 9 | end 10 | 11 | group :test do 12 | gem "async-io" 13 | gem "rspec" 14 | end 15 | -------------------------------------------------------------------------------- /mri/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | group :development do 8 | gem "rake-compiler" 9 | end 10 | 11 | group :test do 12 | gem "async-io" 13 | gem "rspec" 14 | end 15 | -------------------------------------------------------------------------------- /spec/initialize.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "pathname" 4 | 5 | initializers = Pathname.new(File.expand_path("../initializers", __FILE__)) 6 | 7 | if initializers.directory? 8 | initializers.glob("*.rb") do |file| 9 | load(file) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.commit/config.yml: -------------------------------------------------------------------------------- 1 | commit: 2 | includes: 3 | - ruby-standard 4 | - git 5 | - base 6 | 7 | externals: 8 | - repo: "metabahn/commit-templates" 9 | private: true 10 | 11 | git: 12 | ignore: 13 | - "tmp" 14 | - "*.bundle" 15 | - "*.gem" 16 | - "Gemfile.lock" 17 | - ".ruby-version" 18 | -------------------------------------------------------------------------------- /spec/initializers/matchers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec::Matchers.define :eq_sans_whitespace do |expected| 4 | match do |actual| 5 | expected.gsub(/\s+/, "") == actual.gsub(/\s+/, "") 6 | end 7 | 8 | diffable 9 | end 10 | 11 | RSpec::Matchers.define :include_sans_whitespace do |expected| 12 | match do |actual| 13 | actual.to_s.gsub(/\s+/, "").include?(expected.to_s.gsub(/\s+/, "")) 14 | end 15 | 16 | diffable 17 | end 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # llhttp 2 | 3 | Ruby bindings for [llhttp](https://github.com/nodejs/llhttp). 4 | 5 | This is a monorepo that includes projects for MRI and FFI. Generally speaking, you should prefer the MRI version and 6 | fallback to the FFI version for better compatibility. There is parity between the two implementations, but the MRI 7 | implementation is more performant. 8 | 9 | * [MRI](https://github.com/metabahn/llhttp/tree/main/mri) 10 | * [FFI](https://github.com/metabahn/llhttp/tree/main/ffi) 11 | -------------------------------------------------------------------------------- /ffi/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | 5 | task :compile do 6 | system "cd ext && bundle exec rake" 7 | end 8 | 9 | task test: :compile do 10 | unless system "bundle exec rspec" 11 | exit $?.exitstatus 12 | end 13 | end 14 | 15 | task :clean do 16 | [ 17 | "./lib/llhttp/llhttp_ext.bundle" 18 | ].each do |file| 19 | next unless File.exist?(file) 20 | 21 | FileUtils.rm(file) 22 | end 23 | end 24 | 25 | task build: %i[test clean] do 26 | system "gem build llhttp-ffi.gemspec" 27 | end 28 | -------------------------------------------------------------------------------- /mri/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | require "rake/extensiontask" 5 | 6 | Rake::ExtensionTask.new "llhttp_ext" do |ext| 7 | ext.ext_dir = "ext/llhttp" 8 | end 9 | 10 | task test: :compile do 11 | unless system "bundle exec rspec" 12 | exit $?.exitstatus 13 | end 14 | end 15 | 16 | task :clean do 17 | [ 18 | "./lib/llhttp_ext.bundle" 19 | ].each do |file| 20 | next unless File.exist?(file) 21 | 22 | FileUtils.rm(file) 23 | end 24 | end 25 | 26 | task build: %i[test clean] do 27 | system "gem build llhttp.gemspec" 28 | end 29 | -------------------------------------------------------------------------------- /benchmarks/shared.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "benchmark/ips" 4 | require "llhttp" 5 | 6 | def parse(instance) 7 | instance << "GET / HTTP/1.1\r\n" 8 | instance << "content-length: 18\r\n" 9 | instance << "\r\n" 10 | instance << "body1\n" 11 | instance << "body2\n" 12 | instance << "body3\n" 13 | instance << "\r\n" 14 | end 15 | 16 | def benchmark 17 | delegate = LLHttp::Delegate.new 18 | instance = LLHttp::Parser.new(delegate, type: :request) 19 | 20 | Benchmark.ips do |x| 21 | x.report do 22 | parse(instance) 23 | instance.finish 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/initializers/configure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |config| 4 | config.expect_with :rspec do |expectations| 5 | # TODO This option will default to `true` in RSpec 4. Remove then. 6 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 7 | end 8 | 9 | config.mock_with :rspec do |mocks| 10 | # TODO Will default to `true` in RSpec 4. Remove then 11 | mocks.verify_partial_doubles = true 12 | end 13 | 14 | config.disable_monkey_patching! 15 | config.warnings = true 16 | config.color = true 17 | 18 | config.order = :random 19 | Kernel.srand config.seed 20 | end 21 | -------------------------------------------------------------------------------- /mri/README.md: -------------------------------------------------------------------------------- 1 | # llhttp 2 | 3 | Ruby bindings for [llhttp](https://github.com/nodejs/llhttp). 4 | 5 | ## Install 6 | 7 | ``` 8 | gem install llhttp 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```ruby 14 | require "llhttp" 15 | 16 | # Define a delegate class for handling callbacks: 17 | # 18 | class Delegate < LLHttp::Delegate 19 | def on_message_begin 20 | ... 21 | end 22 | end 23 | 24 | delegate = Delegate.new 25 | 26 | # Create a parser: 27 | # 28 | parser = LLHttp::Parser.new(delegate) 29 | 30 | # Parse a request: 31 | # 32 | parser << "GET / HTTP/1.1\r\n\r\n" 33 | 34 | # Reset the parser for the next request: 35 | # 36 | parser.reset 37 | ``` 38 | -------------------------------------------------------------------------------- /ffi/README.md: -------------------------------------------------------------------------------- 1 | # llhttp-ffi 2 | 3 | Ruby FFI bindings for [llhttp](https://github.com/nodejs/llhttp). 4 | 5 | ## Install 6 | 7 | ``` 8 | gem install llhttp-ffi 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```ruby 14 | require "llhttp" 15 | 16 | # Define a delegate class for handling callbacks: 17 | # 18 | class Delegate < LLHttp::Delegate 19 | def on_message_begin 20 | ... 21 | end 22 | end 23 | 24 | delegate = Delegate.new 25 | 26 | # Create a parser: 27 | # 28 | parser = LLHttp::Parser.new(delegate) 29 | 30 | # Parse a request: 31 | # 32 | parser << "GET / HTTP/1.1\r\n\r\n" 33 | 34 | # Reset the parser for the next request: 35 | # 36 | parser.reset 37 | ``` 38 | -------------------------------------------------------------------------------- /spec/integration/callbacks/url_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: url" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_url(*args) 12 | @calls << [:on_url, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_url, ["/"]] 22 | ]) 23 | end 24 | end 25 | 26 | context "request" do 27 | let(:type) { 28 | :request 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /mri/llhttp.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("../lib/llhttp/version", __FILE__) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "llhttp" 7 | spec.version = LLHttp::VERSION 8 | spec.summary = "Ruby bindings for llhttp." 9 | spec.description = spec.summary 10 | 11 | spec.author = "Bryan Powell" 12 | spec.email = "bryan@bryanp.org" 13 | spec.homepage = "https://github.com/bryanp/llhttp/" 14 | 15 | spec.required_ruby_version = ">= 2.6.7" 16 | 17 | spec.license = "MPL-2.0" 18 | 19 | spec.files = Dir["CHANGELOG.md", "README.md", "LICENSE", "lib/**/*", "ext/**/*"] 20 | spec.require_path = "lib" 21 | 22 | spec.extensions = %w[ext/llhttp/extconf.rb] 23 | end 24 | -------------------------------------------------------------------------------- /spec/integration/callbacks/status_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: status" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_status(*args) 12 | @calls << [:on_status, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_status, ["OK"]] 22 | ]) 23 | end 24 | end 25 | 26 | context "response" do 27 | let(:type) { 28 | :response 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/callbacks/url_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: url complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_url_complete(*args) 12 | @calls << [:on_url_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_url_complete, []] 22 | ]) 23 | end 24 | end 25 | 26 | context "request" do 27 | let(:type) { 28 | :request 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/introspection/method_name_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "introspection: method name" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete 12 | @context.instance_variable_set(:@method_name, @context.instance.method_name) 13 | end 14 | } 15 | } 16 | 17 | it "returns the content" do 18 | parse 19 | 20 | expect(@method_name).to eq("GET") 21 | end 22 | end 23 | 24 | context "request" do 25 | let(:type) { 26 | :request 27 | } 28 | 29 | include_examples "examples" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/introspection/status_code_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "introspection: content length" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete 12 | @context.instance_variable_set(:@status_code, @context.instance.status_code) 13 | end 14 | } 15 | } 16 | 17 | it "returns the content" do 18 | parse 19 | 20 | expect(@status_code).to eq(200) 21 | end 22 | end 23 | 24 | context "response" do 25 | let(:type) { 26 | :response 27 | } 28 | 29 | include_examples "examples" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/callbacks/status_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: status complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_status_complete(*args) 12 | @calls << [:on_status_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_status_complete, []] 22 | ]) 23 | end 24 | end 25 | 26 | context "response" do 27 | let(:type) { 28 | :response 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/acceptance/server_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "timeout" 4 | require "async/io" 5 | require "llhttp" 6 | 7 | require_relative "support/server" 8 | 9 | RSpec.describe "parsing in context of a server" do 10 | before do 11 | @pid = Process.fork { 12 | endpoint = Async::IO::Endpoint.tcp("0.0.0.0", 9000) 13 | 14 | Async do 15 | Server.new(endpoint).run 16 | 17 | parser = LLHttp::Parser.new(LLHttp::Delegate.new, type: :request) 18 | parser.parse("GET / HTTP/1.1\r\n") 19 | end 20 | } 21 | end 22 | 23 | after do 24 | Process.kill("HUP", @pid) 25 | end 26 | 27 | it "parses" do 28 | Timeout.timeout(1) do 29 | expect(system("curl -v http://localhost:9000")).to be(true) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /ffi/llhttp-ffi.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("../lib/llhttp/version", __FILE__) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "llhttp-ffi" 7 | spec.version = LLHttp::VERSION 8 | spec.summary = "Ruby FFI bindings for llhttp." 9 | spec.description = spec.summary 10 | 11 | spec.author = "Bryan Powell" 12 | spec.email = "bryan@bryanp.org" 13 | spec.homepage = "https://github.com/bryanp/llhttp/" 14 | 15 | spec.required_ruby_version = ">= 2.5.0" 16 | 17 | spec.license = "MPL-2.0" 18 | 19 | spec.files = Dir["CHANGELOG.md", "README.md", "LICENSE", "lib/**/*", "ext/**/*"] 20 | spec.require_path = "lib" 21 | 22 | spec.extensions = %w[ext/Rakefile] 23 | 24 | spec.add_dependency "ffi-compiler", "~> 1.0" 25 | spec.add_dependency "rake", "~> 13.0" 26 | end 27 | -------------------------------------------------------------------------------- /spec/integration/callbacks/message_begin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: message begin" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_message_begin(*args) 12 | @calls << [:on_message_begin, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_message_begin, []] 22 | ]) 23 | end 24 | end 25 | 26 | context "request" do 27 | let(:type) { 28 | :request 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | 34 | context "response" do 35 | let(:type) { 36 | :response 37 | } 38 | 39 | include_examples "examples" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/integration/introspection/http_major_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "introspection: http major" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete 12 | @context.instance_variable_set(:@http_major, @context.instance.http_major) 13 | end 14 | } 15 | } 16 | 17 | it "returns the http major version" do 18 | parse 19 | 20 | expect(@http_major).to eq(1) 21 | end 22 | end 23 | 24 | context "request" do 25 | let(:type) { 26 | :request 27 | } 28 | 29 | include_examples "examples" 30 | end 31 | 32 | context "response" do 33 | let(:type) { 34 | :response 35 | } 36 | 37 | include_examples "examples" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/introspection/http_minor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "introspection: http minor" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete 12 | @context.instance_variable_set(:@http_minor, @context.instance.http_minor) 13 | end 14 | } 15 | } 16 | 17 | it "returns the http minor version" do 18 | parse 19 | 20 | expect(@http_minor).to eq(1) 21 | end 22 | end 23 | 24 | context "request" do 25 | let(:type) { 26 | :request 27 | } 28 | 29 | include_examples "examples" 30 | end 31 | 32 | context "response" do 33 | let(:type) { 34 | :response 35 | } 36 | 37 | include_examples "examples" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/callbacks/headers_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: headers complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete(*args) 12 | @calls << [:on_headers_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_headers_complete, []] 22 | ]) 23 | end 24 | end 25 | 26 | context "request" do 27 | let(:type) { 28 | :request 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | 34 | context "response" do 35 | let(:type) { 36 | :response 37 | } 38 | 39 | include_examples "examples" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/integration/callbacks/message_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: message complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_message_complete(*args) 12 | @calls << [:on_message_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_message_complete, []] 22 | ]) 23 | end 24 | end 25 | 26 | context "request" do 27 | let(:type) { 28 | :request 29 | } 30 | 31 | include_examples "examples" 32 | end 33 | 34 | context "response" do 35 | let(:type) { 36 | :response 37 | } 38 | 39 | include_examples "examples" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/integration/introspection/content_length_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "introspection: content length" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_headers_complete 12 | @context.instance_variable_set(:@content_length, @context.instance.content_length) 13 | end 14 | } 15 | } 16 | 17 | it "returns the content length" do 18 | parse 19 | 20 | expect(@content_length).to eq(18) 21 | end 22 | end 23 | 24 | context "request" do 25 | let(:type) { 26 | :request 27 | } 28 | 29 | include_examples "examples" 30 | end 31 | 32 | context "response" do 33 | let(:type) { 34 | :response 35 | } 36 | 37 | include_examples "examples" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/parser_errors_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "support/context/parsing" 4 | 5 | RSpec.describe "errors when parsing requests" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | it "raises an error wrapping the llhttp error" do 10 | expect { 11 | parse 12 | }.to raise_error(LLHttp::Error, "Error Parsing data: HPE_INVALID_HEADER_TOKEN Invalid header token") 13 | end 14 | end 15 | 16 | context "request" do 17 | let(:type) { 18 | :request 19 | } 20 | 21 | let(:fixture) { 22 | :invalid_request_header 23 | } 24 | 25 | include_examples "examples" 26 | end 27 | 28 | context "response" do 29 | let(:type) { 30 | :response 31 | } 32 | 33 | let(:fixture) { 34 | :invalid_response_header 35 | } 36 | 37 | include_examples "examples" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/callbacks/body_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: body" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_body(*args) 12 | @calls << [:on_body, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_body, ["body1\n"]], 22 | [:on_body, ["body2\n"]], 23 | [:on_body, ["body3\n"]] 24 | ]) 25 | end 26 | end 27 | 28 | context "request" do 29 | let(:type) { 30 | :request 31 | } 32 | 33 | include_examples "examples" 34 | end 35 | 36 | context "response" do 37 | let(:type) { 38 | :response 39 | } 40 | 41 | include_examples "examples" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/integration/callbacks/header_value_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: header value" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_header_value(*args) 12 | @calls << [:on_header_value, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_header_value, ["18"]], 22 | [:on_header_value, ["text/plain"]] 23 | ]) 24 | end 25 | end 26 | 27 | context "request" do 28 | let(:type) { 29 | :request 30 | } 31 | 32 | include_examples "examples" 33 | end 34 | 35 | context "response" do 36 | let(:type) { 37 | :response 38 | } 39 | 40 | include_examples "examples" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/callbacks/header_field_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: header field" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_header_field(*args) 12 | @calls << [:on_header_field, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_header_field, ["content-length"]], 22 | [:on_header_field, ["content-type"]] 23 | ]) 24 | end 25 | end 26 | 27 | context "request" do 28 | let(:type) { 29 | :request 30 | } 31 | 32 | include_examples "examples" 33 | end 34 | 35 | context "response" do 36 | let(:type) { 37 | :response 38 | } 39 | 40 | include_examples "examples" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/callbacks/header_field_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: header field complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_header_field_complete(*args) 12 | @calls << [:on_header_field_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_header_field_complete, []], 22 | [:on_header_field_complete, []] 23 | ]) 24 | end 25 | end 26 | 27 | context "request" do 28 | let(:type) { 29 | :request 30 | } 31 | 32 | include_examples "examples" 33 | end 34 | 35 | context "response" do 36 | let(:type) { 37 | :response 38 | } 39 | 40 | include_examples "examples" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/callbacks/header_value_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: header value complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_header_value_complete(*args) 12 | @calls << [:on_header_value_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_header_value_complete, []], 22 | [:on_header_value_complete, []] 23 | ]) 24 | end 25 | end 26 | 27 | context "request" do 28 | let(:type) { 29 | :request 30 | } 31 | 32 | include_examples "examples" 33 | end 34 | 35 | context "response" do 36 | let(:type) { 37 | :response 38 | } 39 | 40 | include_examples "examples" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/callbacks/chunk_header_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: chunk header" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_chunk_header(*args) 12 | @calls << [:on_chunk_header, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_chunk_header, []], 22 | [:on_chunk_header, []], 23 | [:on_chunk_header, []], 24 | [:on_chunk_header, []] 25 | ]) 26 | end 27 | end 28 | 29 | context "request" do 30 | let(:type) { 31 | :request 32 | } 33 | 34 | let(:fixture) { 35 | :chunked_request 36 | } 37 | 38 | include_examples "examples" 39 | end 40 | 41 | context "response" do 42 | let(:type) { 43 | :response 44 | } 45 | 46 | let(:fixture) { 47 | :chunked_response 48 | } 49 | 50 | include_examples "examples" 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/integration/callbacks/chunk_complete_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/context/parsing" 4 | 5 | RSpec.describe "callbacks: chunk complete" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_chunk_complete(*args) 12 | @calls << [:on_chunk_complete, args] 13 | end 14 | } 15 | } 16 | 17 | it "is called" do 18 | parse 19 | 20 | expect(delegate.calls).to eq([ 21 | [:on_chunk_complete, []], 22 | [:on_chunk_complete, []], 23 | [:on_chunk_complete, []], 24 | [:on_chunk_complete, []] 25 | ]) 26 | end 27 | end 28 | 29 | context "request" do 30 | let(:type) { 31 | :request 32 | } 33 | 34 | let(:fixture) { 35 | :chunked_request 36 | } 37 | 38 | include_examples "examples" 39 | end 40 | 41 | context "response" do 42 | let(:type) { 43 | :response 44 | } 45 | 46 | let(:fixture) { 47 | :chunked_response 48 | } 49 | 50 | include_examples "examples" 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /mri/.commit/config.yml: -------------------------------------------------------------------------------- 1 | commit: 2 | changelogs: 3 | - label: "commit.changelog-mri" 4 | destination: "./CHANGELOG.md" 5 | 6 | changetypes: 7 | - label: "commit.type.add" 8 | name: "add" 9 | - label: "commit.type.chg" 10 | name: "chg" 11 | - label: "commit.type.fix" 12 | name: "fix" 13 | - label: "commit.type.dep" 14 | name: "dep" 15 | 16 | includes: 17 | - ruby-gem 18 | - oss 19 | 20 | externals: 21 | - repo: "metabahn/commit-templates" 22 | private: true 23 | 24 | license: 25 | slug: mpl 26 | name: "MPL-2.0" 27 | 28 | project: 29 | slug: "llhttp" 30 | description: "Ruby bindings for llhttp." 31 | 32 | author: 33 | name: "Bryan Powell" 34 | email: "bryan@bryanp.org" 35 | homepage: "https://github.com/bryanp/llhttp/" 36 | 37 | copyright: 38 | attribution: "Metabahn" 39 | year: 2020 40 | 41 | ruby: 42 | gem: 43 | namespace: "LLHttp" 44 | extra: |-2 45 | spec.files = Dir["CHANGELOG.md", "README.md", "LICENSE", "lib/**/*", "ext/**/*"] 46 | spec.require_path = "lib" 47 | 48 | spec.extensions = %w[ext/llhttp/extconf.rb] 49 | -------------------------------------------------------------------------------- /spec/unit/parser_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "llhttp" 4 | 5 | RSpec.describe LLHttp::Parser do 6 | let(:delegate) { 7 | LLHttp::Delegate.new 8 | } 9 | 10 | it "initializes with a default type" do 11 | instance = described_class.new(delegate) 12 | expect(instance).to be_instance_of(described_class) 13 | expect(instance.type).to eq(:both) 14 | end 15 | 16 | it "initializes with type: :request" do 17 | instance = described_class.new(delegate, type: :request) 18 | expect(instance).to be_instance_of(described_class) 19 | expect(instance.type).to eq(:request) 20 | end 21 | 22 | it "initializes with type: response" do 23 | instance = described_class.new(delegate, type: :response) 24 | expect(instance).to be_instance_of(described_class) 25 | expect(instance.type).to eq(:response) 26 | end 27 | 28 | it "initializes with type: both" do 29 | instance = described_class.new(delegate, type: :both) 30 | expect(instance).to be_instance_of(described_class) 31 | expect(instance.type).to eq(:both) 32 | end 33 | 34 | it "raises an error when type is invalid" do 35 | expect { 36 | described_class.new(delegate, type: :invalid) 37 | }.to raise_error(KeyError, "key not found: :invalid") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /ffi/.commit/config.yml: -------------------------------------------------------------------------------- 1 | commit: 2 | changelogs: 3 | - label: "commit.changelog-ffi" 4 | destination: "./CHANGELOG.md" 5 | 6 | changetypes: 7 | - label: "commit.type.add" 8 | name: "add" 9 | - label: "commit.type.chg" 10 | name: "chg" 11 | - label: "commit.type.fix" 12 | name: "fix" 13 | - label: "commit.type.dep" 14 | name: "dep" 15 | 16 | includes: 17 | - ruby-gem 18 | - oss 19 | 20 | externals: 21 | - repo: "metabahn/commit-templates" 22 | private: true 23 | 24 | license: 25 | slug: mpl 26 | name: "MPL-2.0" 27 | 28 | project: 29 | slug: "llhttp-ffi" 30 | description: "Ruby FFI bindings for llhttp." 31 | 32 | author: 33 | name: "Bryan Powell" 34 | email: "bryan@bryanp.org" 35 | homepage: "https://github.com/bryanp/llhttp/" 36 | 37 | copyright: 38 | attribution: "Metabahn" 39 | year: 2020 40 | 41 | ruby: 42 | gem: 43 | path: "llhttp" 44 | namespace: "LLHttp" 45 | version: "2.5.0" 46 | extra: |-2 47 | spec.files = Dir["CHANGELOG.md", "README.md", "LICENSE", "lib/**/*", "ext/**/*"] 48 | spec.require_path = "lib" 49 | 50 | spec.extensions = %w[ext/Rakefile] 51 | 52 | spec.add_dependency "ffi-compiler", "~> 1.0" 53 | spec.add_dependency "rake", "~> 13.0" 54 | -------------------------------------------------------------------------------- /ffi/lib/llhttp/delegate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] Delegate for handling callbacks. Subclass this object and implement necessary methods. 5 | # 6 | # class Delegate < LLHttp::Delegate 7 | # def on_message_begin 8 | # ... 9 | # end 10 | # 11 | # def on_url(url) 12 | # ... 13 | # end 14 | # 15 | # def on_status(status) 16 | # ... 17 | # end 18 | # 19 | # def on_header_field(field) 20 | # ... 21 | # end 22 | # 23 | # def on_header_value(value) 24 | # ... 25 | # end 26 | # 27 | # def on_headers_complete 28 | # ... 29 | # end 30 | # 31 | # def on_body(body) 32 | # ... 33 | # end 34 | # 35 | # def on_message_complete 36 | # ... 37 | # end 38 | # 39 | # def on_chunk_header 40 | # ... 41 | # end 42 | # 43 | # def on_chunk_complete 44 | # ... 45 | # end 46 | # 47 | # def on_url_complete 48 | # ... 49 | # end 50 | # 51 | # def on_status_complete 52 | # ... 53 | # end 54 | # 55 | # def on_header_field_complete 56 | # ... 57 | # end 58 | # 59 | # def on_header_value_complete 60 | # ... 61 | # end 62 | # end 63 | # 64 | class Delegate 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/acceptance/support/server.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "async/io" 4 | require "async/io/stream" 5 | 6 | class Delegate < LLHttp::Delegate 7 | def initialize 8 | @finished_headers = false 9 | end 10 | 11 | def finished_headers? 12 | @finished_headers == true 13 | end 14 | 15 | def on_headers_complete 16 | @finished_headers = true 17 | end 18 | 19 | def reset 20 | @finished_headers = false 21 | end 22 | end 23 | 24 | class Server 25 | CRLF = "\r\n" 26 | 27 | def initialize(endpoint) 28 | @endpoint = endpoint 29 | @delegate = Delegate.new 30 | @parser = LLHttp::Parser.new(@delegate, type: :request) 31 | end 32 | 33 | def run 34 | @endpoint.accept(&method(:accept)) 35 | end 36 | 37 | private def accept(client, address, task:) 38 | stream = Async::IO::Stream.new(client, sync: false) 39 | 40 | while parse_next(stream) 41 | stream.write("HTTP/1.1 204 No Content\r\n") 42 | stream.write("content-length: 0\r\n\r\n") 43 | stream.flush 44 | 45 | @delegate.reset 46 | 47 | task.yield 48 | end 49 | ensure 50 | stream.close 51 | 52 | @parser.reset 53 | end 54 | 55 | private def parse_next(stream) 56 | while (line = stream.read_until(CRLF, chomp: false)) 57 | @parser << line 58 | 59 | if @delegate.finished_headers? 60 | return true 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /.github/workflows/commit-tools.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: 4 | - closed 5 | push: 6 | repository_dispatch: 7 | 8 | jobs: 9 | update-changelogs: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Checkout commit tools 17 | uses: actions/checkout@v2 18 | with: 19 | repository: metabahn/commit 20 | path: .commit/tools 21 | 22 | - name: Setup ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: '2.7' 26 | 27 | - name: Update changelogs 28 | run: ./.commit/tools/bin/update-changelogs 29 | env: 30 | COMMIT__GIT_EMAIL: "bot@metabahn.com" 31 | COMMIT__GIT_NAME: "Metabahn Bot" 32 | COMMIT__GIT_TOKEN: ${{secrets.COMMIT_TOKEN}} 33 | COMMIT__GIT_USER: "metabahn-bot" 34 | 35 | update-templates: 36 | needs: 37 | - update-changelogs 38 | 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v2 44 | 45 | - name: Checkout commit tools 46 | uses: actions/checkout@v2 47 | with: 48 | repository: metabahn/commit 49 | path: .commit/tools 50 | 51 | - name: Setup ruby 52 | uses: ruby/setup-ruby@v1 53 | with: 54 | ruby-version: '2.7' 55 | 56 | - name: Update templates 57 | run: ./.commit/tools/bin/update-templates 58 | env: 59 | COMMIT__GIT_EMAIL: "bot@metabahn.com" 60 | COMMIT__GIT_NAME: "Metabahn Bot" 61 | COMMIT__GIT_TOKEN: ${{secrets.COMMIT_TOKEN}} 62 | COMMIT__GIT_USER: "metabahn-bot" 63 | -------------------------------------------------------------------------------- /spec/integration/no_content_length_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "support/context/parsing" 4 | 5 | RSpec.describe "parsing responses without a content length" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_message_begin 12 | @calls << :on_message_begin 13 | end 14 | 15 | def on_header_field(_) 16 | @calls << :on_header_field 17 | end 18 | 19 | def on_header_value(_) 20 | @calls << :on_header_value 21 | end 22 | 23 | def on_headers_complete 24 | @calls << :on_headers_complete 25 | end 26 | 27 | def on_body(_) 28 | @calls << :on_body 29 | end 30 | 31 | def on_message_complete 32 | @calls << :on_message_complete 33 | end 34 | } 35 | } 36 | 37 | it "parses correctly" do 38 | 10_000.times do 39 | parse 40 | 41 | instance.reset 42 | end 43 | 44 | expect(delegate.calls.count(:on_message_begin)).to eq(10_000) 45 | expect(delegate.calls.count(:on_header_field)).to eq(0) 46 | expect(delegate.calls.count(:on_header_value)).to eq(0) 47 | expect(delegate.calls.count(:on_headers_complete)).to eq(10_000) 48 | expect(delegate.calls.count(:on_body)).to eq(0) 49 | expect(delegate.calls.count(:on_message_complete)).to eq(0) 50 | end 51 | end 52 | 53 | context "response" do 54 | let(:type) { 55 | :response 56 | } 57 | 58 | let(:fixture) { 59 | :response_sans_content_length 60 | } 61 | 62 | include_examples "examples" 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/integration/reusing_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "support/context/parsing" 4 | 5 | RSpec.describe "reusing a parser instance" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | let(:extension) { 10 | proc { 11 | def on_message_begin 12 | @calls << :on_message_begin 13 | end 14 | 15 | def on_header_field(_) 16 | @calls << :on_header_field 17 | end 18 | 19 | def on_header_value(_) 20 | @calls << :on_header_value 21 | end 22 | 23 | def on_headers_complete 24 | @calls << :on_headers_complete 25 | end 26 | 27 | def on_body(_) 28 | @calls << :on_body 29 | end 30 | 31 | def on_message_complete 32 | @calls << :on_message_complete 33 | end 34 | } 35 | } 36 | 37 | it "parses correctly" do 38 | 10_000.times do 39 | parse 40 | 41 | instance.reset 42 | end 43 | 44 | expect(delegate.calls.count(:on_message_begin)).to eq(10_000) 45 | expect(delegate.calls.count(:on_header_field)).to eq(20_000) 46 | expect(delegate.calls.count(:on_header_value)).to eq(20_000) 47 | expect(delegate.calls.count(:on_headers_complete)).to eq(10_000) 48 | expect(delegate.calls.count(:on_body)).to eq(30_000) 49 | expect(delegate.calls.count(:on_message_complete)).to eq(10_000) 50 | end 51 | end 52 | 53 | context "request" do 54 | let(:type) { 55 | :request 56 | } 57 | 58 | include_examples "examples" 59 | end 60 | 61 | context "response" do 62 | let(:type) { 63 | :response 64 | } 65 | 66 | include_examples "examples" 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /mri/lib/llhttp/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] Wraps an llhttp context for parsing http requests and responses. 5 | # 6 | # class Delegate < LLHttp::Delegate 7 | # def on_message_begin 8 | # ... 9 | # end 10 | # 11 | # ... 12 | # end 13 | # 14 | # parser = LLHttp::Parser.new(Delegate.new, type: :request) 15 | # parser << "GET / HTTP/1.1\r\n\r\n" 16 | # parser.finish 17 | # 18 | # ... 19 | # 20 | # Introspection 21 | # 22 | # * `LLHttp::Parser#content_length` returns the content length of the current request. 23 | # * `LLHttp::Parser#method_name` returns the method name of the current response. 24 | # * `LLHttp::Parser#status_code` returns the status code of the current response. 25 | # * `LLHttp::Parser#http_major` returns the major http version of the current request/response. 26 | # * `LLHttp::Parser#http_minor` returns the minor http version of the current request/response. 27 | # * `LLHttp::Parser#keep_alive?` returns `true` if there might be more messages. 28 | # 29 | # Finishing 30 | # 31 | # Call `LLHttp::Parser#finish` when processing is complete for the current request or response. 32 | # 33 | # Resetting 34 | # 35 | # Call `LLHttp::Parser#reset` to reset the parser for the next request or response. 36 | # 37 | class Parser 38 | LLHTTP_TYPES = {both: 0, request: 1, response: 2}.freeze 39 | 40 | # [public] The parser type; one of: `both`, `request`, or `response`. 41 | # 42 | attr_reader :type 43 | 44 | def initialize(delegate, type: :both) 45 | @type, @delegate = type.to_sym, delegate 46 | 47 | llhttp_init(LLHTTP_TYPES.fetch(@type)) 48 | end 49 | end 50 | end 51 | 52 | require_relative "../llhttp_ext" 53 | -------------------------------------------------------------------------------- /mri/lib/llhttp/delegate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] Delegate for handling callbacks. Subclass this object and implement necessary methods. 5 | # 6 | # class Delegate < LLHttp::Delegate 7 | # def on_message_begin 8 | # ... 9 | # end 10 | # 11 | # def on_url(url) 12 | # ... 13 | # end 14 | # 15 | # def on_status(status) 16 | # ... 17 | # end 18 | # 19 | # def on_header_field(field) 20 | # ... 21 | # end 22 | # 23 | # def on_header_value(value) 24 | # ... 25 | # end 26 | # 27 | # def on_headers_complete 28 | # ... 29 | # end 30 | # 31 | # def on_body(body) 32 | # ... 33 | # end 34 | # 35 | # def on_message_complete 36 | # ... 37 | # end 38 | # 39 | # def on_chunk_header 40 | # ... 41 | # end 42 | # 43 | # def on_chunk_complete 44 | # ... 45 | # end 46 | # 47 | # def on_url_complete 48 | # ... 49 | # end 50 | # 51 | # def on_status_complete 52 | # ... 53 | # end 54 | # 55 | # def on_header_field_complete 56 | # ... 57 | # end 58 | # 59 | # def on_header_value_complete 60 | # ... 61 | # end 62 | # end 63 | # 64 | class Delegate 65 | private def internal_on_message_begin 66 | on_message_begin 67 | 68 | 0 69 | rescue 70 | -1 71 | end 72 | 73 | private def internal_on_headers_complete 74 | on_headers_complete 75 | 76 | 0 77 | rescue 78 | -1 79 | end 80 | 81 | private def internal_on_message_complete 82 | on_message_complete 83 | 84 | 0 85 | rescue 86 | -1 87 | end 88 | 89 | private def internal_on_chunk_header 90 | on_chunk_header 91 | 92 | 0 93 | rescue 94 | -1 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /ffi/lib/llhttp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "ffi" 4 | require "ffi-compiler/loader" 5 | 6 | module LLHttp 7 | require_relative "llhttp/delegate" 8 | require_relative "llhttp/error" 9 | require_relative "llhttp/parser" 10 | require_relative "llhttp/version" 11 | 12 | extend FFI::Library 13 | ffi_lib(FFI::Compiler::Loader.find("llhttp-ext")) 14 | 15 | callback :llhttp_data_cb, [:pointer, :size_t], :void 16 | callback :llhttp_cb, [], :int 17 | 18 | class Callbacks < FFI::Struct 19 | layout :on_message_begin, :llhttp_cb, 20 | :on_url, :llhttp_data_cb, 21 | :on_status, :llhttp_data_cb, 22 | :on_header_field, :llhttp_data_cb, 23 | :on_header_value, :llhttp_data_cb, 24 | :on_headers_complete, :llhttp_cb, 25 | :on_body, :llhttp_data_cb, 26 | :on_message_complete, :llhttp_cb, 27 | :on_chunk_header, :llhttp_cb, 28 | :on_chunk_complete, :llhttp_cb, 29 | :on_url_complete, :llhttp_cb, 30 | :on_status_complete, :llhttp_cb, 31 | :on_header_field_complete, :llhttp_cb, 32 | :on_header_value_complete, :llhttp_cb 33 | end 34 | 35 | attach_function :rb_llhttp_init, [:int, Callbacks.by_ref], :pointer 36 | attach_function :rb_llhttp_content_length, [:pointer], :uint64 37 | attach_function :rb_llhttp_method_name, [:pointer], :string 38 | attach_function :rb_llhttp_status_code, [:pointer], :uint16 39 | attach_function :rb_llhttp_http_major, [:pointer], :uint16 40 | attach_function :rb_llhttp_http_minor, [:pointer], :uint16 41 | attach_function :rb_llhttp_free, [:pointer], :void 42 | 43 | attach_function :llhttp_execute, [:pointer, :pointer, :size_t], :int 44 | attach_function :llhttp_errno_name, [:int], :string 45 | attach_function :llhttp_get_error_reason, [:pointer], :string 46 | attach_function :llhttp_should_keep_alive, [:pointer], :int 47 | attach_function :llhttp_finish, [:pointer], :int 48 | attach_function :llhttp_reset, [:pointer], :void 49 | end 50 | -------------------------------------------------------------------------------- /ffi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v0.5.0](https://github.com/bryanp/llhttp/releases/tag/2023-03-29) 2 | 3 | *released on 2023-03-29* 4 | 5 | * `chg` [#27](https://github.com/bryanp/llhttp/pull/27) Update ffi to llhttp 8.1.0 ([bryanp](https://github.com/bryanp)) 6 | 7 | ## v0.4.0 8 | 9 | *released on 2021-09-09* 10 | 11 | * `add` [#25](https://github.com/bryanp/llhttp/pull/25) Add support for resetting the parser ([bryanp](https://github.com/bryanp)) 12 | * `chg` [#23](https://github.com/bryanp/llhttp/pull/23) Update ffi to llhttp 6.0.5 ([bryanp](https://github.com/bryanp)) 13 | 14 | ## [v0.3.1](https://github.com/bryanp/llhttp/releases/tag/2021-06-25) 15 | 16 | *released on 2021-06-25* 17 | 18 | * `fix` [#22](https://github.com/bryanp/llhttp/pull/22) Fix call to ffi free ([bryanp](https://github.com/bryanp)) 19 | 20 | ## [v0.3.0](https://github.com/bryanp/llhttp/releases/tag/2021-05-13) 21 | 22 | *released on 2021-05-13* 23 | 24 | * `chg` [#19](https://github.com/bryanp/llhttp/pull/19) Add back support for Ruby 2.5 in ffi ([bryanp](https://github.com/bryanp)) 25 | 26 | ## [v0.2.0](https://github.com/bryanp/llhttp/releases/tag/2021-05-06) 27 | 28 | *released on 2021-05-06* 29 | 30 | * `chg` [#17](https://github.com/bryanp/llhttp/pull/17) Update ffi to llhttp 6.0.1 ([bryanp](https://github.com/bryanp)) 31 | * `chg` [#18](https://github.com/bryanp/llhttp/pull/18) Drop support for Ruby 2.5 ([bryanp](https://github.com/bryanp)) 32 | 33 | ## [v0.1.0](https://github.com/bryanp/llhttp/releases/tag/2021-04-06) 34 | 35 | *released on 2021-04-06* 36 | 37 | * `chg` [#14](https://github.com/bryanp/llhttp/pull/14) Update ffi to llhttp 5.1.0 ([bryanp](https://github.com/bryanp)) 38 | 39 | ## [v0.0.1](https://github.com/bryanp/llhttp/releases/tag/2021-03-04) 40 | 41 | *released on 2021-03-04* 42 | 43 | * `fix` [#13](https://github.com/bryanp/llhttp/pull/13) Resolve an issue with the parser finalizer ([bryanp](https://github.com/bryanp)) 44 | 45 | ## [v0.0.0](https://github.com/bryanp/llhttp/releases/tag/2021-03-03) 46 | 47 | *released on 2021-03-03* 48 | 49 | * `add` [#12](https://github.com/bryanp/llhttp/pull/12) Introduce llhttp-ffi for better compatibility ([bryanp](https://github.com/bryanp)) 50 | 51 | 52 | -------------------------------------------------------------------------------- /spec/integration/callback_errors_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "support/context/parsing" 4 | 5 | RSpec.describe "callback errors" do 6 | include_context "parsing" 7 | 8 | shared_examples "examples" do 9 | describe "on_message_begin" do 10 | let(:extension) { 11 | proc { 12 | def on_message_begin(*args) 13 | fail 14 | end 15 | } 16 | } 17 | 18 | it "raises expectedly" do 19 | expect { 20 | parse 21 | }.to raise_error(LLHttp::Error, "Error Parsing data: HPE_CB_MESSAGE_BEGIN `on_message_begin` callback error") 22 | end 23 | end 24 | 25 | describe "on_headers_complete" do 26 | let(:extension) { 27 | proc { 28 | def on_headers_complete(*args) 29 | fail 30 | end 31 | } 32 | } 33 | 34 | it "raises expectedly" do 35 | expect { 36 | parse 37 | }.to raise_error(LLHttp::Error, "Error Parsing data: HPE_CB_HEADERS_COMPLETE User callback error") 38 | end 39 | end 40 | 41 | describe "on_message_complete" do 42 | let(:extension) { 43 | proc { 44 | def on_message_complete(*args) 45 | fail 46 | end 47 | } 48 | } 49 | 50 | it "raises expectedly" do 51 | expect { 52 | parse 53 | }.to raise_error(LLHttp::Error, "Error Parsing data: HPE_CB_MESSAGE_COMPLETE `on_message_complete` callback error") 54 | end 55 | end 56 | 57 | describe "on_chunk_header" do 58 | let(:extension) { 59 | proc { 60 | def on_chunk_header(*args) 61 | fail 62 | end 63 | } 64 | } 65 | 66 | let(:fixture) { 67 | :"chunked_#{type}" 68 | } 69 | 70 | it "raises expectedly" do 71 | expect { 72 | parse 73 | }.to raise_error(LLHttp::Error, "Error Parsing data: HPE_CB_CHUNK_HEADER `on_chunk_header` callback error") 74 | end 75 | end 76 | end 77 | 78 | context "request" do 79 | let(:type) { 80 | :request 81 | } 82 | 83 | include_examples "examples" 84 | end 85 | 86 | context "response" do 87 | let(:type) { 88 | :response 89 | } 90 | 91 | include_examples "examples" 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/integration/support/context/parsing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "llhttp" 4 | 5 | RSpec.shared_context "parsing" do 6 | let(:instance) { 7 | LLHttp::Parser.new(delegate, type: type) 8 | } 9 | 10 | let(:type) { 11 | :both 12 | } 13 | 14 | let(:delegate) { 15 | local = self 16 | 17 | klass = Class.new(LLHttp::Delegate) { 18 | attr_reader :calls 19 | 20 | def initialize(context) 21 | @calls = [] 22 | @context = context 23 | end 24 | 25 | class_eval(&local.extension) 26 | } 27 | 28 | klass.new(local) 29 | } 30 | 31 | let(:extension) { 32 | proc {} 33 | } 34 | 35 | let(:fixture) { 36 | case type 37 | when :response 38 | :response 39 | else 40 | :request 41 | end 42 | } 43 | 44 | let(:fixtures) { 45 | { 46 | chunked_request: [ 47 | "GET / HTTP/1.1\r\n", 48 | "transfer-encoding: chunked\r\n", 49 | "\r\n", 50 | "7\r\n", 51 | "Mozilla\r\n", 52 | "9\r\n", 53 | "Developer\r\n", 54 | "7\r\n", 55 | "Network\r\n", 56 | "0\r\n", 57 | "\r\n" 58 | ], 59 | 60 | chunked_response: [ 61 | "HTTP/1.1 200 OK\r\n", 62 | "transfer-encoding: chunked\r\n", 63 | "\r\n", 64 | "7\r\n", 65 | "Mozilla\r\n", 66 | "9\r\n", 67 | "Developer\r\n", 68 | "7\r\n", 69 | "Network\r\n", 70 | "0\r\n", 71 | "\r\n" 72 | ], 73 | 74 | invalid_request_header: [ 75 | "GET / HTTP/1.1\r\n", 76 | "content-length\r\n" 77 | ], 78 | 79 | invalid_response_header: [ 80 | "HTTP/1.1 200 OK\r\n", 81 | "content-length\r\n" 82 | ], 83 | 84 | request: [ 85 | "GET / HTTP/1.1\r\n", 86 | "content-length: 18\r\n", 87 | "content-type: text/plain\r\n", 88 | "\r\n", 89 | "body1\n", 90 | "body2\n", 91 | "body3\n", 92 | "\r\n" 93 | ], 94 | 95 | response: [ 96 | "HTTP/1.1 200 OK\r\n", 97 | "content-length: 18\r\n", 98 | "content-type: text/plain\r\n", 99 | "\r\n", 100 | "body1\n", 101 | "body2\n", 102 | "body3\n", 103 | "\r\n" 104 | ], 105 | 106 | response_sans_content_length: [ 107 | "HTTP/1.1 200 OK\r\n\r\n" 108 | ] 109 | } 110 | } 111 | 112 | def parse(fixture = self.fixture) 113 | fixtures.fetch(fixture).each do |part| 114 | instance << part 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /mri/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v0.6.0](https://github.com/bryanp/llhttp/releases/tag/2023-03-29) 2 | 3 | *released on 2023-03-29* 4 | 5 | * `chg` [#28](https://github.com/bryanp/llhttp/pull/28) Update mri to llhttp 8.1.0 ([bryanp](https://github.com/bryanp)) 6 | 7 | ## [v0.5.0](https://github.com/bryanp/llhttp/releases/tag/2021-09-21) 8 | 9 | *released on 2021-09-21* 10 | 11 | * `chg` [#26](https://github.com/bryanp/llhttp/pull/26) Refactor parser data to include all values ([bryanp](https://github.com/bryanp)) 12 | 13 | ## [v0.4.0](https://github.com/bryanp/llhttp/releases/tag/2021-09-09) 14 | 15 | *released on 2021-09-09* 16 | 17 | * `add` [#25](https://github.com/bryanp/llhttp/pull/25) Add support for resetting the parser ([bryanp](https://github.com/bryanp)) 18 | * `chg` [#24](https://github.com/bryanp/llhttp/pull/24) Update mri to llhttp 6.0.5 ([bryanp](https://github.com/bryanp)) 19 | 20 | ## [v0.3.0](https://github.com/bryanp/llhttp/releases/tag/2021-05-06) 21 | 22 | *released on 2021-05-06* 23 | 24 | * `chg` [#16](https://github.com/bryanp/llhttp/pull/16) Update mri to llhttp 6.0.1 ([bryanp](https://github.com/bryanp)) 25 | * `chg` [#18](https://github.com/bryanp/llhttp/pull/18) Drop support for Ruby 2.5 ([bryanp](https://github.com/bryanp)) 26 | 27 | ## [v0.2.0](https://github.com/bryanp/llhttp/releases/tag/2021-04-06) 28 | 29 | *released on 2021-04-06* 30 | 31 | * `chg` [#15](https://github.com/bryanp/llhttp/pull/15) Update mri to llhttp 5.1.0 ([bryanp](https://github.com/bryanp)) 32 | 33 | ## [v0.1.0](https://github.com/bryanp/llhttp/releases/tag/2021-03-03) 34 | 35 | *released on 2021-03-03* 36 | 37 | * `chg` [#11](https://github.com/bryanp/llhttp/pull/11) Rename Parser#method to Parser#method_name ([bryanp](https://github.com/bryanp)) 38 | * `chg` [#10](https://github.com/bryanp/llhttp/pull/10) Only setup callbacks if they are defined on the delegate ([bryanp](https://github.com/bryanp)) 39 | * `chg` [#9](https://github.com/bryanp/llhttp/pull/9) Update llhttp to 4.0.0 ([bryanp](https://github.com/bryanp)) 40 | * `add` [#2](https://github.com/bryanp/llhttp/pull/2) Support introspection of http major/minor version ([midnight-wonderer](https://github.com/midnight-wonderer)) 41 | * `add` [#8](https://github.com/bryanp/llhttp/pull/8) Support new *_complete callbacks ([bryanp](https://github.com/bryanp)) 42 | * `chg` [#7](https://github.com/bryanp/llhttp/pull/7) Return an error status from the appropriate callbacks ([bryanp](https://github.com/bryanp)) 43 | * `chg` [#6](https://github.com/bryanp/llhttp/pull/6) Update llhttp to 3.0.1 ([bryanp](https://github.com/bryanp)) 44 | 45 | ## [v0.0.3](https://github.com/bryanp/llhttp/releases/tag/v0.0.3) 46 | 47 | *released on 2020-12-30* 48 | 49 | * `fix` [#5](https://github.com/bryanp/llhttp/pull/5) Build the extension correctly ([bryanp](https://github.com/bryanp)) 50 | 51 | ## [v0.0.2](https://github.com/bryanp/llhttp/releases/tag/v0.0.2) 52 | 53 | *released on 2020-10-11* 54 | 55 | * `fix` Correctly free parser settings when freeing the parser. 56 | 57 | ## [v0.0.1](https://github.com/bryanp/llhttp/releases/tag/v0.0.1) 58 | 59 | *released on 2020-10-11* 60 | 61 | * Initial release. 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | name: Lint ${{ matrix.ruby }} 14 | 15 | strategy: 16 | matrix: 17 | ruby: 18 | - 2.5 19 | 20 | fail-fast: false 21 | 22 | steps: 23 | - uses: actions/checkout@v1 24 | 25 | - name: Setup Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: "${{matrix.ruby}}" 29 | 30 | - name: Install Dependencies 31 | shell: bash -l -e -o pipefail {0} 32 | run: | 33 | rm -f Gemfile.lock 34 | bundle install --jobs=3 && bundle update --jobs=3 35 | 36 | - name: Run Linter 37 | shell: bash -l -e -o pipefail {0} 38 | run: | 39 | CI=true bundle exec standardrb 40 | 41 | test-ffi: 42 | runs-on: ubuntu-latest 43 | 44 | name: Test FFI ${{ matrix.ruby }} 45 | 46 | strategy: 47 | matrix: 48 | ruby: 49 | - 2.5 50 | - 2.6 51 | - 2.7 52 | - 3.0 53 | 54 | fail-fast: false 55 | 56 | steps: 57 | - uses: actions/checkout@v1 58 | 59 | - name: Setup Ruby 60 | uses: ruby/setup-ruby@v1 61 | with: 62 | ruby-version: "${{matrix.ruby}}" 63 | 64 | - name: Install Dependencies 65 | shell: bash -l -e -o pipefail {0} 66 | run: | 67 | rm -f Gemfile.lock 68 | bundle install --jobs=3 && bundle update --jobs=3 69 | 70 | - name: Install FFI Dependencies 71 | shell: bash -l -e -o pipefail {0} 72 | working-directory: ./ffi 73 | run: | 74 | rm -f Gemfile.lock 75 | bundle install --jobs=3 && bundle update --jobs=3 76 | 77 | - name: Run FFI Tests 78 | shell: bash -l -e -o pipefail {0} 79 | working-directory: ./ffi 80 | run: | 81 | CI=true bundle exec rake test 82 | 83 | test-mri: 84 | runs-on: ubuntu-latest 85 | 86 | name: Test MRI ${{ matrix.ruby }} 87 | 88 | strategy: 89 | matrix: 90 | ruby: 91 | - 2.6 92 | - 2.7 93 | - 3.0 94 | 95 | fail-fast: false 96 | 97 | steps: 98 | - uses: actions/checkout@v1 99 | 100 | - name: Setup Ruby 101 | uses: ruby/setup-ruby@v1 102 | with: 103 | ruby-version: "${{matrix.ruby}}" 104 | 105 | - name: Install Dependencies 106 | shell: bash -l -e -o pipefail {0} 107 | run: | 108 | rm -f Gemfile.lock 109 | bundle install --jobs=3 && bundle update --jobs=3 110 | 111 | - name: Install MRI Dependencies 112 | shell: bash -l -e -o pipefail {0} 113 | working-directory: ./mri 114 | run: | 115 | rm -f Gemfile.lock 116 | bundle install --jobs=3 && bundle update --jobs=3 117 | 118 | - name: Run MRI Tests 119 | shell: bash -l -e -o pipefail {0} 120 | working-directory: ./mri 121 | run: | 122 | CI=true bundle exec rake test 123 | -------------------------------------------------------------------------------- /ffi/.commit/data/releases.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - version: v0.5.0 3 | date: '2023-03-29' 4 | link: https://github.com/bryanp/llhttp/releases/tag/2023-03-29 5 | changes: 6 | - id: 27 7 | type: chg 8 | summary: Update ffi to llhttp 8.1.0 9 | link: https://github.com/bryanp/llhttp/pull/27 10 | author: 11 | username: bryanp 12 | link: https://github.com/bryanp 13 | - version: v0.4.0 14 | date: '2021-09-09' 15 | changes: 16 | - id: 25 17 | type: add 18 | summary: Add support for resetting the parser 19 | link: https://github.com/bryanp/llhttp/pull/25 20 | author: 21 | username: bryanp 22 | link: https://github.com/bryanp 23 | - id: 23 24 | type: chg 25 | summary: Update ffi to llhttp 6.0.5 26 | link: https://github.com/bryanp/llhttp/pull/23 27 | author: 28 | username: bryanp 29 | link: https://github.com/bryanp 30 | - version: v0.3.1 31 | date: '2021-06-25' 32 | link: https://github.com/bryanp/llhttp/releases/tag/2021-06-25 33 | changes: 34 | - id: 22 35 | type: fix 36 | summary: Fix call to ffi free 37 | link: https://github.com/bryanp/llhttp/pull/22 38 | author: 39 | username: bryanp 40 | link: https://github.com/bryanp 41 | - version: v0.3.0 42 | date: '2021-05-13' 43 | link: https://github.com/bryanp/llhttp/releases/tag/2021-05-13 44 | changes: 45 | - id: 19 46 | type: chg 47 | summary: Add back support for Ruby 2.5 in ffi 48 | link: https://github.com/bryanp/llhttp/pull/19 49 | author: 50 | username: bryanp 51 | link: https://github.com/bryanp 52 | - version: v0.2.0 53 | date: '2021-05-06' 54 | link: https://github.com/bryanp/llhttp/releases/tag/2021-05-06 55 | changes: 56 | - id: 17 57 | type: chg 58 | summary: Update ffi to llhttp 6.0.1 59 | link: https://github.com/bryanp/llhttp/pull/17 60 | author: 61 | username: bryanp 62 | link: https://github.com/bryanp 63 | - id: 18 64 | type: chg 65 | summary: Drop support for Ruby 2.5 66 | link: https://github.com/bryanp/llhttp/pull/18 67 | author: 68 | username: bryanp 69 | link: https://github.com/bryanp 70 | - version: v0.1.0 71 | date: '2021-04-06' 72 | link: https://github.com/bryanp/llhttp/releases/tag/2021-04-06 73 | changes: 74 | - id: 14 75 | type: chg 76 | summary: Update ffi to llhttp 5.1.0 77 | link: https://github.com/bryanp/llhttp/pull/14 78 | author: 79 | username: bryanp 80 | link: https://github.com/bryanp 81 | - version: v0.0.1 82 | date: '2021-03-04' 83 | link: https://github.com/bryanp/llhttp/releases/tag/2021-03-04 84 | changes: 85 | - id: 13 86 | type: fix 87 | summary: Resolve an issue with the parser finalizer 88 | link: https://github.com/bryanp/llhttp/pull/13 89 | author: 90 | username: bryanp 91 | link: https://github.com/bryanp 92 | - version: v0.0.0 93 | date: '2021-03-03' 94 | link: https://github.com/bryanp/llhttp/releases/tag/2021-03-03 95 | changes: 96 | - id: 12 97 | type: add 98 | summary: Introduce llhttp-ffi for better compatibility 99 | link: https://github.com/bryanp/llhttp/pull/12 100 | author: 101 | username: bryanp 102 | link: https://github.com/bryanp 103 | -------------------------------------------------------------------------------- /mri/.commit/data/releases.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - version: v0.6.0 3 | date: '2023-03-29' 4 | link: https://github.com/bryanp/llhttp/releases/tag/2023-03-29 5 | changes: 6 | - id: 28 7 | type: chg 8 | summary: Update mri to llhttp 8.1.0 9 | link: https://github.com/bryanp/llhttp/pull/28 10 | author: 11 | username: bryanp 12 | link: https://github.com/bryanp 13 | - version: v0.5.0 14 | date: '2021-09-21' 15 | link: https://github.com/bryanp/llhttp/releases/tag/2021-09-21 16 | changes: 17 | - id: 26 18 | type: chg 19 | summary: Refactor parser data to include all values 20 | link: https://github.com/bryanp/llhttp/pull/26 21 | author: 22 | username: bryanp 23 | link: https://github.com/bryanp 24 | - version: v0.4.0 25 | date: '2021-09-09' 26 | link: https://github.com/bryanp/llhttp/releases/tag/2021-09-09 27 | changes: 28 | - id: 25 29 | type: add 30 | summary: Add support for resetting the parser 31 | link: https://github.com/bryanp/llhttp/pull/25 32 | author: 33 | username: bryanp 34 | link: https://github.com/bryanp 35 | - id: 24 36 | type: chg 37 | summary: Update mri to llhttp 6.0.5 38 | link: https://github.com/bryanp/llhttp/pull/24 39 | author: 40 | username: bryanp 41 | link: https://github.com/bryanp 42 | - version: v0.3.0 43 | date: '2021-05-06' 44 | link: https://github.com/bryanp/llhttp/releases/tag/2021-05-06 45 | changes: 46 | - id: 16 47 | type: chg 48 | summary: Update mri to llhttp 6.0.1 49 | link: https://github.com/bryanp/llhttp/pull/16 50 | author: 51 | username: bryanp 52 | link: https://github.com/bryanp 53 | - id: 18 54 | type: chg 55 | summary: Drop support for Ruby 2.5 56 | link: https://github.com/bryanp/llhttp/pull/18 57 | author: 58 | username: bryanp 59 | link: https://github.com/bryanp 60 | - version: v0.2.0 61 | date: '2021-04-06' 62 | link: https://github.com/bryanp/llhttp/releases/tag/2021-04-06 63 | changes: 64 | - id: 15 65 | type: chg 66 | summary: Update mri to llhttp 5.1.0 67 | link: https://github.com/bryanp/llhttp/pull/15 68 | author: 69 | username: bryanp 70 | link: https://github.com/bryanp 71 | - version: v0.1.0 72 | date: '2021-03-03' 73 | link: https://github.com/bryanp/llhttp/releases/tag/2021-03-03 74 | changes: 75 | - id: 11 76 | type: chg 77 | summary: Rename Parser#method to Parser#method_name 78 | link: https://github.com/bryanp/llhttp/pull/11 79 | author: 80 | username: bryanp 81 | link: https://github.com/bryanp 82 | - id: 10 83 | type: chg 84 | summary: Only setup callbacks if they are defined on the delegate 85 | link: https://github.com/bryanp/llhttp/pull/10 86 | author: 87 | username: bryanp 88 | link: https://github.com/bryanp 89 | - id: 9 90 | type: chg 91 | summary: Update llhttp to 4.0.0 92 | link: https://github.com/bryanp/llhttp/pull/9 93 | author: 94 | username: bryanp 95 | link: https://github.com/bryanp 96 | - id: 2 97 | type: add 98 | summary: Support introspection of http major/minor version 99 | link: https://github.com/bryanp/llhttp/pull/2 100 | author: 101 | username: midnight-wonderer 102 | link: https://github.com/midnight-wonderer 103 | - id: 8 104 | type: add 105 | summary: Support new *_complete callbacks 106 | link: https://github.com/bryanp/llhttp/pull/8 107 | author: 108 | username: bryanp 109 | link: https://github.com/bryanp 110 | - id: 7 111 | type: chg 112 | summary: Return an error status from the appropriate callbacks 113 | link: https://github.com/bryanp/llhttp/pull/7 114 | author: 115 | username: bryanp 116 | link: https://github.com/bryanp 117 | - id: 6 118 | type: chg 119 | summary: Update llhttp to 3.0.1 120 | link: https://github.com/bryanp/llhttp/pull/6 121 | author: 122 | username: bryanp 123 | link: https://github.com/bryanp 124 | - version: v0.0.3 125 | date: '2020-12-30' 126 | link: https://github.com/bryanp/llhttp/releases/tag/v0.0.3 127 | changes: 128 | - id: 5 129 | type: fix 130 | summary: Build the extension correctly 131 | link: https://github.com/bryanp/llhttp/pull/5 132 | author: 133 | username: bryanp 134 | link: https://github.com/bryanp 135 | - version: v0.0.2 136 | date: '2020-10-11' 137 | link: https://github.com/bryanp/llhttp/releases/tag/v0.0.2 138 | changes: 139 | - type: fix 140 | summary: Correctly free parser settings when freeing the parser. 141 | - version: v0.0.1 142 | date: '2020-10-11' 143 | link: https://github.com/bryanp/llhttp/releases/tag/v0.0.1 144 | changes: 145 | - summary: Initial release. 146 | -------------------------------------------------------------------------------- /ffi/lib/llhttp/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LLHttp 4 | # [public] Wraps an llhttp context for parsing http requests and responses. 5 | # 6 | # class Delegate < LLHttp::Delegate 7 | # def on_message_begin 8 | # ... 9 | # end 10 | # 11 | # ... 12 | # end 13 | # 14 | # parser = LLHttp::Parser.new(Delegate.new, type: :request) 15 | # parser << "GET / HTTP/1.1\r\n\r\n" 16 | # parser.finish 17 | # 18 | # ... 19 | # 20 | # Introspection 21 | # 22 | # * `LLHttp::Parser#content_length` returns the content length of the current request. 23 | # * `LLHttp::Parser#method_name` returns the method name of the current response. 24 | # * `LLHttp::Parser#status_code` returns the status code of the current response. 25 | # * `LLHttp::Parser#http_major` returns the major http version of the current request/response. 26 | # * `LLHttp::Parser#http_minor` returns the minor http version of the current request/response. 27 | # * `LLHttp::Parser#keep_alive?` returns `true` if there might be more messages. 28 | # 29 | # Finishing 30 | # 31 | # Call `LLHttp::Parser#finish` when processing is complete for the current request or response. 32 | # 33 | class Parser 34 | LLHTTP_TYPES = {both: 0, request: 1, response: 2}.freeze 35 | 36 | CALLBACKS = %i[ 37 | on_message_begin 38 | on_headers_complete 39 | on_message_complete 40 | on_chunk_header 41 | on_chunk_complete 42 | on_url_complete 43 | on_status_complete 44 | on_header_field_complete 45 | on_header_value_complete 46 | ].freeze 47 | 48 | CALLBACKS_WITH_DATA = %i[ 49 | on_url 50 | on_status 51 | on_header_field 52 | on_header_value 53 | on_body 54 | ].freeze 55 | 56 | # [public] The parser type; one of: `:both`, `:request`, or `:response`. 57 | # 58 | attr_reader :type 59 | 60 | def initialize(delegate, type: :both) 61 | @type, @delegate = type.to_sym, delegate 62 | 63 | @callbacks = Callbacks.new 64 | 65 | (CALLBACKS + CALLBACKS_WITH_DATA).each do |callback| 66 | if delegate.respond_to?(callback) 67 | @callbacks[callback] = method(callback).to_proc 68 | end 69 | end 70 | 71 | @pointer = LLHttp.rb_llhttp_init(LLHTTP_TYPES.fetch(@type), @callbacks) 72 | 73 | ObjectSpace.define_finalizer(self, self.class.free(@pointer)) 74 | end 75 | 76 | # [public] Parse the given data. 77 | # 78 | def parse(data) 79 | errno = LLHttp.llhttp_execute(@pointer, data, data.length) 80 | raise build_error(errno) if errno > 0 81 | end 82 | alias_method :<<, :parse 83 | 84 | # [public] Get the content length of the current request. 85 | # 86 | def content_length 87 | LLHttp.rb_llhttp_content_length(@pointer) 88 | end 89 | 90 | # [public] Get the method of the current response. 91 | # 92 | def method_name 93 | LLHttp.rb_llhttp_method_name(@pointer) 94 | end 95 | 96 | # [public] Get the status code of the current response. 97 | # 98 | def status_code 99 | LLHttp.rb_llhttp_status_code(@pointer) 100 | end 101 | 102 | # [public] Get the major http version of the current request/response. 103 | # 104 | def http_major 105 | LLHttp.rb_llhttp_http_major(@pointer) 106 | end 107 | 108 | # [public] Get the minor http version of the current request/response. 109 | # 110 | def http_minor 111 | LLHttp.rb_llhttp_http_minor(@pointer) 112 | end 113 | 114 | # [public] Returns `true` if there might be more messages. 115 | # 116 | def keep_alive? 117 | LLHttp.llhttp_should_keep_alive(@pointer) == 1 118 | end 119 | 120 | # [public] Tells the parser we are finished. 121 | # 122 | def finish 123 | LLHttp.llhttp_finish(@pointer) 124 | end 125 | 126 | # [public] Get ready to parse the next request/response. 127 | # 128 | def reset 129 | LLHttp.llhttp_reset(@pointer) 130 | end 131 | 132 | CALLBACKS.each do |callback| 133 | class_eval( 134 | <<~RB, __FILE__, __LINE__ + 1 135 | private def #{callback} 136 | @delegate.#{callback} 137 | 138 | 0 139 | rescue 140 | -1 141 | end 142 | RB 143 | ) 144 | end 145 | 146 | CALLBACKS_WITH_DATA.each do |callback| 147 | class_eval( 148 | <<~RB, __FILE__, __LINE__ + 1 149 | private def #{callback}(buffer, length) 150 | @delegate.#{callback}(buffer.get_bytes(0, length)) 151 | end 152 | RB 153 | ) 154 | end 155 | 156 | private def build_error(errno) 157 | Error.new("Error Parsing data: #{LLHttp.llhttp_errno_name(errno)} #{LLHttp.llhttp_get_error_reason(@pointer)}") 158 | end 159 | 160 | def self.free(pointer) 161 | proc { LLHttp.rb_llhttp_free(pointer) } 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /ffi/ext/llhttp/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 (parser->upgrade && (parser->method == HTTP_CONNECT || 43 | (parser->flags & F_SKIPBODY) || !hasBody)) { 44 | /* Exit, the rest of the message is in a different protocol. */ 45 | return 1; 46 | } 47 | 48 | if (parser->flags & F_SKIPBODY) { 49 | return 0; 50 | } else if (parser->flags & F_CHUNKED) { 51 | /* chunked encoding - ignore Content-Length header, prepare for a chunk */ 52 | return 2; 53 | } else if (parser->flags & F_TRANSFER_ENCODING) { 54 | if (parser->type == HTTP_REQUEST && 55 | (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && 56 | (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { 57 | /* RFC 7230 3.3.3 */ 58 | 59 | /* If a Transfer-Encoding header field 60 | * is present in a request and the chunked transfer coding is not 61 | * the final encoding, the message body length cannot be determined 62 | * reliably; the server MUST respond with the 400 (Bad Request) 63 | * status code and then close the connection. 64 | */ 65 | return 5; 66 | } else { 67 | /* RFC 7230 3.3.3 */ 68 | 69 | /* If a Transfer-Encoding header field is present in a response and 70 | * the chunked transfer coding is not the final encoding, the 71 | * message body length is determined by reading the connection until 72 | * it is closed by the server. 73 | */ 74 | return 4; 75 | } 76 | } else { 77 | if (!(parser->flags & F_CONTENT_LENGTH)) { 78 | if (!llhttp_message_needs_eof(parser)) { 79 | /* Assume content-length 0 - read the next */ 80 | return 0; 81 | } else { 82 | /* Read body until EOF */ 83 | return 4; 84 | } 85 | } else if (parser->content_length == 0) { 86 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 87 | return 0; 88 | } else { 89 | /* Content-Length header given and non-zero */ 90 | return 3; 91 | } 92 | } 93 | } 94 | 95 | 96 | int llhttp__after_message_complete(llhttp_t* parser, const char* p, 97 | const char* endp) { 98 | int should_keep_alive; 99 | 100 | should_keep_alive = llhttp_should_keep_alive(parser); 101 | parser->finish = HTTP_FINISH_SAFE; 102 | parser->flags = 0; 103 | 104 | /* NOTE: this is ignored in loose parsing mode */ 105 | return should_keep_alive; 106 | } 107 | 108 | 109 | int llhttp_message_needs_eof(const llhttp_t* parser) { 110 | if (parser->type == HTTP_REQUEST) { 111 | return 0; 112 | } 113 | 114 | /* See RFC 2616 section 4.4 */ 115 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 116 | parser->status_code == 204 || /* No Content */ 117 | parser->status_code == 304 || /* Not Modified */ 118 | (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ 119 | return 0; 120 | } 121 | 122 | /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ 123 | if ((parser->flags & F_TRANSFER_ENCODING) && 124 | (parser->flags & F_CHUNKED) == 0) { 125 | return 1; 126 | } 127 | 128 | if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { 129 | return 0; 130 | } 131 | 132 | return 1; 133 | } 134 | 135 | 136 | int llhttp_should_keep_alive(const llhttp_t* parser) { 137 | if (parser->http_major > 0 && parser->http_minor > 0) { 138 | /* HTTP/1.1 */ 139 | if (parser->flags & F_CONNECTION_CLOSE) { 140 | return 0; 141 | } 142 | } else { 143 | /* HTTP/1.0 or earlier */ 144 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 145 | return 0; 146 | } 147 | } 148 | 149 | return !llhttp_message_needs_eof(parser); 150 | } 151 | -------------------------------------------------------------------------------- /mri/ext/llhttp/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 (parser->upgrade && (parser->method == HTTP_CONNECT || 43 | (parser->flags & F_SKIPBODY) || !hasBody)) { 44 | /* Exit, the rest of the message is in a different protocol. */ 45 | return 1; 46 | } 47 | 48 | if (parser->flags & F_SKIPBODY) { 49 | return 0; 50 | } else if (parser->flags & F_CHUNKED) { 51 | /* chunked encoding - ignore Content-Length header, prepare for a chunk */ 52 | return 2; 53 | } else if (parser->flags & F_TRANSFER_ENCODING) { 54 | if (parser->type == HTTP_REQUEST && 55 | (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && 56 | (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { 57 | /* RFC 7230 3.3.3 */ 58 | 59 | /* If a Transfer-Encoding header field 60 | * is present in a request and the chunked transfer coding is not 61 | * the final encoding, the message body length cannot be determined 62 | * reliably; the server MUST respond with the 400 (Bad Request) 63 | * status code and then close the connection. 64 | */ 65 | return 5; 66 | } else { 67 | /* RFC 7230 3.3.3 */ 68 | 69 | /* If a Transfer-Encoding header field is present in a response and 70 | * the chunked transfer coding is not the final encoding, the 71 | * message body length is determined by reading the connection until 72 | * it is closed by the server. 73 | */ 74 | return 4; 75 | } 76 | } else { 77 | if (!(parser->flags & F_CONTENT_LENGTH)) { 78 | if (!llhttp_message_needs_eof(parser)) { 79 | /* Assume content-length 0 - read the next */ 80 | return 0; 81 | } else { 82 | /* Read body until EOF */ 83 | return 4; 84 | } 85 | } else if (parser->content_length == 0) { 86 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 87 | return 0; 88 | } else { 89 | /* Content-Length header given and non-zero */ 90 | return 3; 91 | } 92 | } 93 | } 94 | 95 | 96 | int llhttp__after_message_complete(llhttp_t* parser, const char* p, 97 | const char* endp) { 98 | int should_keep_alive; 99 | 100 | should_keep_alive = llhttp_should_keep_alive(parser); 101 | parser->finish = HTTP_FINISH_SAFE; 102 | parser->flags = 0; 103 | 104 | /* NOTE: this is ignored in loose parsing mode */ 105 | return should_keep_alive; 106 | } 107 | 108 | 109 | int llhttp_message_needs_eof(const llhttp_t* parser) { 110 | if (parser->type == HTTP_REQUEST) { 111 | return 0; 112 | } 113 | 114 | /* See RFC 2616 section 4.4 */ 115 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 116 | parser->status_code == 204 || /* No Content */ 117 | parser->status_code == 304 || /* Not Modified */ 118 | (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ 119 | return 0; 120 | } 121 | 122 | /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ 123 | if ((parser->flags & F_TRANSFER_ENCODING) && 124 | (parser->flags & F_CHUNKED) == 0) { 125 | return 1; 126 | } 127 | 128 | if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { 129 | return 0; 130 | } 131 | 132 | return 1; 133 | } 134 | 135 | 136 | int llhttp_should_keep_alive(const llhttp_t* parser) { 137 | if (parser->http_major > 0 && parser->http_minor > 0) { 138 | /* HTTP/1.1 */ 139 | if (parser->flags & F_CONNECTION_CLOSE) { 140 | return 0; 141 | } 142 | } else { 143 | /* HTTP/1.0 or earlier */ 144 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 145 | return 0; 146 | } 147 | } 148 | 149 | return !llhttp_message_needs_eof(parser); 150 | } 151 | -------------------------------------------------------------------------------- /ffi/ext/llhttp/llhttp_ext.c: -------------------------------------------------------------------------------- 1 | /* 2 | This software is licensed under the MPL-2.0 License. 3 | 4 | Copyright Bryan Powell, 2020. 5 | */ 6 | 7 | #include 8 | #include 9 | #include "llhttp.h" 10 | 11 | typedef struct rb_llhttp_callbacks_s rb_llhttp_callbacks_t; 12 | 13 | typedef int (*rb_llhttp_data_cb)(const char *data, size_t length); 14 | typedef int (*rb_llhttp_cb)(); 15 | 16 | struct rb_llhttp_callbacks_s { 17 | rb_llhttp_cb on_message_begin; 18 | rb_llhttp_data_cb on_url; 19 | rb_llhttp_data_cb on_status; 20 | rb_llhttp_data_cb on_header_field; 21 | rb_llhttp_data_cb on_header_value; 22 | rb_llhttp_cb on_headers_complete; 23 | rb_llhttp_data_cb on_body; 24 | rb_llhttp_cb on_message_complete; 25 | rb_llhttp_cb on_chunk_header; 26 | rb_llhttp_cb on_chunk_complete; 27 | rb_llhttp_cb on_url_complete; 28 | rb_llhttp_cb on_status_complete; 29 | rb_llhttp_cb on_header_field_complete; 30 | rb_llhttp_cb on_header_value_complete; 31 | }; 32 | 33 | int rb_llhttp_on_message_begin(llhttp_t *parser) { 34 | rb_llhttp_callbacks_t* callbacks = parser->data; 35 | return callbacks->on_message_begin(); 36 | } 37 | 38 | int rb_llhttp_on_url(llhttp_t *parser, char *data, size_t length) { 39 | rb_llhttp_callbacks_t* callbacks = parser->data; 40 | callbacks->on_url(data, length); 41 | return 0; 42 | } 43 | 44 | int rb_llhttp_on_status(llhttp_t *parser, char *data, size_t length) { 45 | rb_llhttp_callbacks_t* callbacks = parser->data; 46 | callbacks->on_status(data, length); 47 | return 0; 48 | } 49 | 50 | int rb_llhttp_on_header_field(llhttp_t *parser, char *data, size_t length) { 51 | rb_llhttp_callbacks_t* callbacks = parser->data; 52 | callbacks->on_header_field(data, length); 53 | return 0; 54 | } 55 | 56 | int rb_llhttp_on_header_value(llhttp_t *parser, char *data, size_t length) { 57 | rb_llhttp_callbacks_t* callbacks = parser->data; 58 | callbacks->on_header_value(data, length); 59 | return 0; 60 | } 61 | 62 | int rb_llhttp_on_headers_complete(llhttp_t *parser) { 63 | rb_llhttp_callbacks_t* callbacks = parser->data; 64 | return callbacks->on_headers_complete(); 65 | } 66 | 67 | int rb_llhttp_on_body(llhttp_t *parser, char *data, size_t length) { 68 | rb_llhttp_callbacks_t* callbacks = parser->data; 69 | callbacks->on_body(data, length); 70 | return 0; 71 | } 72 | 73 | int rb_llhttp_on_message_complete(llhttp_t *parser) { 74 | rb_llhttp_callbacks_t* callbacks = parser->data; 75 | return callbacks->on_message_complete(); 76 | } 77 | 78 | int rb_llhttp_on_chunk_header(llhttp_t *parser) { 79 | rb_llhttp_callbacks_t* callbacks = parser->data; 80 | return callbacks->on_chunk_header(); 81 | } 82 | 83 | int rb_llhttp_on_chunk_complete(llhttp_t *parser) { 84 | rb_llhttp_callbacks_t* callbacks = parser->data; 85 | callbacks->on_chunk_complete(); 86 | return 0; 87 | } 88 | 89 | int rb_llhttp_on_url_complete(llhttp_t *parser) { 90 | rb_llhttp_callbacks_t* callbacks = parser->data; 91 | callbacks->on_url_complete(); 92 | return 0; 93 | } 94 | 95 | int rb_llhttp_on_status_complete(llhttp_t *parser) { 96 | rb_llhttp_callbacks_t* callbacks = parser->data; 97 | callbacks->on_status_complete(); 98 | return 0; 99 | } 100 | 101 | int rb_llhttp_on_header_field_complete(llhttp_t *parser) { 102 | rb_llhttp_callbacks_t* callbacks = parser->data; 103 | callbacks->on_header_field_complete(); 104 | return 0; 105 | } 106 | 107 | int rb_llhttp_on_header_value_complete(llhttp_t *parser) { 108 | rb_llhttp_callbacks_t* callbacks = parser->data; 109 | callbacks->on_header_value_complete(); 110 | return 0; 111 | } 112 | 113 | llhttp_t* rb_llhttp_init(int type, rb_llhttp_callbacks_t* callbacks) { 114 | llhttp_t *parser = (llhttp_t *)malloc(sizeof(llhttp_t)); 115 | llhttp_settings_t *settings = (llhttp_settings_t *)malloc(sizeof(llhttp_settings_t)); 116 | 117 | llhttp_settings_init(settings); 118 | 119 | if (callbacks->on_message_begin) { 120 | settings->on_message_begin = (llhttp_cb)rb_llhttp_on_message_begin; 121 | } 122 | 123 | if (callbacks->on_url) { 124 | settings->on_url = (llhttp_data_cb)rb_llhttp_on_url; 125 | } 126 | 127 | if (callbacks->on_status) { 128 | settings->on_status = (llhttp_data_cb)rb_llhttp_on_status; 129 | } 130 | 131 | if (callbacks->on_header_field) { 132 | settings->on_header_field = (llhttp_data_cb)rb_llhttp_on_header_field; 133 | } 134 | 135 | if (callbacks->on_header_value) { 136 | settings->on_header_value = (llhttp_data_cb)rb_llhttp_on_header_value; 137 | } 138 | 139 | if (callbacks->on_headers_complete) { 140 | settings->on_headers_complete = (llhttp_cb)rb_llhttp_on_headers_complete; 141 | } 142 | 143 | if (callbacks->on_body) { 144 | settings->on_body = (llhttp_data_cb)rb_llhttp_on_body; 145 | } 146 | 147 | if (callbacks->on_message_complete) { 148 | settings->on_message_complete = (llhttp_cb)rb_llhttp_on_message_complete; 149 | } 150 | 151 | if (callbacks->on_chunk_header) { 152 | settings->on_chunk_header = (llhttp_cb)rb_llhttp_on_chunk_header; 153 | } 154 | 155 | if (callbacks->on_chunk_complete) { 156 | settings->on_chunk_complete = (llhttp_cb)rb_llhttp_on_chunk_complete; 157 | } 158 | 159 | if (callbacks->on_url_complete) { 160 | settings->on_url_complete = (llhttp_cb)rb_llhttp_on_url_complete; 161 | } 162 | 163 | if (callbacks->on_status_complete) { 164 | settings->on_status_complete = (llhttp_cb)rb_llhttp_on_status_complete; 165 | } 166 | 167 | if (callbacks->on_header_field_complete) { 168 | settings->on_header_field_complete = (llhttp_cb)rb_llhttp_on_header_field_complete; 169 | } 170 | 171 | if (callbacks->on_header_value_complete) { 172 | settings->on_header_value_complete = (llhttp_cb)rb_llhttp_on_header_value_complete; 173 | } 174 | 175 | llhttp_init(parser, type, settings); 176 | 177 | parser->data = callbacks; 178 | 179 | return parser; 180 | } 181 | 182 | void rb_llhttp_free(llhttp_t* parser) { 183 | if (parser) { 184 | free(parser->settings); 185 | free(parser); 186 | } 187 | } 188 | 189 | uint64_t rb_llhttp_content_length(llhttp_t* parser) { 190 | return parser->content_length; 191 | } 192 | 193 | const char* rb_llhttp_method_name(llhttp_t* parser) { 194 | return llhttp_method_name(parser->method); 195 | } 196 | 197 | uint16_t rb_llhttp_status_code(llhttp_t* parser) { 198 | return parser->status_code; 199 | } 200 | 201 | uint16_t rb_llhttp_http_major(llhttp_t* parser) { 202 | return parser->http_major; 203 | } 204 | 205 | uint16_t rb_llhttp_http_minor(llhttp_t* parser) { 206 | return parser->http_minor; 207 | } 208 | 209 | const char* rb_llhttp_errno_name(llhttp_errno_t errno) { 210 | return llhttp_errno_name(errno); 211 | } 212 | 213 | const char* rb_llhttp_error_reason(llhttp_t* parser) { 214 | return parser->reason; 215 | } 216 | -------------------------------------------------------------------------------- /mri/ext/llhttp/llhttp_ext.c: -------------------------------------------------------------------------------- 1 | /* 2 | This software is licensed under the MPL-2.0 License. 3 | 4 | Copyright Bryan Powell, 2020. 5 | */ 6 | 7 | #include 8 | 9 | #include "llhttp.h" 10 | 11 | static VALUE mLLHttp, cParser, eError; 12 | 13 | typedef struct { 14 | VALUE delegate; 15 | ID on_message_begin; 16 | ID on_url; 17 | ID on_status; 18 | ID on_header_field; 19 | ID on_header_value; 20 | ID on_headers_complete; 21 | ID on_body; 22 | ID on_message_complete; 23 | ID on_chunk_header; 24 | ID on_chunk_complete; 25 | ID on_url_complete; 26 | ID on_status_complete; 27 | ID on_header_field_complete; 28 | ID on_header_value_complete; 29 | } rb_llhttp_parser_data; 30 | 31 | static void rb_llhttp_free(llhttp_t *parser) { 32 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 33 | 34 | // If `parserData` is not `0`, it (and settings) were initialized. 35 | // 36 | if (parserData != 0) { 37 | free(parserData); 38 | free(parser->settings); 39 | } 40 | 41 | free(parser); 42 | } 43 | 44 | VALUE rb_llhttp_allocate(VALUE klass) { 45 | llhttp_t *parser = (llhttp_t *)malloc(sizeof(llhttp_t)); 46 | 47 | // Set data to false so we know when the parser has been initialized. 48 | // 49 | parser->data = 0; 50 | 51 | return Data_Wrap_Struct(klass, 0, rb_llhttp_free, parser); 52 | } 53 | 54 | VALUE rb_llhttp_callback_call(VALUE delegate, ID method) { 55 | return rb_funcall(delegate, method, 0); 56 | } 57 | 58 | void rb_llhttp_data_callback_call(VALUE delegate, ID method, char *data, size_t length) { 59 | rb_funcall(delegate, method, 1, rb_str_new(data, length)); 60 | } 61 | 62 | int rb_llhttp_on_message_begin(llhttp_t *parser) { 63 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 64 | 65 | return NUM2INT(rb_llhttp_callback_call(parserData->delegate, parserData->on_message_begin)); 66 | } 67 | 68 | int rb_llhttp_on_headers_complete(llhttp_t *parser) { 69 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 70 | 71 | return NUM2INT(rb_llhttp_callback_call(parserData->delegate, parserData->on_headers_complete)); 72 | } 73 | 74 | int rb_llhttp_on_message_complete(llhttp_t *parser) { 75 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 76 | 77 | return NUM2INT(rb_llhttp_callback_call(parserData->delegate, parserData->on_message_complete)); 78 | } 79 | 80 | int rb_llhttp_on_chunk_header(llhttp_t *parser) { 81 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 82 | 83 | return NUM2INT(rb_llhttp_callback_call(parserData->delegate, parserData->on_chunk_header)); 84 | } 85 | 86 | int rb_llhttp_on_url(llhttp_t *parser, char *data, size_t length) { 87 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 88 | 89 | rb_llhttp_data_callback_call(parserData->delegate, parserData->on_url, data, length); 90 | 91 | return 0; 92 | } 93 | 94 | int rb_llhttp_on_status(llhttp_t *parser, char *data, size_t length) { 95 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 96 | 97 | rb_llhttp_data_callback_call(parserData->delegate, parserData->on_status, data, length); 98 | 99 | return 0; 100 | } 101 | 102 | int rb_llhttp_on_header_field(llhttp_t *parser, char *data, size_t length) { 103 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 104 | 105 | rb_llhttp_data_callback_call(parserData->delegate, parserData->on_header_field, data, length); 106 | 107 | return 0; 108 | } 109 | 110 | int rb_llhttp_on_header_value(llhttp_t *parser, char *data, size_t length) { 111 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 112 | 113 | rb_llhttp_data_callback_call(parserData->delegate, parserData->on_header_value, data, length); 114 | 115 | return 0; 116 | } 117 | 118 | int rb_llhttp_on_body(llhttp_t *parser, char *data, size_t length) { 119 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 120 | 121 | rb_llhttp_data_callback_call(parserData->delegate, parserData->on_body, data, length); 122 | 123 | return 0; 124 | } 125 | 126 | int rb_llhttp_on_chunk_complete(llhttp_t *parser) { 127 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 128 | 129 | rb_llhttp_callback_call(parserData->delegate, parserData->on_chunk_complete); 130 | 131 | return 0; 132 | } 133 | 134 | int rb_llhttp_on_url_complete(llhttp_t *parser) { 135 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 136 | 137 | rb_llhttp_callback_call(parserData->delegate, parserData->on_url_complete); 138 | 139 | return 0; 140 | } 141 | 142 | int rb_llhttp_on_status_complete(llhttp_t *parser) { 143 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 144 | 145 | rb_llhttp_callback_call(parserData->delegate, parserData->on_status_complete); 146 | 147 | return 0; 148 | } 149 | 150 | int rb_llhttp_on_header_field_complete(llhttp_t *parser) { 151 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 152 | 153 | rb_llhttp_callback_call(parserData->delegate, parserData->on_header_field_complete); 154 | 155 | return 0; 156 | } 157 | 158 | int rb_llhttp_on_header_value_complete(llhttp_t *parser) { 159 | rb_llhttp_parser_data *parserData = (rb_llhttp_parser_data*) parser->data; 160 | 161 | rb_llhttp_callback_call(parserData->delegate, parserData->on_header_value_complete); 162 | 163 | return 0; 164 | } 165 | 166 | VALUE rb_llhttp_parse(VALUE self, VALUE data) { 167 | llhttp_t *parser; 168 | 169 | Data_Get_Struct(self, llhttp_t, parser); 170 | 171 | enum llhttp_errno err = llhttp_execute(parser, RSTRING_PTR(data), RSTRING_LEN(data)); 172 | 173 | if (err != HPE_OK) { 174 | rb_raise(eError, "Error Parsing data: %s %s", llhttp_errno_name(err), parser->reason); 175 | } 176 | 177 | return Qtrue; 178 | } 179 | 180 | VALUE rb_llhttp_finish(VALUE self) { 181 | llhttp_t *parser; 182 | 183 | Data_Get_Struct(self, llhttp_t, parser); 184 | 185 | enum llhttp_errno err = llhttp_finish(parser); 186 | 187 | if (err != HPE_OK) { 188 | rb_raise(eError, "Error Parsing data: %s %s", llhttp_errno_name(err), parser->reason); 189 | } 190 | 191 | return Qtrue; 192 | } 193 | 194 | VALUE rb_llhttp_reset(VALUE self) { 195 | llhttp_t *parser; 196 | 197 | Data_Get_Struct(self, llhttp_t, parser); 198 | 199 | llhttp_reset(parser); 200 | 201 | return Qtrue; 202 | } 203 | 204 | VALUE rb_llhttp_content_length(VALUE self) { 205 | llhttp_t *parser; 206 | 207 | Data_Get_Struct(self, llhttp_t, parser); 208 | 209 | return ULL2NUM(parser->content_length); 210 | } 211 | 212 | VALUE rb_llhttp_method_name(VALUE self) { 213 | llhttp_t *parser; 214 | 215 | Data_Get_Struct(self, llhttp_t, parser); 216 | 217 | return rb_str_new_cstr(llhttp_method_name(parser->method)); 218 | } 219 | 220 | VALUE rb_llhttp_status_code(VALUE self) { 221 | llhttp_t *parser; 222 | 223 | Data_Get_Struct(self, llhttp_t, parser); 224 | 225 | return UINT2NUM(parser->status_code); 226 | } 227 | 228 | VALUE rb_llhttp_http_major(VALUE self) { 229 | llhttp_t *parser; 230 | 231 | Data_Get_Struct(self, llhttp_t, parser); 232 | 233 | return UINT2NUM(parser->http_major); 234 | } 235 | 236 | VALUE rb_llhttp_http_minor(VALUE self) { 237 | llhttp_t *parser; 238 | 239 | Data_Get_Struct(self, llhttp_t, parser); 240 | 241 | return UINT2NUM(parser->http_minor); 242 | } 243 | 244 | VALUE rb_llhttp_keep_alive(VALUE self) { 245 | llhttp_t *parser; 246 | 247 | Data_Get_Struct(self, llhttp_t, parser); 248 | 249 | int ret = llhttp_should_keep_alive(parser); 250 | 251 | return ret == 1 ? Qtrue : Qfalse; 252 | } 253 | 254 | static VALUE rb_llhttp_init(VALUE self, VALUE type) { 255 | llhttp_t *parser; 256 | 257 | Data_Get_Struct(self, llhttp_t, parser); 258 | 259 | llhttp_settings_t *settings = (llhttp_settings_t *)malloc(sizeof(llhttp_settings_t)); 260 | llhttp_settings_init(settings); 261 | 262 | rb_llhttp_parser_data *parserData = malloc(sizeof(rb_llhttp_parser_data)); 263 | parserData->delegate = rb_iv_get(self, "@delegate"); 264 | 265 | parserData->on_message_begin = rb_intern("internal_on_message_begin"); 266 | parserData->on_headers_complete = rb_intern("internal_on_headers_complete"); 267 | parserData->on_message_complete = rb_intern("internal_on_message_complete"); 268 | parserData->on_chunk_header = rb_intern("internal_on_chunk_header"); 269 | parserData->on_url = rb_intern("on_url"); 270 | parserData->on_status = rb_intern("on_status"); 271 | parserData->on_header_field = rb_intern("on_header_field"); 272 | parserData->on_header_value = rb_intern("on_header_value"); 273 | parserData->on_body = rb_intern("on_body"); 274 | parserData->on_chunk_complete = rb_intern("on_chunk_complete"); 275 | parserData->on_url_complete = rb_intern("on_url_complete"); 276 | parserData->on_status_complete = rb_intern("on_status_complete"); 277 | parserData->on_header_field_complete = rb_intern("on_header_field_complete"); 278 | parserData->on_header_value_complete = rb_intern("on_header_value_complete"); 279 | 280 | if (rb_respond_to(parserData->delegate, rb_intern("on_message_begin"))) { 281 | settings->on_message_begin = (llhttp_cb)rb_llhttp_on_message_begin; 282 | } 283 | 284 | if (rb_respond_to(parserData->delegate, rb_intern("on_headers_complete"))) { 285 | settings->on_headers_complete = (llhttp_cb)rb_llhttp_on_headers_complete; 286 | } 287 | 288 | if (rb_respond_to(parserData->delegate, rb_intern("on_message_complete"))) { 289 | settings->on_message_complete = (llhttp_cb)rb_llhttp_on_message_complete; 290 | } 291 | 292 | if (rb_respond_to(parserData->delegate, rb_intern("on_chunk_header"))) { 293 | settings->on_chunk_header = (llhttp_cb)rb_llhttp_on_chunk_header; 294 | } 295 | 296 | if (rb_respond_to(parserData->delegate, parserData->on_url)) { 297 | settings->on_url = (llhttp_data_cb)rb_llhttp_on_url; 298 | } 299 | 300 | if (rb_respond_to(parserData->delegate, parserData->on_status)) { 301 | settings->on_status = (llhttp_data_cb)rb_llhttp_on_status; 302 | } 303 | 304 | if (rb_respond_to(parserData->delegate, parserData->on_header_field)) { 305 | settings->on_header_field = (llhttp_data_cb)rb_llhttp_on_header_field; 306 | } 307 | 308 | if (rb_respond_to(parserData->delegate, parserData->on_header_value)) { 309 | settings->on_header_value = (llhttp_data_cb)rb_llhttp_on_header_value; 310 | } 311 | 312 | if (rb_respond_to(parserData->delegate, parserData->on_body)) { 313 | settings->on_body = (llhttp_data_cb)rb_llhttp_on_body; 314 | } 315 | 316 | if (rb_respond_to(parserData->delegate, parserData->on_chunk_complete)) { 317 | settings->on_chunk_complete = (llhttp_cb)rb_llhttp_on_chunk_complete; 318 | } 319 | 320 | if (rb_respond_to(parserData->delegate, parserData->on_url_complete)) { 321 | settings->on_url_complete = (llhttp_cb)rb_llhttp_on_url_complete; 322 | } 323 | 324 | if (rb_respond_to(parserData->delegate, parserData->on_status_complete)) { 325 | settings->on_status_complete = (llhttp_cb)rb_llhttp_on_status_complete; 326 | } 327 | 328 | if (rb_respond_to(parserData->delegate, parserData->on_header_field_complete)) { 329 | settings->on_header_field_complete = (llhttp_cb)rb_llhttp_on_header_field_complete; 330 | } 331 | 332 | if (rb_respond_to(parserData->delegate, parserData->on_header_value_complete)) { 333 | settings->on_header_value_complete = (llhttp_cb)rb_llhttp_on_header_value_complete; 334 | } 335 | 336 | llhttp_init(parser, FIX2INT(type), settings); 337 | 338 | parser->data = (void*)parserData; 339 | 340 | return Qtrue; 341 | } 342 | 343 | void Init_llhttp_ext(void) { 344 | mLLHttp = rb_const_get(rb_cObject, rb_intern("LLHttp")); 345 | cParser = rb_const_get(mLLHttp, rb_intern("Parser")); 346 | eError = rb_const_get(mLLHttp, rb_intern("Error")); 347 | 348 | rb_define_alloc_func(cParser, rb_llhttp_allocate); 349 | 350 | rb_define_method(cParser, "<<", rb_llhttp_parse, 1); 351 | rb_define_method(cParser, "parse", rb_llhttp_parse, 1); 352 | rb_define_method(cParser, "finish", rb_llhttp_finish, 0); 353 | rb_define_method(cParser, "reset", rb_llhttp_reset, 0); 354 | 355 | rb_define_method(cParser, "content_length", rb_llhttp_content_length, 0); 356 | rb_define_method(cParser, "method_name", rb_llhttp_method_name, 0); 357 | rb_define_method(cParser, "status_code", rb_llhttp_status_code, 0); 358 | rb_define_method(cParser, "http_major", rb_llhttp_http_major, 0); 359 | rb_define_method(cParser, "http_minor", rb_llhttp_http_minor, 0); 360 | 361 | rb_define_method(cParser, "keep_alive?", rb_llhttp_keep_alive, 0); 362 | 363 | rb_define_private_method(cParser, "llhttp_init", rb_llhttp_init, 1); 364 | } 365 | -------------------------------------------------------------------------------- /ffi/ext/llhttp/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 | wasm_on_message_begin, 61 | wasm_on_url, 62 | wasm_on_status, 63 | NULL, 64 | NULL, 65 | wasm_on_header_field, 66 | wasm_on_header_value, 67 | NULL, 68 | NULL, 69 | wasm_on_headers_complete_wrap, 70 | wasm_on_body, 71 | wasm_on_message_complete, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL, 76 | NULL, 77 | NULL, 78 | NULL, 79 | NULL, 80 | NULL, 81 | NULL, 82 | NULL, 83 | }; 84 | 85 | 86 | llhttp_t* llhttp_alloc(llhttp_type_t type) { 87 | llhttp_t* parser = malloc(sizeof(llhttp_t)); 88 | llhttp_init(parser, type, &wasm_settings); 89 | return parser; 90 | } 91 | 92 | void llhttp_free(llhttp_t* parser) { 93 | free(parser); 94 | } 95 | 96 | #endif // defined(__wasm__) 97 | 98 | /* Some getters required to get stuff from the parser */ 99 | 100 | uint8_t llhttp_get_type(llhttp_t* parser) { 101 | return parser->type; 102 | } 103 | 104 | uint8_t llhttp_get_http_major(llhttp_t* parser) { 105 | return parser->http_major; 106 | } 107 | 108 | uint8_t llhttp_get_http_minor(llhttp_t* parser) { 109 | return parser->http_minor; 110 | } 111 | 112 | uint8_t llhttp_get_method(llhttp_t* parser) { 113 | return parser->method; 114 | } 115 | 116 | int llhttp_get_status_code(llhttp_t* parser) { 117 | return parser->status_code; 118 | } 119 | 120 | uint8_t llhttp_get_upgrade(llhttp_t* parser) { 121 | return parser->upgrade; 122 | } 123 | 124 | 125 | void llhttp_reset(llhttp_t* parser) { 126 | llhttp_type_t type = parser->type; 127 | const llhttp_settings_t* settings = parser->settings; 128 | void* data = parser->data; 129 | uint8_t lenient_flags = parser->lenient_flags; 130 | 131 | llhttp__internal_init(parser); 132 | 133 | parser->type = type; 134 | parser->settings = (void*) settings; 135 | parser->data = data; 136 | parser->lenient_flags = lenient_flags; 137 | } 138 | 139 | 140 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { 141 | return llhttp__internal_execute(parser, data, data + len); 142 | } 143 | 144 | 145 | void llhttp_settings_init(llhttp_settings_t* settings) { 146 | memset(settings, 0, sizeof(*settings)); 147 | } 148 | 149 | 150 | llhttp_errno_t llhttp_finish(llhttp_t* parser) { 151 | int err; 152 | 153 | /* We're in an error state. Don't bother doing anything. */ 154 | if (parser->error != 0) { 155 | return 0; 156 | } 157 | 158 | switch (parser->finish) { 159 | case HTTP_FINISH_SAFE_WITH_CB: 160 | CALLBACK_MAYBE(parser, on_message_complete); 161 | if (err != HPE_OK) return err; 162 | 163 | /* FALLTHROUGH */ 164 | case HTTP_FINISH_SAFE: 165 | return HPE_OK; 166 | case HTTP_FINISH_UNSAFE: 167 | parser->reason = "Invalid EOF state"; 168 | return HPE_INVALID_EOF_STATE; 169 | default: 170 | abort(); 171 | } 172 | } 173 | 174 | 175 | void llhttp_pause(llhttp_t* parser) { 176 | if (parser->error != HPE_OK) { 177 | return; 178 | } 179 | 180 | parser->error = HPE_PAUSED; 181 | parser->reason = "Paused"; 182 | } 183 | 184 | 185 | void llhttp_resume(llhttp_t* parser) { 186 | if (parser->error != HPE_PAUSED) { 187 | return; 188 | } 189 | 190 | parser->error = 0; 191 | } 192 | 193 | 194 | void llhttp_resume_after_upgrade(llhttp_t* parser) { 195 | if (parser->error != HPE_PAUSED_UPGRADE) { 196 | return; 197 | } 198 | 199 | parser->error = 0; 200 | } 201 | 202 | 203 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { 204 | return parser->error; 205 | } 206 | 207 | 208 | const char* llhttp_get_error_reason(const llhttp_t* parser) { 209 | return parser->reason; 210 | } 211 | 212 | 213 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { 214 | parser->reason = reason; 215 | } 216 | 217 | 218 | const char* llhttp_get_error_pos(const llhttp_t* parser) { 219 | return parser->error_pos; 220 | } 221 | 222 | 223 | const char* llhttp_errno_name(llhttp_errno_t err) { 224 | #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; 225 | switch (err) { 226 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 227 | default: abort(); 228 | } 229 | #undef HTTP_ERRNO_GEN 230 | } 231 | 232 | 233 | const char* llhttp_method_name(llhttp_method_t method) { 234 | #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; 235 | switch (method) { 236 | HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) 237 | default: abort(); 238 | } 239 | #undef HTTP_METHOD_GEN 240 | } 241 | 242 | const char* llhttp_status_name(llhttp_status_t status) { 243 | #define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; 244 | switch (status) { 245 | HTTP_STATUS_MAP(HTTP_STATUS_GEN) 246 | default: abort(); 247 | } 248 | #undef HTTP_STATUS_GEN 249 | } 250 | 251 | 252 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { 253 | if (enabled) { 254 | parser->lenient_flags |= LENIENT_HEADERS; 255 | } else { 256 | parser->lenient_flags &= ~LENIENT_HEADERS; 257 | } 258 | } 259 | 260 | 261 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { 262 | if (enabled) { 263 | parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; 264 | } else { 265 | parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; 266 | } 267 | } 268 | 269 | 270 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { 271 | if (enabled) { 272 | parser->lenient_flags |= LENIENT_KEEP_ALIVE; 273 | } else { 274 | parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; 275 | } 276 | } 277 | 278 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { 279 | if (enabled) { 280 | parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; 281 | } else { 282 | parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; 283 | } 284 | } 285 | 286 | /* Callbacks */ 287 | 288 | 289 | int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { 290 | int err; 291 | CALLBACK_MAYBE(s, on_message_begin); 292 | return err; 293 | } 294 | 295 | 296 | int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { 297 | int err; 298 | SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); 299 | return err; 300 | } 301 | 302 | 303 | int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { 304 | int err; 305 | CALLBACK_MAYBE(s, on_url_complete); 306 | return err; 307 | } 308 | 309 | 310 | int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { 311 | int err; 312 | SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); 313 | return err; 314 | } 315 | 316 | 317 | int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { 318 | int err; 319 | CALLBACK_MAYBE(s, on_status_complete); 320 | return err; 321 | } 322 | 323 | 324 | int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { 325 | int err; 326 | SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); 327 | return err; 328 | } 329 | 330 | 331 | int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { 332 | int err; 333 | CALLBACK_MAYBE(s, on_method_complete); 334 | return err; 335 | } 336 | 337 | 338 | int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { 339 | int err; 340 | SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); 341 | return err; 342 | } 343 | 344 | 345 | int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { 346 | int err; 347 | CALLBACK_MAYBE(s, on_version_complete); 348 | return err; 349 | } 350 | 351 | 352 | int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { 353 | int err; 354 | SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); 355 | return err; 356 | } 357 | 358 | 359 | int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { 360 | int err; 361 | CALLBACK_MAYBE(s, on_header_field_complete); 362 | return err; 363 | } 364 | 365 | 366 | int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { 367 | int err; 368 | SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); 369 | return err; 370 | } 371 | 372 | 373 | int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { 374 | int err; 375 | CALLBACK_MAYBE(s, on_header_value_complete); 376 | return err; 377 | } 378 | 379 | 380 | int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { 381 | int err; 382 | CALLBACK_MAYBE(s, on_headers_complete); 383 | return err; 384 | } 385 | 386 | 387 | int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { 388 | int err; 389 | CALLBACK_MAYBE(s, on_message_complete); 390 | return err; 391 | } 392 | 393 | 394 | int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { 395 | int err; 396 | SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); 397 | return err; 398 | } 399 | 400 | 401 | int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { 402 | int err; 403 | CALLBACK_MAYBE(s, on_chunk_header); 404 | return err; 405 | } 406 | 407 | 408 | int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { 409 | int err; 410 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); 411 | return err; 412 | } 413 | 414 | 415 | int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { 416 | int err; 417 | CALLBACK_MAYBE(s, on_chunk_extension_name_complete); 418 | return err; 419 | } 420 | 421 | 422 | int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { 423 | int err; 424 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); 425 | return err; 426 | } 427 | 428 | 429 | int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { 430 | int err; 431 | CALLBACK_MAYBE(s, on_chunk_extension_value_complete); 432 | return err; 433 | } 434 | 435 | 436 | int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { 437 | int err; 438 | CALLBACK_MAYBE(s, on_chunk_complete); 439 | return err; 440 | } 441 | 442 | 443 | int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { 444 | int err; 445 | CALLBACK_MAYBE(s, on_reset); 446 | return err; 447 | } 448 | 449 | 450 | /* Private */ 451 | 452 | 453 | void llhttp__debug(llhttp_t* s, const char* p, const char* endp, 454 | const char* msg) { 455 | if (p == endp) { 456 | fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, 457 | s->flags, msg); 458 | } else { 459 | fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, 460 | s->type, s->flags, *p, msg); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /mri/ext/llhttp/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 | wasm_on_message_begin, 61 | wasm_on_url, 62 | wasm_on_status, 63 | NULL, 64 | NULL, 65 | wasm_on_header_field, 66 | wasm_on_header_value, 67 | NULL, 68 | NULL, 69 | wasm_on_headers_complete_wrap, 70 | wasm_on_body, 71 | wasm_on_message_complete, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL, 76 | NULL, 77 | NULL, 78 | NULL, 79 | NULL, 80 | NULL, 81 | NULL, 82 | NULL, 83 | }; 84 | 85 | 86 | llhttp_t* llhttp_alloc(llhttp_type_t type) { 87 | llhttp_t* parser = malloc(sizeof(llhttp_t)); 88 | llhttp_init(parser, type, &wasm_settings); 89 | return parser; 90 | } 91 | 92 | void llhttp_free(llhttp_t* parser) { 93 | free(parser); 94 | } 95 | 96 | #endif // defined(__wasm__) 97 | 98 | /* Some getters required to get stuff from the parser */ 99 | 100 | uint8_t llhttp_get_type(llhttp_t* parser) { 101 | return parser->type; 102 | } 103 | 104 | uint8_t llhttp_get_http_major(llhttp_t* parser) { 105 | return parser->http_major; 106 | } 107 | 108 | uint8_t llhttp_get_http_minor(llhttp_t* parser) { 109 | return parser->http_minor; 110 | } 111 | 112 | uint8_t llhttp_get_method(llhttp_t* parser) { 113 | return parser->method; 114 | } 115 | 116 | int llhttp_get_status_code(llhttp_t* parser) { 117 | return parser->status_code; 118 | } 119 | 120 | uint8_t llhttp_get_upgrade(llhttp_t* parser) { 121 | return parser->upgrade; 122 | } 123 | 124 | 125 | void llhttp_reset(llhttp_t* parser) { 126 | llhttp_type_t type = parser->type; 127 | const llhttp_settings_t* settings = parser->settings; 128 | void* data = parser->data; 129 | uint8_t lenient_flags = parser->lenient_flags; 130 | 131 | llhttp__internal_init(parser); 132 | 133 | parser->type = type; 134 | parser->settings = (void*) settings; 135 | parser->data = data; 136 | parser->lenient_flags = lenient_flags; 137 | } 138 | 139 | 140 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { 141 | return llhttp__internal_execute(parser, data, data + len); 142 | } 143 | 144 | 145 | void llhttp_settings_init(llhttp_settings_t* settings) { 146 | memset(settings, 0, sizeof(*settings)); 147 | } 148 | 149 | 150 | llhttp_errno_t llhttp_finish(llhttp_t* parser) { 151 | int err; 152 | 153 | /* We're in an error state. Don't bother doing anything. */ 154 | if (parser->error != 0) { 155 | return 0; 156 | } 157 | 158 | switch (parser->finish) { 159 | case HTTP_FINISH_SAFE_WITH_CB: 160 | CALLBACK_MAYBE(parser, on_message_complete); 161 | if (err != HPE_OK) return err; 162 | 163 | /* FALLTHROUGH */ 164 | case HTTP_FINISH_SAFE: 165 | return HPE_OK; 166 | case HTTP_FINISH_UNSAFE: 167 | parser->reason = "Invalid EOF state"; 168 | return HPE_INVALID_EOF_STATE; 169 | default: 170 | abort(); 171 | } 172 | } 173 | 174 | 175 | void llhttp_pause(llhttp_t* parser) { 176 | if (parser->error != HPE_OK) { 177 | return; 178 | } 179 | 180 | parser->error = HPE_PAUSED; 181 | parser->reason = "Paused"; 182 | } 183 | 184 | 185 | void llhttp_resume(llhttp_t* parser) { 186 | if (parser->error != HPE_PAUSED) { 187 | return; 188 | } 189 | 190 | parser->error = 0; 191 | } 192 | 193 | 194 | void llhttp_resume_after_upgrade(llhttp_t* parser) { 195 | if (parser->error != HPE_PAUSED_UPGRADE) { 196 | return; 197 | } 198 | 199 | parser->error = 0; 200 | } 201 | 202 | 203 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { 204 | return parser->error; 205 | } 206 | 207 | 208 | const char* llhttp_get_error_reason(const llhttp_t* parser) { 209 | return parser->reason; 210 | } 211 | 212 | 213 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { 214 | parser->reason = reason; 215 | } 216 | 217 | 218 | const char* llhttp_get_error_pos(const llhttp_t* parser) { 219 | return parser->error_pos; 220 | } 221 | 222 | 223 | const char* llhttp_errno_name(llhttp_errno_t err) { 224 | #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; 225 | switch (err) { 226 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 227 | default: abort(); 228 | } 229 | #undef HTTP_ERRNO_GEN 230 | } 231 | 232 | 233 | const char* llhttp_method_name(llhttp_method_t method) { 234 | #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; 235 | switch (method) { 236 | HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) 237 | default: abort(); 238 | } 239 | #undef HTTP_METHOD_GEN 240 | } 241 | 242 | const char* llhttp_status_name(llhttp_status_t status) { 243 | #define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; 244 | switch (status) { 245 | HTTP_STATUS_MAP(HTTP_STATUS_GEN) 246 | default: abort(); 247 | } 248 | #undef HTTP_STATUS_GEN 249 | } 250 | 251 | 252 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { 253 | if (enabled) { 254 | parser->lenient_flags |= LENIENT_HEADERS; 255 | } else { 256 | parser->lenient_flags &= ~LENIENT_HEADERS; 257 | } 258 | } 259 | 260 | 261 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { 262 | if (enabled) { 263 | parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; 264 | } else { 265 | parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; 266 | } 267 | } 268 | 269 | 270 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { 271 | if (enabled) { 272 | parser->lenient_flags |= LENIENT_KEEP_ALIVE; 273 | } else { 274 | parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; 275 | } 276 | } 277 | 278 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { 279 | if (enabled) { 280 | parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; 281 | } else { 282 | parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; 283 | } 284 | } 285 | 286 | /* Callbacks */ 287 | 288 | 289 | int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { 290 | int err; 291 | CALLBACK_MAYBE(s, on_message_begin); 292 | return err; 293 | } 294 | 295 | 296 | int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { 297 | int err; 298 | SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); 299 | return err; 300 | } 301 | 302 | 303 | int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { 304 | int err; 305 | CALLBACK_MAYBE(s, on_url_complete); 306 | return err; 307 | } 308 | 309 | 310 | int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { 311 | int err; 312 | SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); 313 | return err; 314 | } 315 | 316 | 317 | int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { 318 | int err; 319 | CALLBACK_MAYBE(s, on_status_complete); 320 | return err; 321 | } 322 | 323 | 324 | int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { 325 | int err; 326 | SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); 327 | return err; 328 | } 329 | 330 | 331 | int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { 332 | int err; 333 | CALLBACK_MAYBE(s, on_method_complete); 334 | return err; 335 | } 336 | 337 | 338 | int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { 339 | int err; 340 | SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); 341 | return err; 342 | } 343 | 344 | 345 | int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { 346 | int err; 347 | CALLBACK_MAYBE(s, on_version_complete); 348 | return err; 349 | } 350 | 351 | 352 | int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { 353 | int err; 354 | SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); 355 | return err; 356 | } 357 | 358 | 359 | int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { 360 | int err; 361 | CALLBACK_MAYBE(s, on_header_field_complete); 362 | return err; 363 | } 364 | 365 | 366 | int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { 367 | int err; 368 | SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); 369 | return err; 370 | } 371 | 372 | 373 | int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { 374 | int err; 375 | CALLBACK_MAYBE(s, on_header_value_complete); 376 | return err; 377 | } 378 | 379 | 380 | int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { 381 | int err; 382 | CALLBACK_MAYBE(s, on_headers_complete); 383 | return err; 384 | } 385 | 386 | 387 | int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { 388 | int err; 389 | CALLBACK_MAYBE(s, on_message_complete); 390 | return err; 391 | } 392 | 393 | 394 | int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { 395 | int err; 396 | SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); 397 | return err; 398 | } 399 | 400 | 401 | int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { 402 | int err; 403 | CALLBACK_MAYBE(s, on_chunk_header); 404 | return err; 405 | } 406 | 407 | 408 | int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { 409 | int err; 410 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); 411 | return err; 412 | } 413 | 414 | 415 | int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { 416 | int err; 417 | CALLBACK_MAYBE(s, on_chunk_extension_name_complete); 418 | return err; 419 | } 420 | 421 | 422 | int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { 423 | int err; 424 | SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); 425 | return err; 426 | } 427 | 428 | 429 | int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { 430 | int err; 431 | CALLBACK_MAYBE(s, on_chunk_extension_value_complete); 432 | return err; 433 | } 434 | 435 | 436 | int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { 437 | int err; 438 | CALLBACK_MAYBE(s, on_chunk_complete); 439 | return err; 440 | } 441 | 442 | 443 | int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { 444 | int err; 445 | CALLBACK_MAYBE(s, on_reset); 446 | return err; 447 | } 448 | 449 | 450 | /* Private */ 451 | 452 | 453 | void llhttp__debug(llhttp_t* s, const char* p, const char* endp, 454 | const char* msg) { 455 | if (p == endp) { 456 | fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, 457 | s->flags, msg); 458 | } else { 459 | fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, 460 | s->type, s->flags, *p, msg); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /ffi/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. “Contributor” 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. “Contributor Version” 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor’s Contribution. 14 | 15 | 1.3. “Contribution” 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. “Covered Software” 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. “Incompatible With Secondary Licenses” 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of version 33 | 1.1 or earlier of the License, but not also under the terms of a 34 | Secondary License. 35 | 36 | 1.6. “Executable Form” 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. “Larger Work” 41 | 42 | means a work that combines Covered Software with other material, in a separate 43 | file or files, that is not Covered Software. 44 | 45 | 1.8. “License” 46 | 47 | means this document. 48 | 49 | 1.9. “Licensable” 50 | 51 | means having the right to grant, to the maximum extent possible, whether at the 52 | time of the initial grant or subsequently, any and all of the rights conveyed by 53 | this License. 54 | 55 | 1.10. “Modifications” 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, deletion 60 | from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. “Patent Claims” of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, process, 67 | and apparatus claims, in any patent Licensable by such Contributor that 68 | would be infringed, but for the grant of the License, by the making, 69 | using, selling, offering for sale, having made, import, or transfer of 70 | either its Contributions or its Contributor Version. 71 | 72 | 1.12. “Secondary License” 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. “Source Code Form” 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. “You” (or “Your”) 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, “You” includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, “control” means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or as 104 | part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its Contributions 108 | or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution become 113 | effective for each Contribution on the date the Contributor first distributes 114 | such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under this 119 | License. No additional rights or licenses will be implied from the distribution 120 | or licensing of Covered Software under this License. Notwithstanding Section 121 | 2.1(b) above, no patent license is granted by a Contributor: 122 | 123 | a. for any code that a Contributor has removed from Covered Software; or 124 | 125 | b. for infringements caused by: (i) Your and any other third party’s 126 | modifications of Covered Software, or (ii) the combination of its 127 | Contributions with other software (except as part of its Contributor 128 | Version); or 129 | 130 | c. under Patent Claims infringed by Covered Software in the absence of its 131 | Contributions. 132 | 133 | This License does not grant any rights in the trademarks, service marks, or 134 | logos of any Contributor (except as may be necessary to comply with the 135 | notice requirements in Section 3.4). 136 | 137 | 2.4. Subsequent Licenses 138 | 139 | No Contributor makes additional grants as a result of Your choice to 140 | distribute the Covered Software under a subsequent version of this License 141 | (see Section 10.2) or under the terms of a Secondary License (if permitted 142 | under the terms of Section 3.3). 143 | 144 | 2.5. Representation 145 | 146 | Each Contributor represents that the Contributor believes its Contributions 147 | are its original creation(s) or it has sufficient rights to grant the 148 | rights to its Contributions conveyed by this License. 149 | 150 | 2.6. Fair Use 151 | 152 | This License is not intended to limit any rights You have under applicable 153 | copyright doctrines of fair use, fair dealing, or other equivalents. 154 | 155 | 2.7. Conditions 156 | 157 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 158 | Section 2.1. 159 | 160 | 161 | 3. Responsibilities 162 | 163 | 3.1. Distribution of Source Form 164 | 165 | All distribution of Covered Software in Source Code Form, including any 166 | Modifications that You create or to which You contribute, must be under the 167 | terms of this License. You must inform recipients that the Source Code Form 168 | of the Covered Software is governed by the terms of this License, and how 169 | they can obtain a copy of this License. You may not attempt to alter or 170 | restrict the recipients’ rights in the Source Code Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | a. such Covered Software must also be made available in Source Code Form, 177 | as described in Section 3.1, and You must inform recipients of the 178 | Executable Form how they can obtain a copy of such Source Code Form by 179 | reasonable means in a timely manner, at a charge no more than the cost 180 | of distribution to the recipient; and 181 | 182 | b. You may distribute such Executable Form under the terms of this License, 183 | or sublicense it under different terms, provided that the license for 184 | the Executable Form does not attempt to limit or alter the recipients’ 185 | rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for the 191 | Covered Software. If the Larger Work is a combination of Covered Software 192 | with a work governed by one or more Secondary Licenses, and the Covered 193 | Software is not Incompatible With Secondary Licenses, this License permits 194 | You to additionally distribute such Covered Software under the terms of 195 | such Secondary License(s), so that the recipient of the Larger Work may, at 196 | their option, further distribute the Covered Software under the terms of 197 | either this License or such Secondary License(s). 198 | 199 | 3.4. Notices 200 | 201 | You may not remove or alter the substance of any license notices (including 202 | copyright notices, patent notices, disclaimers of warranty, or limitations 203 | of liability) contained within the Source Code Form of the Covered 204 | Software, except that You may alter any license notices to the extent 205 | required to remedy known factual inaccuracies. 206 | 207 | 3.5. Application of Additional Terms 208 | 209 | You may choose to offer, and to charge a fee for, warranty, support, 210 | indemnity or liability obligations to one or more recipients of Covered 211 | Software. However, You may do so only on Your own behalf, and not on behalf 212 | of any Contributor. You must make it absolutely clear that any such 213 | warranty, support, indemnity, or liability obligation is offered by You 214 | alone, and You hereby agree to indemnify every Contributor for any 215 | liability incurred by such Contributor as a result of warranty, support, 216 | indemnity or liability terms You offer. You may include additional 217 | disclaimers of warranty and limitations of liability specific to any 218 | jurisdiction. 219 | 220 | 4. Inability to Comply Due to Statute or Regulation 221 | 222 | If it is impossible for You to comply with any of the terms of this License 223 | with respect to some or all of the Covered Software due to statute, judicial 224 | order, or regulation then You must: (a) comply with the terms of this License 225 | to the maximum extent possible; and (b) describe the limitations and the code 226 | they affect. Such description must be placed in a text file included with all 227 | distributions of the Covered Software under this License. Except to the 228 | extent prohibited by statute or regulation, such description must be 229 | sufficiently detailed for a recipient of ordinary skill to be able to 230 | understand it. 231 | 232 | 5. Termination 233 | 234 | 5.1. The rights granted under this License will terminate automatically if You 235 | fail to comply with any of its terms. However, if You become compliant, 236 | then the rights granted under this License from a particular Contributor 237 | are reinstated (a) provisionally, unless and until such Contributor 238 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 239 | if such Contributor fails to notify You of the non-compliance by some 240 | reasonable means prior to 60 days after You have come back into compliance. 241 | Moreover, Your grants from a particular Contributor are reinstated on an 242 | ongoing basis if such Contributor notifies You of the non-compliance by 243 | some reasonable means, this is the first time You have received notice of 244 | non-compliance with this License from such Contributor, and You become 245 | compliant prior to 30 days after Your receipt of the notice. 246 | 247 | 5.2. If You initiate litigation against any entity by asserting a patent 248 | infringement claim (excluding declaratory judgment actions, counter-claims, 249 | and cross-claims) alleging that a Contributor Version directly or 250 | indirectly infringes any patent, then the rights granted to You by any and 251 | all Contributors for the Covered Software under Section 2.1 of this License 252 | shall terminate. 253 | 254 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 255 | license agreements (excluding distributors and resellers) which have been 256 | validly granted by You or Your distributors under this License prior to 257 | termination shall survive termination. 258 | 259 | 6. Disclaimer of Warranty 260 | 261 | Covered Software is provided under this License on an “as is” basis, without 262 | warranty of any kind, either expressed, implied, or statutory, including, 263 | without limitation, warranties that the Covered Software is free of defects, 264 | merchantable, fit for a particular purpose or non-infringing. The entire 265 | risk as to the quality and performance of the Covered Software is with You. 266 | Should any Covered Software prove defective in any respect, You (not any 267 | Contributor) assume the cost of any necessary servicing, repair, or 268 | correction. This disclaimer of warranty constitutes an essential part of this 269 | License. No use of any Covered Software is authorized under this License 270 | except under this disclaimer. 271 | 272 | 7. Limitation of Liability 273 | 274 | Under no circumstances and under no legal theory, whether tort (including 275 | negligence), contract, or otherwise, shall any Contributor, or anyone who 276 | distributes Covered Software as permitted above, be liable to You for any 277 | direct, indirect, special, incidental, or consequential damages of any 278 | character including, without limitation, damages for lost profits, loss of 279 | goodwill, work stoppage, computer failure or malfunction, or any and all 280 | other commercial damages or losses, even if such party shall have been 281 | informed of the possibility of such damages. This limitation of liability 282 | shall not apply to liability for death or personal injury resulting from such 283 | party’s negligence to the extent applicable law prohibits such limitation. 284 | Some jurisdictions do not allow the exclusion or limitation of incidental or 285 | consequential damages, so this exclusion and limitation may not apply to You. 286 | 287 | 8. Litigation 288 | 289 | Any litigation relating to this License may be brought only in the courts of 290 | a jurisdiction where the defendant maintains its principal place of business 291 | and such litigation shall be governed by laws of that jurisdiction, without 292 | reference to its conflict-of-law provisions. Nothing in this Section shall 293 | prevent a party’s ability to bring cross-claims or counter-claims. 294 | 295 | 9. Miscellaneous 296 | 297 | This License represents the complete agreement concerning the subject matter 298 | hereof. If any provision of this License is held to be unenforceable, such 299 | provision shall be reformed only to the extent necessary to make it 300 | enforceable. Any law or regulation which provides that the language of a 301 | contract shall be construed against the drafter shall not be used to construe 302 | this License against a Contributor. 303 | 304 | 305 | 10. Versions of the License 306 | 307 | 10.1. New Versions 308 | 309 | Mozilla Foundation is the license steward. Except as provided in Section 310 | 10.3, no one other than the license steward has the right to modify or 311 | publish new versions of this License. Each version will be given a 312 | distinguishing version number. 313 | 314 | 10.2. Effect of New Versions 315 | 316 | You may distribute the Covered Software under the terms of the version of 317 | the License under which You originally received the Covered Software, or 318 | under the terms of any subsequent version published by the license 319 | steward. 320 | 321 | 10.3. Modified Versions 322 | 323 | If you create software not governed by this License, and you want to 324 | create a new license for such software, you may create and use a modified 325 | version of this License if you rename the license and remove any 326 | references to the name of the license steward (except to note that such 327 | modified license differs from this License). 328 | 329 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 330 | If You choose to distribute Source Code Form that is Incompatible With 331 | Secondary Licenses under the terms of this version of the License, the 332 | notice described in Exhibit B of this License must be attached. 333 | 334 | Exhibit A - Source Code Form License Notice 335 | 336 | This Source Code Form is subject to the 337 | terms of the Mozilla Public License, v. 338 | 2.0. If a copy of the MPL was not 339 | distributed with this file, You can 340 | obtain one at 341 | http://mozilla.org/MPL/2.0/. 342 | 343 | If it is not possible or desirable to put the notice in a particular file, then 344 | You may include the notice in a location (such as a LICENSE file in a relevant 345 | directory) where a recipient would be likely to look for such a notice. 346 | 347 | You may add additional accurate notices of copyright ownership. 348 | 349 | Exhibit B - “Incompatible With Secondary Licenses” Notice 350 | 351 | This Source Code Form is “Incompatible 352 | With Secondary Licenses”, as defined by 353 | the Mozilla Public License, v. 2.0. 354 | -------------------------------------------------------------------------------- /mri/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. “Contributor” 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. “Contributor Version” 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor’s Contribution. 14 | 15 | 1.3. “Contribution” 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. “Covered Software” 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. “Incompatible With Secondary Licenses” 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of version 33 | 1.1 or earlier of the License, but not also under the terms of a 34 | Secondary License. 35 | 36 | 1.6. “Executable Form” 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. “Larger Work” 41 | 42 | means a work that combines Covered Software with other material, in a separate 43 | file or files, that is not Covered Software. 44 | 45 | 1.8. “License” 46 | 47 | means this document. 48 | 49 | 1.9. “Licensable” 50 | 51 | means having the right to grant, to the maximum extent possible, whether at the 52 | time of the initial grant or subsequently, any and all of the rights conveyed by 53 | this License. 54 | 55 | 1.10. “Modifications” 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, deletion 60 | from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. “Patent Claims” of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, process, 67 | and apparatus claims, in any patent Licensable by such Contributor that 68 | would be infringed, but for the grant of the License, by the making, 69 | using, selling, offering for sale, having made, import, or transfer of 70 | either its Contributions or its Contributor Version. 71 | 72 | 1.12. “Secondary License” 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. “Source Code Form” 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. “You” (or “Your”) 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, “You” includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, “control” means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or as 104 | part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its Contributions 108 | or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution become 113 | effective for each Contribution on the date the Contributor first distributes 114 | such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under this 119 | License. No additional rights or licenses will be implied from the distribution 120 | or licensing of Covered Software under this License. Notwithstanding Section 121 | 2.1(b) above, no patent license is granted by a Contributor: 122 | 123 | a. for any code that a Contributor has removed from Covered Software; or 124 | 125 | b. for infringements caused by: (i) Your and any other third party’s 126 | modifications of Covered Software, or (ii) the combination of its 127 | Contributions with other software (except as part of its Contributor 128 | Version); or 129 | 130 | c. under Patent Claims infringed by Covered Software in the absence of its 131 | Contributions. 132 | 133 | This License does not grant any rights in the trademarks, service marks, or 134 | logos of any Contributor (except as may be necessary to comply with the 135 | notice requirements in Section 3.4). 136 | 137 | 2.4. Subsequent Licenses 138 | 139 | No Contributor makes additional grants as a result of Your choice to 140 | distribute the Covered Software under a subsequent version of this License 141 | (see Section 10.2) or under the terms of a Secondary License (if permitted 142 | under the terms of Section 3.3). 143 | 144 | 2.5. Representation 145 | 146 | Each Contributor represents that the Contributor believes its Contributions 147 | are its original creation(s) or it has sufficient rights to grant the 148 | rights to its Contributions conveyed by this License. 149 | 150 | 2.6. Fair Use 151 | 152 | This License is not intended to limit any rights You have under applicable 153 | copyright doctrines of fair use, fair dealing, or other equivalents. 154 | 155 | 2.7. Conditions 156 | 157 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 158 | Section 2.1. 159 | 160 | 161 | 3. Responsibilities 162 | 163 | 3.1. Distribution of Source Form 164 | 165 | All distribution of Covered Software in Source Code Form, including any 166 | Modifications that You create or to which You contribute, must be under the 167 | terms of this License. You must inform recipients that the Source Code Form 168 | of the Covered Software is governed by the terms of this License, and how 169 | they can obtain a copy of this License. You may not attempt to alter or 170 | restrict the recipients’ rights in the Source Code Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | a. such Covered Software must also be made available in Source Code Form, 177 | as described in Section 3.1, and You must inform recipients of the 178 | Executable Form how they can obtain a copy of such Source Code Form by 179 | reasonable means in a timely manner, at a charge no more than the cost 180 | of distribution to the recipient; and 181 | 182 | b. You may distribute such Executable Form under the terms of this License, 183 | or sublicense it under different terms, provided that the license for 184 | the Executable Form does not attempt to limit or alter the recipients’ 185 | rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for the 191 | Covered Software. If the Larger Work is a combination of Covered Software 192 | with a work governed by one or more Secondary Licenses, and the Covered 193 | Software is not Incompatible With Secondary Licenses, this License permits 194 | You to additionally distribute such Covered Software under the terms of 195 | such Secondary License(s), so that the recipient of the Larger Work may, at 196 | their option, further distribute the Covered Software under the terms of 197 | either this License or such Secondary License(s). 198 | 199 | 3.4. Notices 200 | 201 | You may not remove or alter the substance of any license notices (including 202 | copyright notices, patent notices, disclaimers of warranty, or limitations 203 | of liability) contained within the Source Code Form of the Covered 204 | Software, except that You may alter any license notices to the extent 205 | required to remedy known factual inaccuracies. 206 | 207 | 3.5. Application of Additional Terms 208 | 209 | You may choose to offer, and to charge a fee for, warranty, support, 210 | indemnity or liability obligations to one or more recipients of Covered 211 | Software. However, You may do so only on Your own behalf, and not on behalf 212 | of any Contributor. You must make it absolutely clear that any such 213 | warranty, support, indemnity, or liability obligation is offered by You 214 | alone, and You hereby agree to indemnify every Contributor for any 215 | liability incurred by such Contributor as a result of warranty, support, 216 | indemnity or liability terms You offer. You may include additional 217 | disclaimers of warranty and limitations of liability specific to any 218 | jurisdiction. 219 | 220 | 4. Inability to Comply Due to Statute or Regulation 221 | 222 | If it is impossible for You to comply with any of the terms of this License 223 | with respect to some or all of the Covered Software due to statute, judicial 224 | order, or regulation then You must: (a) comply with the terms of this License 225 | to the maximum extent possible; and (b) describe the limitations and the code 226 | they affect. Such description must be placed in a text file included with all 227 | distributions of the Covered Software under this License. Except to the 228 | extent prohibited by statute or regulation, such description must be 229 | sufficiently detailed for a recipient of ordinary skill to be able to 230 | understand it. 231 | 232 | 5. Termination 233 | 234 | 5.1. The rights granted under this License will terminate automatically if You 235 | fail to comply with any of its terms. However, if You become compliant, 236 | then the rights granted under this License from a particular Contributor 237 | are reinstated (a) provisionally, unless and until such Contributor 238 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 239 | if such Contributor fails to notify You of the non-compliance by some 240 | reasonable means prior to 60 days after You have come back into compliance. 241 | Moreover, Your grants from a particular Contributor are reinstated on an 242 | ongoing basis if such Contributor notifies You of the non-compliance by 243 | some reasonable means, this is the first time You have received notice of 244 | non-compliance with this License from such Contributor, and You become 245 | compliant prior to 30 days after Your receipt of the notice. 246 | 247 | 5.2. If You initiate litigation against any entity by asserting a patent 248 | infringement claim (excluding declaratory judgment actions, counter-claims, 249 | and cross-claims) alleging that a Contributor Version directly or 250 | indirectly infringes any patent, then the rights granted to You by any and 251 | all Contributors for the Covered Software under Section 2.1 of this License 252 | shall terminate. 253 | 254 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 255 | license agreements (excluding distributors and resellers) which have been 256 | validly granted by You or Your distributors under this License prior to 257 | termination shall survive termination. 258 | 259 | 6. Disclaimer of Warranty 260 | 261 | Covered Software is provided under this License on an “as is” basis, without 262 | warranty of any kind, either expressed, implied, or statutory, including, 263 | without limitation, warranties that the Covered Software is free of defects, 264 | merchantable, fit for a particular purpose or non-infringing. The entire 265 | risk as to the quality and performance of the Covered Software is with You. 266 | Should any Covered Software prove defective in any respect, You (not any 267 | Contributor) assume the cost of any necessary servicing, repair, or 268 | correction. This disclaimer of warranty constitutes an essential part of this 269 | License. No use of any Covered Software is authorized under this License 270 | except under this disclaimer. 271 | 272 | 7. Limitation of Liability 273 | 274 | Under no circumstances and under no legal theory, whether tort (including 275 | negligence), contract, or otherwise, shall any Contributor, or anyone who 276 | distributes Covered Software as permitted above, be liable to You for any 277 | direct, indirect, special, incidental, or consequential damages of any 278 | character including, without limitation, damages for lost profits, loss of 279 | goodwill, work stoppage, computer failure or malfunction, or any and all 280 | other commercial damages or losses, even if such party shall have been 281 | informed of the possibility of such damages. This limitation of liability 282 | shall not apply to liability for death or personal injury resulting from such 283 | party’s negligence to the extent applicable law prohibits such limitation. 284 | Some jurisdictions do not allow the exclusion or limitation of incidental or 285 | consequential damages, so this exclusion and limitation may not apply to You. 286 | 287 | 8. Litigation 288 | 289 | Any litigation relating to this License may be brought only in the courts of 290 | a jurisdiction where the defendant maintains its principal place of business 291 | and such litigation shall be governed by laws of that jurisdiction, without 292 | reference to its conflict-of-law provisions. Nothing in this Section shall 293 | prevent a party’s ability to bring cross-claims or counter-claims. 294 | 295 | 9. Miscellaneous 296 | 297 | This License represents the complete agreement concerning the subject matter 298 | hereof. If any provision of this License is held to be unenforceable, such 299 | provision shall be reformed only to the extent necessary to make it 300 | enforceable. Any law or regulation which provides that the language of a 301 | contract shall be construed against the drafter shall not be used to construe 302 | this License against a Contributor. 303 | 304 | 305 | 10. Versions of the License 306 | 307 | 10.1. New Versions 308 | 309 | Mozilla Foundation is the license steward. Except as provided in Section 310 | 10.3, no one other than the license steward has the right to modify or 311 | publish new versions of this License. Each version will be given a 312 | distinguishing version number. 313 | 314 | 10.2. Effect of New Versions 315 | 316 | You may distribute the Covered Software under the terms of the version of 317 | the License under which You originally received the Covered Software, or 318 | under the terms of any subsequent version published by the license 319 | steward. 320 | 321 | 10.3. Modified Versions 322 | 323 | If you create software not governed by this License, and you want to 324 | create a new license for such software, you may create and use a modified 325 | version of this License if you rename the license and remove any 326 | references to the name of the license steward (except to note that such 327 | modified license differs from this License). 328 | 329 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 330 | If You choose to distribute Source Code Form that is Incompatible With 331 | Secondary Licenses under the terms of this version of the License, the 332 | notice described in Exhibit B of this License must be attached. 333 | 334 | Exhibit A - Source Code Form License Notice 335 | 336 | This Source Code Form is subject to the 337 | terms of the Mozilla Public License, v. 338 | 2.0. If a copy of the MPL was not 339 | distributed with this file, You can 340 | obtain one at 341 | http://mozilla.org/MPL/2.0/. 342 | 343 | If it is not possible or desirable to put the notice in a particular file, then 344 | You may include the notice in a location (such as a LICENSE file in a relevant 345 | directory) where a recipient would be likely to look for such a notice. 346 | 347 | You may add additional accurate notices of copyright ownership. 348 | 349 | Exhibit B - “Incompatible With Secondary Licenses” Notice 350 | 351 | This Source Code Form is “Incompatible 352 | With Secondary Licenses”, as defined by 353 | the Mozilla Public License, v. 2.0. 354 | -------------------------------------------------------------------------------- /ffi/ext/llhttp/llhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_LLHTTP_H_ 2 | #define INCLUDE_LLHTTP_H_ 3 | 4 | #define LLHTTP_VERSION_MAJOR 8 5 | #define LLHTTP_VERSION_MINOR 1 6 | #define LLHTTP_VERSION_PATCH 0 7 | 8 | #ifndef LLHTTP_STRICT_MODE 9 | # define LLHTTP_STRICT_MODE 0 10 | #endif 11 | 12 | #ifndef INCLUDE_LLHTTP_ITSELF_H_ 13 | #define INCLUDE_LLHTTP_ITSELF_H_ 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | #include 19 | 20 | typedef struct llhttp__internal_s llhttp__internal_t; 21 | struct llhttp__internal_s { 22 | int32_t _index; 23 | void* _span_pos0; 24 | void* _span_cb0; 25 | int32_t error; 26 | const char* reason; 27 | const char* error_pos; 28 | void* data; 29 | void* _current; 30 | uint64_t content_length; 31 | uint8_t type; 32 | uint8_t method; 33 | uint8_t http_major; 34 | uint8_t http_minor; 35 | uint8_t header_state; 36 | uint8_t lenient_flags; 37 | uint8_t upgrade; 38 | uint8_t finish; 39 | uint16_t flags; 40 | uint16_t status_code; 41 | uint8_t initial_message_completed; 42 | void* settings; 43 | }; 44 | 45 | int llhttp__internal_init(llhttp__internal_t* s); 46 | int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); 47 | 48 | #ifdef __cplusplus 49 | } /* extern "C" */ 50 | #endif 51 | #endif /* INCLUDE_LLHTTP_ITSELF_H_ */ 52 | 53 | #ifndef LLLLHTTP_C_HEADERS_ 54 | #define LLLLHTTP_C_HEADERS_ 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | enum llhttp_errno { 60 | HPE_OK = 0, 61 | HPE_INTERNAL = 1, 62 | HPE_STRICT = 2, 63 | HPE_CR_EXPECTED = 25, 64 | HPE_LF_EXPECTED = 3, 65 | HPE_UNEXPECTED_CONTENT_LENGTH = 4, 66 | HPE_UNEXPECTED_SPACE = 30, 67 | HPE_CLOSED_CONNECTION = 5, 68 | HPE_INVALID_METHOD = 6, 69 | HPE_INVALID_URL = 7, 70 | HPE_INVALID_CONSTANT = 8, 71 | HPE_INVALID_VERSION = 9, 72 | HPE_INVALID_HEADER_TOKEN = 10, 73 | HPE_INVALID_CONTENT_LENGTH = 11, 74 | HPE_INVALID_CHUNK_SIZE = 12, 75 | HPE_INVALID_STATUS = 13, 76 | HPE_INVALID_EOF_STATE = 14, 77 | HPE_INVALID_TRANSFER_ENCODING = 15, 78 | HPE_CB_MESSAGE_BEGIN = 16, 79 | HPE_CB_HEADERS_COMPLETE = 17, 80 | HPE_CB_MESSAGE_COMPLETE = 18, 81 | HPE_CB_CHUNK_HEADER = 19, 82 | HPE_CB_CHUNK_COMPLETE = 20, 83 | HPE_PAUSED = 21, 84 | HPE_PAUSED_UPGRADE = 22, 85 | HPE_PAUSED_H2_UPGRADE = 23, 86 | HPE_USER = 24, 87 | HPE_CB_URL_COMPLETE = 26, 88 | HPE_CB_STATUS_COMPLETE = 27, 89 | HPE_CB_METHOD_COMPLETE = 32, 90 | HPE_CB_VERSION_COMPLETE = 33, 91 | HPE_CB_HEADER_FIELD_COMPLETE = 28, 92 | HPE_CB_HEADER_VALUE_COMPLETE = 29, 93 | HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, 94 | HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, 95 | HPE_CB_RESET = 31 96 | }; 97 | typedef enum llhttp_errno llhttp_errno_t; 98 | 99 | enum llhttp_flags { 100 | F_CONNECTION_KEEP_ALIVE = 0x1, 101 | F_CONNECTION_CLOSE = 0x2, 102 | F_CONNECTION_UPGRADE = 0x4, 103 | F_CHUNKED = 0x8, 104 | F_UPGRADE = 0x10, 105 | F_CONTENT_LENGTH = 0x20, 106 | F_SKIPBODY = 0x40, 107 | F_TRAILING = 0x80, 108 | F_TRANSFER_ENCODING = 0x200 109 | }; 110 | typedef enum llhttp_flags llhttp_flags_t; 111 | 112 | enum llhttp_lenient_flags { 113 | LENIENT_HEADERS = 0x1, 114 | LENIENT_CHUNKED_LENGTH = 0x2, 115 | LENIENT_KEEP_ALIVE = 0x4, 116 | LENIENT_TRANSFER_ENCODING = 0x8, 117 | LENIENT_VERSION = 0x10 118 | }; 119 | typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; 120 | 121 | enum llhttp_type { 122 | HTTP_BOTH = 0, 123 | HTTP_REQUEST = 1, 124 | HTTP_RESPONSE = 2 125 | }; 126 | typedef enum llhttp_type llhttp_type_t; 127 | 128 | enum llhttp_finish { 129 | HTTP_FINISH_SAFE = 0, 130 | HTTP_FINISH_SAFE_WITH_CB = 1, 131 | HTTP_FINISH_UNSAFE = 2 132 | }; 133 | typedef enum llhttp_finish llhttp_finish_t; 134 | 135 | enum llhttp_method { 136 | HTTP_DELETE = 0, 137 | HTTP_GET = 1, 138 | HTTP_HEAD = 2, 139 | HTTP_POST = 3, 140 | HTTP_PUT = 4, 141 | HTTP_CONNECT = 5, 142 | HTTP_OPTIONS = 6, 143 | HTTP_TRACE = 7, 144 | HTTP_COPY = 8, 145 | HTTP_LOCK = 9, 146 | HTTP_MKCOL = 10, 147 | HTTP_MOVE = 11, 148 | HTTP_PROPFIND = 12, 149 | HTTP_PROPPATCH = 13, 150 | HTTP_SEARCH = 14, 151 | HTTP_UNLOCK = 15, 152 | HTTP_BIND = 16, 153 | HTTP_REBIND = 17, 154 | HTTP_UNBIND = 18, 155 | HTTP_ACL = 19, 156 | HTTP_REPORT = 20, 157 | HTTP_MKACTIVITY = 21, 158 | HTTP_CHECKOUT = 22, 159 | HTTP_MERGE = 23, 160 | HTTP_MSEARCH = 24, 161 | HTTP_NOTIFY = 25, 162 | HTTP_SUBSCRIBE = 26, 163 | HTTP_UNSUBSCRIBE = 27, 164 | HTTP_PATCH = 28, 165 | HTTP_PURGE = 29, 166 | HTTP_MKCALENDAR = 30, 167 | HTTP_LINK = 31, 168 | HTTP_UNLINK = 32, 169 | HTTP_SOURCE = 33, 170 | HTTP_PRI = 34, 171 | HTTP_DESCRIBE = 35, 172 | HTTP_ANNOUNCE = 36, 173 | HTTP_SETUP = 37, 174 | HTTP_PLAY = 38, 175 | HTTP_PAUSE = 39, 176 | HTTP_TEARDOWN = 40, 177 | HTTP_GET_PARAMETER = 41, 178 | HTTP_SET_PARAMETER = 42, 179 | HTTP_REDIRECT = 43, 180 | HTTP_RECORD = 44, 181 | HTTP_FLUSH = 45 182 | }; 183 | typedef enum llhttp_method llhttp_method_t; 184 | 185 | enum llhttp_status { 186 | HTTP_STATUS_CONTINUE = 100, 187 | HTTP_STATUS_SWITCHING_PROTOCOLS = 101, 188 | HTTP_STATUS_PROCESSING = 102, 189 | HTTP_STATUS_EARLY_HINTS = 103, 190 | HTTP_STATUS_RESPONSE_IS_STALE = 110, 191 | HTTP_STATUS_REVALIDATION_FAILED = 111, 192 | HTTP_STATUS_DISCONNECTED_OPERATION = 112, 193 | HTTP_STATUS_HEURISTIC_EXPIRATION = 113, 194 | HTTP_STATUS_MISCELLANEOUS_WARNING = 199, 195 | HTTP_STATUS_OK = 200, 196 | HTTP_STATUS_CREATED = 201, 197 | HTTP_STATUS_ACCEPTED = 202, 198 | HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, 199 | HTTP_STATUS_NO_CONTENT = 204, 200 | HTTP_STATUS_RESET_CONTENT = 205, 201 | HTTP_STATUS_PARTIAL_CONTENT = 206, 202 | HTTP_STATUS_MULTI_STATUS = 207, 203 | HTTP_STATUS_ALREADY_REPORTED = 208, 204 | HTTP_STATUS_TRANSFORMATION_APPLIED = 214, 205 | HTTP_STATUS_IM_USED = 226, 206 | HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, 207 | HTTP_STATUS_MULTIPLE_CHOICES = 300, 208 | HTTP_STATUS_MOVED_PERMANENTLY = 301, 209 | HTTP_STATUS_FOUND = 302, 210 | HTTP_STATUS_SEE_OTHER = 303, 211 | HTTP_STATUS_NOT_MODIFIED = 304, 212 | HTTP_STATUS_USE_PROXY = 305, 213 | HTTP_STATUS_SWITCH_PROXY = 306, 214 | HTTP_STATUS_TEMPORARY_REDIRECT = 307, 215 | HTTP_STATUS_PERMANENT_REDIRECT = 308, 216 | HTTP_STATUS_BAD_REQUEST = 400, 217 | HTTP_STATUS_UNAUTHORIZED = 401, 218 | HTTP_STATUS_PAYMENT_REQUIRED = 402, 219 | HTTP_STATUS_FORBIDDEN = 403, 220 | HTTP_STATUS_NOT_FOUND = 404, 221 | HTTP_STATUS_METHOD_NOT_ALLOWED = 405, 222 | HTTP_STATUS_NOT_ACCEPTABLE = 406, 223 | HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, 224 | HTTP_STATUS_REQUEST_TIMEOUT = 408, 225 | HTTP_STATUS_CONFLICT = 409, 226 | HTTP_STATUS_GONE = 410, 227 | HTTP_STATUS_LENGTH_REQUIRED = 411, 228 | HTTP_STATUS_PRECONDITION_FAILED = 412, 229 | HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, 230 | HTTP_STATUS_URI_TOO_LONG = 414, 231 | HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, 232 | HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, 233 | HTTP_STATUS_EXPECTATION_FAILED = 417, 234 | HTTP_STATUS_IM_A_TEAPOT = 418, 235 | HTTP_STATUS_PAGE_EXPIRED = 419, 236 | HTTP_STATUS_ENHANCE_YOUR_CALM = 420, 237 | HTTP_STATUS_MISDIRECTED_REQUEST = 421, 238 | HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, 239 | HTTP_STATUS_LOCKED = 423, 240 | HTTP_STATUS_FAILED_DEPENDENCY = 424, 241 | HTTP_STATUS_TOO_EARLY = 425, 242 | HTTP_STATUS_UPGRADE_REQUIRED = 426, 243 | HTTP_STATUS_PRECONDITION_REQUIRED = 428, 244 | HTTP_STATUS_TOO_MANY_REQUESTS = 429, 245 | HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, 246 | HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 247 | HTTP_STATUS_LOGIN_TIMEOUT = 440, 248 | HTTP_STATUS_NO_RESPONSE = 444, 249 | HTTP_STATUS_RETRY_WITH = 449, 250 | HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, 251 | HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, 252 | HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, 253 | HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, 254 | HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, 255 | HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, 256 | HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, 257 | HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, 258 | HTTP_STATUS_INVALID_TOKEN = 498, 259 | HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, 260 | HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, 261 | HTTP_STATUS_NOT_IMPLEMENTED = 501, 262 | HTTP_STATUS_BAD_GATEWAY = 502, 263 | HTTP_STATUS_SERVICE_UNAVAILABLE = 503, 264 | HTTP_STATUS_GATEWAY_TIMEOUT = 504, 265 | HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, 266 | HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, 267 | HTTP_STATUS_INSUFFICIENT_STORAGE = 507, 268 | HTTP_STATUS_LOOP_DETECTED = 508, 269 | HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, 270 | HTTP_STATUS_NOT_EXTENDED = 510, 271 | HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, 272 | HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, 273 | HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, 274 | HTTP_STATUS_CONNECTION_TIMEOUT = 522, 275 | HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, 276 | HTTP_STATUS_TIMEOUT_OCCURED = 524, 277 | HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, 278 | HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, 279 | HTTP_STATUS_RAILGUN_ERROR = 527, 280 | HTTP_STATUS_SITE_IS_OVERLOADED = 529, 281 | HTTP_STATUS_SITE_IS_FROZEN = 530, 282 | HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, 283 | HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, 284 | HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 285 | }; 286 | typedef enum llhttp_status llhttp_status_t; 287 | 288 | #define HTTP_ERRNO_MAP(XX) \ 289 | XX(0, OK, OK) \ 290 | XX(1, INTERNAL, INTERNAL) \ 291 | XX(2, STRICT, STRICT) \ 292 | XX(25, CR_EXPECTED, CR_EXPECTED) \ 293 | XX(3, LF_EXPECTED, LF_EXPECTED) \ 294 | XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ 295 | XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ 296 | XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ 297 | XX(6, INVALID_METHOD, INVALID_METHOD) \ 298 | XX(7, INVALID_URL, INVALID_URL) \ 299 | XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ 300 | XX(9, INVALID_VERSION, INVALID_VERSION) \ 301 | XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ 302 | XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ 303 | XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ 304 | XX(13, INVALID_STATUS, INVALID_STATUS) \ 305 | XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ 306 | XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ 307 | XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ 308 | XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ 309 | XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ 310 | XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ 311 | XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ 312 | XX(21, PAUSED, PAUSED) \ 313 | XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ 314 | XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ 315 | XX(24, USER, USER) \ 316 | XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ 317 | XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ 318 | XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ 319 | XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ 320 | XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ 321 | XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ 322 | XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ 323 | XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ 324 | XX(31, CB_RESET, CB_RESET) \ 325 | 326 | 327 | #define HTTP_METHOD_MAP(XX) \ 328 | XX(0, DELETE, DELETE) \ 329 | XX(1, GET, GET) \ 330 | XX(2, HEAD, HEAD) \ 331 | XX(3, POST, POST) \ 332 | XX(4, PUT, PUT) \ 333 | XX(5, CONNECT, CONNECT) \ 334 | XX(6, OPTIONS, OPTIONS) \ 335 | XX(7, TRACE, TRACE) \ 336 | XX(8, COPY, COPY) \ 337 | XX(9, LOCK, LOCK) \ 338 | XX(10, MKCOL, MKCOL) \ 339 | XX(11, MOVE, MOVE) \ 340 | XX(12, PROPFIND, PROPFIND) \ 341 | XX(13, PROPPATCH, PROPPATCH) \ 342 | XX(14, SEARCH, SEARCH) \ 343 | XX(15, UNLOCK, UNLOCK) \ 344 | XX(16, BIND, BIND) \ 345 | XX(17, REBIND, REBIND) \ 346 | XX(18, UNBIND, UNBIND) \ 347 | XX(19, ACL, ACL) \ 348 | XX(20, REPORT, REPORT) \ 349 | XX(21, MKACTIVITY, MKACTIVITY) \ 350 | XX(22, CHECKOUT, CHECKOUT) \ 351 | XX(23, MERGE, MERGE) \ 352 | XX(24, MSEARCH, M-SEARCH) \ 353 | XX(25, NOTIFY, NOTIFY) \ 354 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 355 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 356 | XX(28, PATCH, PATCH) \ 357 | XX(29, PURGE, PURGE) \ 358 | XX(30, MKCALENDAR, MKCALENDAR) \ 359 | XX(31, LINK, LINK) \ 360 | XX(32, UNLINK, UNLINK) \ 361 | XX(33, SOURCE, SOURCE) \ 362 | 363 | 364 | #define RTSP_METHOD_MAP(XX) \ 365 | XX(1, GET, GET) \ 366 | XX(3, POST, POST) \ 367 | XX(6, OPTIONS, OPTIONS) \ 368 | XX(35, DESCRIBE, DESCRIBE) \ 369 | XX(36, ANNOUNCE, ANNOUNCE) \ 370 | XX(37, SETUP, SETUP) \ 371 | XX(38, PLAY, PLAY) \ 372 | XX(39, PAUSE, PAUSE) \ 373 | XX(40, TEARDOWN, TEARDOWN) \ 374 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 375 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 376 | XX(43, REDIRECT, REDIRECT) \ 377 | XX(44, RECORD, RECORD) \ 378 | XX(45, FLUSH, FLUSH) \ 379 | 380 | 381 | #define HTTP_ALL_METHOD_MAP(XX) \ 382 | XX(0, DELETE, DELETE) \ 383 | XX(1, GET, GET) \ 384 | XX(2, HEAD, HEAD) \ 385 | XX(3, POST, POST) \ 386 | XX(4, PUT, PUT) \ 387 | XX(5, CONNECT, CONNECT) \ 388 | XX(6, OPTIONS, OPTIONS) \ 389 | XX(7, TRACE, TRACE) \ 390 | XX(8, COPY, COPY) \ 391 | XX(9, LOCK, LOCK) \ 392 | XX(10, MKCOL, MKCOL) \ 393 | XX(11, MOVE, MOVE) \ 394 | XX(12, PROPFIND, PROPFIND) \ 395 | XX(13, PROPPATCH, PROPPATCH) \ 396 | XX(14, SEARCH, SEARCH) \ 397 | XX(15, UNLOCK, UNLOCK) \ 398 | XX(16, BIND, BIND) \ 399 | XX(17, REBIND, REBIND) \ 400 | XX(18, UNBIND, UNBIND) \ 401 | XX(19, ACL, ACL) \ 402 | XX(20, REPORT, REPORT) \ 403 | XX(21, MKACTIVITY, MKACTIVITY) \ 404 | XX(22, CHECKOUT, CHECKOUT) \ 405 | XX(23, MERGE, MERGE) \ 406 | XX(24, MSEARCH, M-SEARCH) \ 407 | XX(25, NOTIFY, NOTIFY) \ 408 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 409 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 410 | XX(28, PATCH, PATCH) \ 411 | XX(29, PURGE, PURGE) \ 412 | XX(30, MKCALENDAR, MKCALENDAR) \ 413 | XX(31, LINK, LINK) \ 414 | XX(32, UNLINK, UNLINK) \ 415 | XX(33, SOURCE, SOURCE) \ 416 | XX(34, PRI, PRI) \ 417 | XX(35, DESCRIBE, DESCRIBE) \ 418 | XX(36, ANNOUNCE, ANNOUNCE) \ 419 | XX(37, SETUP, SETUP) \ 420 | XX(38, PLAY, PLAY) \ 421 | XX(39, PAUSE, PAUSE) \ 422 | XX(40, TEARDOWN, TEARDOWN) \ 423 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 424 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 425 | XX(43, REDIRECT, REDIRECT) \ 426 | XX(44, RECORD, RECORD) \ 427 | XX(45, FLUSH, FLUSH) \ 428 | 429 | 430 | #define HTTP_STATUS_MAP(XX) \ 431 | XX(100, CONTINUE, CONTINUE) \ 432 | XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ 433 | XX(102, PROCESSING, PROCESSING) \ 434 | XX(103, EARLY_HINTS, EARLY_HINTS) \ 435 | XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ 436 | XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ 437 | XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ 438 | XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ 439 | XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ 440 | XX(200, OK, OK) \ 441 | XX(201, CREATED, CREATED) \ 442 | XX(202, ACCEPTED, ACCEPTED) \ 443 | XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ 444 | XX(204, NO_CONTENT, NO_CONTENT) \ 445 | XX(205, RESET_CONTENT, RESET_CONTENT) \ 446 | XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ 447 | XX(207, MULTI_STATUS, MULTI_STATUS) \ 448 | XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ 449 | XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ 450 | XX(226, IM_USED, IM_USED) \ 451 | XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ 452 | XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ 453 | XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ 454 | XX(302, FOUND, FOUND) \ 455 | XX(303, SEE_OTHER, SEE_OTHER) \ 456 | XX(304, NOT_MODIFIED, NOT_MODIFIED) \ 457 | XX(305, USE_PROXY, USE_PROXY) \ 458 | XX(306, SWITCH_PROXY, SWITCH_PROXY) \ 459 | XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ 460 | XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ 461 | XX(400, BAD_REQUEST, BAD_REQUEST) \ 462 | XX(401, UNAUTHORIZED, UNAUTHORIZED) \ 463 | XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ 464 | XX(403, FORBIDDEN, FORBIDDEN) \ 465 | XX(404, NOT_FOUND, NOT_FOUND) \ 466 | XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ 467 | XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ 468 | XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ 469 | XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ 470 | XX(409, CONFLICT, CONFLICT) \ 471 | XX(410, GONE, GONE) \ 472 | XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ 473 | XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ 474 | XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ 475 | XX(414, URI_TOO_LONG, URI_TOO_LONG) \ 476 | XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ 477 | XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ 478 | XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ 479 | XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ 480 | XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ 481 | XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ 482 | XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ 483 | XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ 484 | XX(423, LOCKED, LOCKED) \ 485 | XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ 486 | XX(425, TOO_EARLY, TOO_EARLY) \ 487 | XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ 488 | XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ 489 | XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ 490 | XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ 491 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ 492 | XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ 493 | XX(444, NO_RESPONSE, NO_RESPONSE) \ 494 | XX(449, RETRY_WITH, RETRY_WITH) \ 495 | XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ 496 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ 497 | XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ 498 | XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ 499 | XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ 500 | XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ 501 | XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ 502 | XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ 503 | XX(498, INVALID_TOKEN, INVALID_TOKEN) \ 504 | XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ 505 | XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ 506 | XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ 507 | XX(502, BAD_GATEWAY, BAD_GATEWAY) \ 508 | XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ 509 | XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ 510 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ 511 | XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ 512 | XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ 513 | XX(508, LOOP_DETECTED, LOOP_DETECTED) \ 514 | XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ 515 | XX(510, NOT_EXTENDED, NOT_EXTENDED) \ 516 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ 517 | XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ 518 | XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ 519 | XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ 520 | XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ 521 | XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ 522 | XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ 523 | XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ 524 | XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ 525 | XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ 526 | XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ 527 | XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ 528 | XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ 529 | XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ 530 | 531 | 532 | #ifdef __cplusplus 533 | } /* extern "C" */ 534 | #endif 535 | #endif /* LLLLHTTP_C_HEADERS_ */ 536 | 537 | #ifndef INCLUDE_LLHTTP_API_H_ 538 | #define INCLUDE_LLHTTP_API_H_ 539 | #ifdef __cplusplus 540 | extern "C" { 541 | #endif 542 | #include 543 | 544 | #if defined(__wasm__) 545 | #define LLHTTP_EXPORT __attribute__((visibility("default"))) 546 | #else 547 | #define LLHTTP_EXPORT 548 | #endif 549 | 550 | typedef llhttp__internal_t llhttp_t; 551 | typedef struct llhttp_settings_s llhttp_settings_t; 552 | 553 | typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); 554 | typedef int (*llhttp_cb)(llhttp_t*); 555 | 556 | struct llhttp_settings_s { 557 | /* Possible return values 0, -1, `HPE_PAUSED` */ 558 | llhttp_cb on_message_begin; 559 | 560 | /* Possible return values 0, -1, HPE_USER */ 561 | llhttp_data_cb on_url; 562 | llhttp_data_cb on_status; 563 | llhttp_data_cb on_method; 564 | llhttp_data_cb on_version; 565 | llhttp_data_cb on_header_field; 566 | llhttp_data_cb on_header_value; 567 | llhttp_data_cb on_chunk_extension_name; 568 | llhttp_data_cb on_chunk_extension_value; 569 | 570 | /* Possible return values: 571 | * 0 - Proceed normally 572 | * 1 - Assume that request/response has no body, and proceed to parsing the 573 | * next message 574 | * 2 - Assume absence of body (as above) and make `llhttp_execute()` return 575 | * `HPE_PAUSED_UPGRADE` 576 | * -1 - Error 577 | * `HPE_PAUSED` 578 | */ 579 | llhttp_cb on_headers_complete; 580 | 581 | /* Possible return values 0, -1, HPE_USER */ 582 | llhttp_data_cb on_body; 583 | 584 | /* Possible return values 0, -1, `HPE_PAUSED` */ 585 | llhttp_cb on_message_complete; 586 | llhttp_cb on_url_complete; 587 | llhttp_cb on_status_complete; 588 | llhttp_cb on_method_complete; 589 | llhttp_cb on_version_complete; 590 | llhttp_cb on_header_field_complete; 591 | llhttp_cb on_header_value_complete; 592 | llhttp_cb on_chunk_extension_name_complete; 593 | llhttp_cb on_chunk_extension_value_complete; 594 | 595 | /* When on_chunk_header is called, the current chunk length is stored 596 | * in parser->content_length. 597 | * Possible return values 0, -1, `HPE_PAUSED` 598 | */ 599 | llhttp_cb on_chunk_header; 600 | llhttp_cb on_chunk_complete; 601 | llhttp_cb on_reset; 602 | }; 603 | 604 | /* Initialize the parser with specific type and user settings. 605 | * 606 | * NOTE: lifetime of `settings` has to be at least the same as the lifetime of 607 | * the `parser` here. In practice, `settings` has to be either a static 608 | * variable or be allocated with `malloc`, `new`, etc. 609 | */ 610 | LLHTTP_EXPORT 611 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 612 | const llhttp_settings_t* settings); 613 | 614 | LLHTTP_EXPORT 615 | llhttp_t* llhttp_alloc(llhttp_type_t type); 616 | 617 | LLHTTP_EXPORT 618 | void llhttp_free(llhttp_t* parser); 619 | 620 | LLHTTP_EXPORT 621 | uint8_t llhttp_get_type(llhttp_t* parser); 622 | 623 | LLHTTP_EXPORT 624 | uint8_t llhttp_get_http_major(llhttp_t* parser); 625 | 626 | LLHTTP_EXPORT 627 | uint8_t llhttp_get_http_minor(llhttp_t* parser); 628 | 629 | LLHTTP_EXPORT 630 | uint8_t llhttp_get_method(llhttp_t* parser); 631 | 632 | LLHTTP_EXPORT 633 | int llhttp_get_status_code(llhttp_t* parser); 634 | 635 | LLHTTP_EXPORT 636 | uint8_t llhttp_get_upgrade(llhttp_t* parser); 637 | 638 | /* Reset an already initialized parser back to the start state, preserving the 639 | * existing parser type, callback settings, user data, and lenient flags. 640 | */ 641 | LLHTTP_EXPORT 642 | void llhttp_reset(llhttp_t* parser); 643 | 644 | /* Initialize the settings object */ 645 | LLHTTP_EXPORT 646 | void llhttp_settings_init(llhttp_settings_t* settings); 647 | 648 | /* Parse full or partial request/response, invoking user callbacks along the 649 | * way. 650 | * 651 | * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing 652 | * interrupts, and such errno is returned from `llhttp_execute()`. If 653 | * `HPE_PAUSED` was used as a errno, the execution can be resumed with 654 | * `llhttp_resume()` call. 655 | * 656 | * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` 657 | * is returned after fully parsing the request/response. If the user wishes to 658 | * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. 659 | * 660 | * NOTE: if this function ever returns a non-pause type error, it will continue 661 | * to return the same error upon each successive call up until `llhttp_init()` 662 | * is called. 663 | */ 664 | LLHTTP_EXPORT 665 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); 666 | 667 | /* This method should be called when the other side has no further bytes to 668 | * send (e.g. shutdown of readable side of the TCP connection.) 669 | * 670 | * Requests without `Content-Length` and other messages might require treating 671 | * all incoming bytes as the part of the body, up to the last byte of the 672 | * connection. This method will invoke `on_message_complete()` callback if the 673 | * request was terminated safely. Otherwise a error code would be returned. 674 | */ 675 | LLHTTP_EXPORT 676 | llhttp_errno_t llhttp_finish(llhttp_t* parser); 677 | 678 | /* Returns `1` if the incoming message is parsed until the last byte, and has 679 | * to be completed by calling `llhttp_finish()` on EOF 680 | */ 681 | LLHTTP_EXPORT 682 | int llhttp_message_needs_eof(const llhttp_t* parser); 683 | 684 | /* Returns `1` if there might be any other messages following the last that was 685 | * successfully parsed. 686 | */ 687 | LLHTTP_EXPORT 688 | int llhttp_should_keep_alive(const llhttp_t* parser); 689 | 690 | /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set 691 | * appropriate error reason. 692 | * 693 | * Important: do not call this from user callbacks! User callbacks must return 694 | * `HPE_PAUSED` if pausing is required. 695 | */ 696 | LLHTTP_EXPORT 697 | void llhttp_pause(llhttp_t* parser); 698 | 699 | /* Might be called to resume the execution after the pause in user's callback. 700 | * See `llhttp_execute()` above for details. 701 | * 702 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. 703 | */ 704 | LLHTTP_EXPORT 705 | void llhttp_resume(llhttp_t* parser); 706 | 707 | /* Might be called to resume the execution after the pause in user's callback. 708 | * See `llhttp_execute()` above for details. 709 | * 710 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` 711 | */ 712 | LLHTTP_EXPORT 713 | void llhttp_resume_after_upgrade(llhttp_t* parser); 714 | 715 | /* Returns the latest return error */ 716 | LLHTTP_EXPORT 717 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); 718 | 719 | /* Returns the verbal explanation of the latest returned error. 720 | * 721 | * Note: User callback should set error reason when returning the error. See 722 | * `llhttp_set_error_reason()` for details. 723 | */ 724 | LLHTTP_EXPORT 725 | const char* llhttp_get_error_reason(const llhttp_t* parser); 726 | 727 | /* Assign verbal description to the returned error. Must be called in user 728 | * callbacks right before returning the errno. 729 | * 730 | * Note: `HPE_USER` error code might be useful in user callbacks. 731 | */ 732 | LLHTTP_EXPORT 733 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason); 734 | 735 | /* Returns the pointer to the last parsed byte before the returned error. The 736 | * pointer is relative to the `data` argument of `llhttp_execute()`. 737 | * 738 | * Note: this method might be useful for counting the number of parsed bytes. 739 | */ 740 | LLHTTP_EXPORT 741 | const char* llhttp_get_error_pos(const llhttp_t* parser); 742 | 743 | /* Returns textual name of error code */ 744 | LLHTTP_EXPORT 745 | const char* llhttp_errno_name(llhttp_errno_t err); 746 | 747 | /* Returns textual name of HTTP method */ 748 | LLHTTP_EXPORT 749 | const char* llhttp_method_name(llhttp_method_t method); 750 | 751 | /* Returns textual name of HTTP status */ 752 | LLHTTP_EXPORT 753 | const char* llhttp_status_name(llhttp_status_t status); 754 | 755 | /* Enables/disables lenient header value parsing (disabled by default). 756 | * 757 | * Lenient parsing disables header value token checks, extending llhttp's 758 | * protocol support to highly non-compliant clients/server. No 759 | * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when 760 | * lenient parsing is "on". 761 | * 762 | * **(USE AT YOUR OWN RISK)** 763 | */ 764 | LLHTTP_EXPORT 765 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); 766 | 767 | 768 | /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and 769 | * `Content-Length` headers (disabled by default). 770 | * 771 | * Normally `llhttp` would error when `Transfer-Encoding` is present in 772 | * conjunction with `Content-Length`. This error is important to prevent HTTP 773 | * request smuggling, but may be less desirable for small number of cases 774 | * involving legacy servers. 775 | * 776 | * **(USE AT YOUR OWN RISK)** 777 | */ 778 | LLHTTP_EXPORT 779 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); 780 | 781 | 782 | /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 783 | * requests responses. 784 | * 785 | * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) 786 | * the HTTP request/response after the request/response with `Connection: close` 787 | * and `Content-Length`. This is important to prevent cache poisoning attacks, 788 | * but might interact badly with outdated and insecure clients. With this flag 789 | * the extra request/response will be parsed normally. 790 | * 791 | * **(USE AT YOUR OWN RISK)** 792 | */ 793 | LLHTTP_EXPORT 794 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); 795 | 796 | /* Enables/disables lenient handling of `Transfer-Encoding` header. 797 | * 798 | * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value 799 | * and another value after it (either in a single header or in multiple 800 | * headers whose value are internally joined using `, `). 801 | * This is mandated by the spec to reliably determine request body size and thus 802 | * avoid request smuggling. 803 | * With this flag the extra value will be parsed normally. 804 | * 805 | * **(USE AT YOUR OWN RISK)** 806 | */ 807 | LLHTTP_EXPORT 808 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); 809 | 810 | #ifdef __cplusplus 811 | } /* extern "C" */ 812 | #endif 813 | #endif /* INCLUDE_LLHTTP_API_H_ */ 814 | 815 | #endif /* INCLUDE_LLHTTP_H_ */ 816 | -------------------------------------------------------------------------------- /mri/ext/llhttp/llhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_LLHTTP_H_ 2 | #define INCLUDE_LLHTTP_H_ 3 | 4 | #define LLHTTP_VERSION_MAJOR 8 5 | #define LLHTTP_VERSION_MINOR 1 6 | #define LLHTTP_VERSION_PATCH 0 7 | 8 | #ifndef LLHTTP_STRICT_MODE 9 | # define LLHTTP_STRICT_MODE 0 10 | #endif 11 | 12 | #ifndef INCLUDE_LLHTTP_ITSELF_H_ 13 | #define INCLUDE_LLHTTP_ITSELF_H_ 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | #include 19 | 20 | typedef struct llhttp__internal_s llhttp__internal_t; 21 | struct llhttp__internal_s { 22 | int32_t _index; 23 | void* _span_pos0; 24 | void* _span_cb0; 25 | int32_t error; 26 | const char* reason; 27 | const char* error_pos; 28 | void* data; 29 | void* _current; 30 | uint64_t content_length; 31 | uint8_t type; 32 | uint8_t method; 33 | uint8_t http_major; 34 | uint8_t http_minor; 35 | uint8_t header_state; 36 | uint8_t lenient_flags; 37 | uint8_t upgrade; 38 | uint8_t finish; 39 | uint16_t flags; 40 | uint16_t status_code; 41 | uint8_t initial_message_completed; 42 | void* settings; 43 | }; 44 | 45 | int llhttp__internal_init(llhttp__internal_t* s); 46 | int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); 47 | 48 | #ifdef __cplusplus 49 | } /* extern "C" */ 50 | #endif 51 | #endif /* INCLUDE_LLHTTP_ITSELF_H_ */ 52 | 53 | #ifndef LLLLHTTP_C_HEADERS_ 54 | #define LLLLHTTP_C_HEADERS_ 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | enum llhttp_errno { 60 | HPE_OK = 0, 61 | HPE_INTERNAL = 1, 62 | HPE_STRICT = 2, 63 | HPE_CR_EXPECTED = 25, 64 | HPE_LF_EXPECTED = 3, 65 | HPE_UNEXPECTED_CONTENT_LENGTH = 4, 66 | HPE_UNEXPECTED_SPACE = 30, 67 | HPE_CLOSED_CONNECTION = 5, 68 | HPE_INVALID_METHOD = 6, 69 | HPE_INVALID_URL = 7, 70 | HPE_INVALID_CONSTANT = 8, 71 | HPE_INVALID_VERSION = 9, 72 | HPE_INVALID_HEADER_TOKEN = 10, 73 | HPE_INVALID_CONTENT_LENGTH = 11, 74 | HPE_INVALID_CHUNK_SIZE = 12, 75 | HPE_INVALID_STATUS = 13, 76 | HPE_INVALID_EOF_STATE = 14, 77 | HPE_INVALID_TRANSFER_ENCODING = 15, 78 | HPE_CB_MESSAGE_BEGIN = 16, 79 | HPE_CB_HEADERS_COMPLETE = 17, 80 | HPE_CB_MESSAGE_COMPLETE = 18, 81 | HPE_CB_CHUNK_HEADER = 19, 82 | HPE_CB_CHUNK_COMPLETE = 20, 83 | HPE_PAUSED = 21, 84 | HPE_PAUSED_UPGRADE = 22, 85 | HPE_PAUSED_H2_UPGRADE = 23, 86 | HPE_USER = 24, 87 | HPE_CB_URL_COMPLETE = 26, 88 | HPE_CB_STATUS_COMPLETE = 27, 89 | HPE_CB_METHOD_COMPLETE = 32, 90 | HPE_CB_VERSION_COMPLETE = 33, 91 | HPE_CB_HEADER_FIELD_COMPLETE = 28, 92 | HPE_CB_HEADER_VALUE_COMPLETE = 29, 93 | HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, 94 | HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, 95 | HPE_CB_RESET = 31 96 | }; 97 | typedef enum llhttp_errno llhttp_errno_t; 98 | 99 | enum llhttp_flags { 100 | F_CONNECTION_KEEP_ALIVE = 0x1, 101 | F_CONNECTION_CLOSE = 0x2, 102 | F_CONNECTION_UPGRADE = 0x4, 103 | F_CHUNKED = 0x8, 104 | F_UPGRADE = 0x10, 105 | F_CONTENT_LENGTH = 0x20, 106 | F_SKIPBODY = 0x40, 107 | F_TRAILING = 0x80, 108 | F_TRANSFER_ENCODING = 0x200 109 | }; 110 | typedef enum llhttp_flags llhttp_flags_t; 111 | 112 | enum llhttp_lenient_flags { 113 | LENIENT_HEADERS = 0x1, 114 | LENIENT_CHUNKED_LENGTH = 0x2, 115 | LENIENT_KEEP_ALIVE = 0x4, 116 | LENIENT_TRANSFER_ENCODING = 0x8, 117 | LENIENT_VERSION = 0x10 118 | }; 119 | typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; 120 | 121 | enum llhttp_type { 122 | HTTP_BOTH = 0, 123 | HTTP_REQUEST = 1, 124 | HTTP_RESPONSE = 2 125 | }; 126 | typedef enum llhttp_type llhttp_type_t; 127 | 128 | enum llhttp_finish { 129 | HTTP_FINISH_SAFE = 0, 130 | HTTP_FINISH_SAFE_WITH_CB = 1, 131 | HTTP_FINISH_UNSAFE = 2 132 | }; 133 | typedef enum llhttp_finish llhttp_finish_t; 134 | 135 | enum llhttp_method { 136 | HTTP_DELETE = 0, 137 | HTTP_GET = 1, 138 | HTTP_HEAD = 2, 139 | HTTP_POST = 3, 140 | HTTP_PUT = 4, 141 | HTTP_CONNECT = 5, 142 | HTTP_OPTIONS = 6, 143 | HTTP_TRACE = 7, 144 | HTTP_COPY = 8, 145 | HTTP_LOCK = 9, 146 | HTTP_MKCOL = 10, 147 | HTTP_MOVE = 11, 148 | HTTP_PROPFIND = 12, 149 | HTTP_PROPPATCH = 13, 150 | HTTP_SEARCH = 14, 151 | HTTP_UNLOCK = 15, 152 | HTTP_BIND = 16, 153 | HTTP_REBIND = 17, 154 | HTTP_UNBIND = 18, 155 | HTTP_ACL = 19, 156 | HTTP_REPORT = 20, 157 | HTTP_MKACTIVITY = 21, 158 | HTTP_CHECKOUT = 22, 159 | HTTP_MERGE = 23, 160 | HTTP_MSEARCH = 24, 161 | HTTP_NOTIFY = 25, 162 | HTTP_SUBSCRIBE = 26, 163 | HTTP_UNSUBSCRIBE = 27, 164 | HTTP_PATCH = 28, 165 | HTTP_PURGE = 29, 166 | HTTP_MKCALENDAR = 30, 167 | HTTP_LINK = 31, 168 | HTTP_UNLINK = 32, 169 | HTTP_SOURCE = 33, 170 | HTTP_PRI = 34, 171 | HTTP_DESCRIBE = 35, 172 | HTTP_ANNOUNCE = 36, 173 | HTTP_SETUP = 37, 174 | HTTP_PLAY = 38, 175 | HTTP_PAUSE = 39, 176 | HTTP_TEARDOWN = 40, 177 | HTTP_GET_PARAMETER = 41, 178 | HTTP_SET_PARAMETER = 42, 179 | HTTP_REDIRECT = 43, 180 | HTTP_RECORD = 44, 181 | HTTP_FLUSH = 45 182 | }; 183 | typedef enum llhttp_method llhttp_method_t; 184 | 185 | enum llhttp_status { 186 | HTTP_STATUS_CONTINUE = 100, 187 | HTTP_STATUS_SWITCHING_PROTOCOLS = 101, 188 | HTTP_STATUS_PROCESSING = 102, 189 | HTTP_STATUS_EARLY_HINTS = 103, 190 | HTTP_STATUS_RESPONSE_IS_STALE = 110, 191 | HTTP_STATUS_REVALIDATION_FAILED = 111, 192 | HTTP_STATUS_DISCONNECTED_OPERATION = 112, 193 | HTTP_STATUS_HEURISTIC_EXPIRATION = 113, 194 | HTTP_STATUS_MISCELLANEOUS_WARNING = 199, 195 | HTTP_STATUS_OK = 200, 196 | HTTP_STATUS_CREATED = 201, 197 | HTTP_STATUS_ACCEPTED = 202, 198 | HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, 199 | HTTP_STATUS_NO_CONTENT = 204, 200 | HTTP_STATUS_RESET_CONTENT = 205, 201 | HTTP_STATUS_PARTIAL_CONTENT = 206, 202 | HTTP_STATUS_MULTI_STATUS = 207, 203 | HTTP_STATUS_ALREADY_REPORTED = 208, 204 | HTTP_STATUS_TRANSFORMATION_APPLIED = 214, 205 | HTTP_STATUS_IM_USED = 226, 206 | HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, 207 | HTTP_STATUS_MULTIPLE_CHOICES = 300, 208 | HTTP_STATUS_MOVED_PERMANENTLY = 301, 209 | HTTP_STATUS_FOUND = 302, 210 | HTTP_STATUS_SEE_OTHER = 303, 211 | HTTP_STATUS_NOT_MODIFIED = 304, 212 | HTTP_STATUS_USE_PROXY = 305, 213 | HTTP_STATUS_SWITCH_PROXY = 306, 214 | HTTP_STATUS_TEMPORARY_REDIRECT = 307, 215 | HTTP_STATUS_PERMANENT_REDIRECT = 308, 216 | HTTP_STATUS_BAD_REQUEST = 400, 217 | HTTP_STATUS_UNAUTHORIZED = 401, 218 | HTTP_STATUS_PAYMENT_REQUIRED = 402, 219 | HTTP_STATUS_FORBIDDEN = 403, 220 | HTTP_STATUS_NOT_FOUND = 404, 221 | HTTP_STATUS_METHOD_NOT_ALLOWED = 405, 222 | HTTP_STATUS_NOT_ACCEPTABLE = 406, 223 | HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, 224 | HTTP_STATUS_REQUEST_TIMEOUT = 408, 225 | HTTP_STATUS_CONFLICT = 409, 226 | HTTP_STATUS_GONE = 410, 227 | HTTP_STATUS_LENGTH_REQUIRED = 411, 228 | HTTP_STATUS_PRECONDITION_FAILED = 412, 229 | HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, 230 | HTTP_STATUS_URI_TOO_LONG = 414, 231 | HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, 232 | HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, 233 | HTTP_STATUS_EXPECTATION_FAILED = 417, 234 | HTTP_STATUS_IM_A_TEAPOT = 418, 235 | HTTP_STATUS_PAGE_EXPIRED = 419, 236 | HTTP_STATUS_ENHANCE_YOUR_CALM = 420, 237 | HTTP_STATUS_MISDIRECTED_REQUEST = 421, 238 | HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, 239 | HTTP_STATUS_LOCKED = 423, 240 | HTTP_STATUS_FAILED_DEPENDENCY = 424, 241 | HTTP_STATUS_TOO_EARLY = 425, 242 | HTTP_STATUS_UPGRADE_REQUIRED = 426, 243 | HTTP_STATUS_PRECONDITION_REQUIRED = 428, 244 | HTTP_STATUS_TOO_MANY_REQUESTS = 429, 245 | HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, 246 | HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 247 | HTTP_STATUS_LOGIN_TIMEOUT = 440, 248 | HTTP_STATUS_NO_RESPONSE = 444, 249 | HTTP_STATUS_RETRY_WITH = 449, 250 | HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, 251 | HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, 252 | HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, 253 | HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, 254 | HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, 255 | HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, 256 | HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, 257 | HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, 258 | HTTP_STATUS_INVALID_TOKEN = 498, 259 | HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, 260 | HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, 261 | HTTP_STATUS_NOT_IMPLEMENTED = 501, 262 | HTTP_STATUS_BAD_GATEWAY = 502, 263 | HTTP_STATUS_SERVICE_UNAVAILABLE = 503, 264 | HTTP_STATUS_GATEWAY_TIMEOUT = 504, 265 | HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, 266 | HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, 267 | HTTP_STATUS_INSUFFICIENT_STORAGE = 507, 268 | HTTP_STATUS_LOOP_DETECTED = 508, 269 | HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, 270 | HTTP_STATUS_NOT_EXTENDED = 510, 271 | HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, 272 | HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, 273 | HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, 274 | HTTP_STATUS_CONNECTION_TIMEOUT = 522, 275 | HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, 276 | HTTP_STATUS_TIMEOUT_OCCURED = 524, 277 | HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, 278 | HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, 279 | HTTP_STATUS_RAILGUN_ERROR = 527, 280 | HTTP_STATUS_SITE_IS_OVERLOADED = 529, 281 | HTTP_STATUS_SITE_IS_FROZEN = 530, 282 | HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, 283 | HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, 284 | HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 285 | }; 286 | typedef enum llhttp_status llhttp_status_t; 287 | 288 | #define HTTP_ERRNO_MAP(XX) \ 289 | XX(0, OK, OK) \ 290 | XX(1, INTERNAL, INTERNAL) \ 291 | XX(2, STRICT, STRICT) \ 292 | XX(25, CR_EXPECTED, CR_EXPECTED) \ 293 | XX(3, LF_EXPECTED, LF_EXPECTED) \ 294 | XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ 295 | XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ 296 | XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ 297 | XX(6, INVALID_METHOD, INVALID_METHOD) \ 298 | XX(7, INVALID_URL, INVALID_URL) \ 299 | XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ 300 | XX(9, INVALID_VERSION, INVALID_VERSION) \ 301 | XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ 302 | XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ 303 | XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ 304 | XX(13, INVALID_STATUS, INVALID_STATUS) \ 305 | XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ 306 | XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ 307 | XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ 308 | XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ 309 | XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ 310 | XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ 311 | XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ 312 | XX(21, PAUSED, PAUSED) \ 313 | XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ 314 | XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ 315 | XX(24, USER, USER) \ 316 | XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ 317 | XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ 318 | XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ 319 | XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ 320 | XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ 321 | XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ 322 | XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ 323 | XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ 324 | XX(31, CB_RESET, CB_RESET) \ 325 | 326 | 327 | #define HTTP_METHOD_MAP(XX) \ 328 | XX(0, DELETE, DELETE) \ 329 | XX(1, GET, GET) \ 330 | XX(2, HEAD, HEAD) \ 331 | XX(3, POST, POST) \ 332 | XX(4, PUT, PUT) \ 333 | XX(5, CONNECT, CONNECT) \ 334 | XX(6, OPTIONS, OPTIONS) \ 335 | XX(7, TRACE, TRACE) \ 336 | XX(8, COPY, COPY) \ 337 | XX(9, LOCK, LOCK) \ 338 | XX(10, MKCOL, MKCOL) \ 339 | XX(11, MOVE, MOVE) \ 340 | XX(12, PROPFIND, PROPFIND) \ 341 | XX(13, PROPPATCH, PROPPATCH) \ 342 | XX(14, SEARCH, SEARCH) \ 343 | XX(15, UNLOCK, UNLOCK) \ 344 | XX(16, BIND, BIND) \ 345 | XX(17, REBIND, REBIND) \ 346 | XX(18, UNBIND, UNBIND) \ 347 | XX(19, ACL, ACL) \ 348 | XX(20, REPORT, REPORT) \ 349 | XX(21, MKACTIVITY, MKACTIVITY) \ 350 | XX(22, CHECKOUT, CHECKOUT) \ 351 | XX(23, MERGE, MERGE) \ 352 | XX(24, MSEARCH, M-SEARCH) \ 353 | XX(25, NOTIFY, NOTIFY) \ 354 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 355 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 356 | XX(28, PATCH, PATCH) \ 357 | XX(29, PURGE, PURGE) \ 358 | XX(30, MKCALENDAR, MKCALENDAR) \ 359 | XX(31, LINK, LINK) \ 360 | XX(32, UNLINK, UNLINK) \ 361 | XX(33, SOURCE, SOURCE) \ 362 | 363 | 364 | #define RTSP_METHOD_MAP(XX) \ 365 | XX(1, GET, GET) \ 366 | XX(3, POST, POST) \ 367 | XX(6, OPTIONS, OPTIONS) \ 368 | XX(35, DESCRIBE, DESCRIBE) \ 369 | XX(36, ANNOUNCE, ANNOUNCE) \ 370 | XX(37, SETUP, SETUP) \ 371 | XX(38, PLAY, PLAY) \ 372 | XX(39, PAUSE, PAUSE) \ 373 | XX(40, TEARDOWN, TEARDOWN) \ 374 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 375 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 376 | XX(43, REDIRECT, REDIRECT) \ 377 | XX(44, RECORD, RECORD) \ 378 | XX(45, FLUSH, FLUSH) \ 379 | 380 | 381 | #define HTTP_ALL_METHOD_MAP(XX) \ 382 | XX(0, DELETE, DELETE) \ 383 | XX(1, GET, GET) \ 384 | XX(2, HEAD, HEAD) \ 385 | XX(3, POST, POST) \ 386 | XX(4, PUT, PUT) \ 387 | XX(5, CONNECT, CONNECT) \ 388 | XX(6, OPTIONS, OPTIONS) \ 389 | XX(7, TRACE, TRACE) \ 390 | XX(8, COPY, COPY) \ 391 | XX(9, LOCK, LOCK) \ 392 | XX(10, MKCOL, MKCOL) \ 393 | XX(11, MOVE, MOVE) \ 394 | XX(12, PROPFIND, PROPFIND) \ 395 | XX(13, PROPPATCH, PROPPATCH) \ 396 | XX(14, SEARCH, SEARCH) \ 397 | XX(15, UNLOCK, UNLOCK) \ 398 | XX(16, BIND, BIND) \ 399 | XX(17, REBIND, REBIND) \ 400 | XX(18, UNBIND, UNBIND) \ 401 | XX(19, ACL, ACL) \ 402 | XX(20, REPORT, REPORT) \ 403 | XX(21, MKACTIVITY, MKACTIVITY) \ 404 | XX(22, CHECKOUT, CHECKOUT) \ 405 | XX(23, MERGE, MERGE) \ 406 | XX(24, MSEARCH, M-SEARCH) \ 407 | XX(25, NOTIFY, NOTIFY) \ 408 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 409 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 410 | XX(28, PATCH, PATCH) \ 411 | XX(29, PURGE, PURGE) \ 412 | XX(30, MKCALENDAR, MKCALENDAR) \ 413 | XX(31, LINK, LINK) \ 414 | XX(32, UNLINK, UNLINK) \ 415 | XX(33, SOURCE, SOURCE) \ 416 | XX(34, PRI, PRI) \ 417 | XX(35, DESCRIBE, DESCRIBE) \ 418 | XX(36, ANNOUNCE, ANNOUNCE) \ 419 | XX(37, SETUP, SETUP) \ 420 | XX(38, PLAY, PLAY) \ 421 | XX(39, PAUSE, PAUSE) \ 422 | XX(40, TEARDOWN, TEARDOWN) \ 423 | XX(41, GET_PARAMETER, GET_PARAMETER) \ 424 | XX(42, SET_PARAMETER, SET_PARAMETER) \ 425 | XX(43, REDIRECT, REDIRECT) \ 426 | XX(44, RECORD, RECORD) \ 427 | XX(45, FLUSH, FLUSH) \ 428 | 429 | 430 | #define HTTP_STATUS_MAP(XX) \ 431 | XX(100, CONTINUE, CONTINUE) \ 432 | XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ 433 | XX(102, PROCESSING, PROCESSING) \ 434 | XX(103, EARLY_HINTS, EARLY_HINTS) \ 435 | XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ 436 | XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ 437 | XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ 438 | XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ 439 | XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ 440 | XX(200, OK, OK) \ 441 | XX(201, CREATED, CREATED) \ 442 | XX(202, ACCEPTED, ACCEPTED) \ 443 | XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ 444 | XX(204, NO_CONTENT, NO_CONTENT) \ 445 | XX(205, RESET_CONTENT, RESET_CONTENT) \ 446 | XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ 447 | XX(207, MULTI_STATUS, MULTI_STATUS) \ 448 | XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ 449 | XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ 450 | XX(226, IM_USED, IM_USED) \ 451 | XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ 452 | XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ 453 | XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ 454 | XX(302, FOUND, FOUND) \ 455 | XX(303, SEE_OTHER, SEE_OTHER) \ 456 | XX(304, NOT_MODIFIED, NOT_MODIFIED) \ 457 | XX(305, USE_PROXY, USE_PROXY) \ 458 | XX(306, SWITCH_PROXY, SWITCH_PROXY) \ 459 | XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ 460 | XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ 461 | XX(400, BAD_REQUEST, BAD_REQUEST) \ 462 | XX(401, UNAUTHORIZED, UNAUTHORIZED) \ 463 | XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ 464 | XX(403, FORBIDDEN, FORBIDDEN) \ 465 | XX(404, NOT_FOUND, NOT_FOUND) \ 466 | XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ 467 | XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ 468 | XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ 469 | XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ 470 | XX(409, CONFLICT, CONFLICT) \ 471 | XX(410, GONE, GONE) \ 472 | XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ 473 | XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ 474 | XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ 475 | XX(414, URI_TOO_LONG, URI_TOO_LONG) \ 476 | XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ 477 | XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ 478 | XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ 479 | XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ 480 | XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ 481 | XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ 482 | XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ 483 | XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ 484 | XX(423, LOCKED, LOCKED) \ 485 | XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ 486 | XX(425, TOO_EARLY, TOO_EARLY) \ 487 | XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ 488 | XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ 489 | XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ 490 | XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ 491 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ 492 | XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ 493 | XX(444, NO_RESPONSE, NO_RESPONSE) \ 494 | XX(449, RETRY_WITH, RETRY_WITH) \ 495 | XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ 496 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ 497 | XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ 498 | XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ 499 | XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ 500 | XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ 501 | XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ 502 | XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ 503 | XX(498, INVALID_TOKEN, INVALID_TOKEN) \ 504 | XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ 505 | XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ 506 | XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ 507 | XX(502, BAD_GATEWAY, BAD_GATEWAY) \ 508 | XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ 509 | XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ 510 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ 511 | XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ 512 | XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ 513 | XX(508, LOOP_DETECTED, LOOP_DETECTED) \ 514 | XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ 515 | XX(510, NOT_EXTENDED, NOT_EXTENDED) \ 516 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ 517 | XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ 518 | XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ 519 | XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ 520 | XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ 521 | XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ 522 | XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ 523 | XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ 524 | XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ 525 | XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ 526 | XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ 527 | XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ 528 | XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ 529 | XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ 530 | 531 | 532 | #ifdef __cplusplus 533 | } /* extern "C" */ 534 | #endif 535 | #endif /* LLLLHTTP_C_HEADERS_ */ 536 | 537 | #ifndef INCLUDE_LLHTTP_API_H_ 538 | #define INCLUDE_LLHTTP_API_H_ 539 | #ifdef __cplusplus 540 | extern "C" { 541 | #endif 542 | #include 543 | 544 | #if defined(__wasm__) 545 | #define LLHTTP_EXPORT __attribute__((visibility("default"))) 546 | #else 547 | #define LLHTTP_EXPORT 548 | #endif 549 | 550 | typedef llhttp__internal_t llhttp_t; 551 | typedef struct llhttp_settings_s llhttp_settings_t; 552 | 553 | typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); 554 | typedef int (*llhttp_cb)(llhttp_t*); 555 | 556 | struct llhttp_settings_s { 557 | /* Possible return values 0, -1, `HPE_PAUSED` */ 558 | llhttp_cb on_message_begin; 559 | 560 | /* Possible return values 0, -1, HPE_USER */ 561 | llhttp_data_cb on_url; 562 | llhttp_data_cb on_status; 563 | llhttp_data_cb on_method; 564 | llhttp_data_cb on_version; 565 | llhttp_data_cb on_header_field; 566 | llhttp_data_cb on_header_value; 567 | llhttp_data_cb on_chunk_extension_name; 568 | llhttp_data_cb on_chunk_extension_value; 569 | 570 | /* Possible return values: 571 | * 0 - Proceed normally 572 | * 1 - Assume that request/response has no body, and proceed to parsing the 573 | * next message 574 | * 2 - Assume absence of body (as above) and make `llhttp_execute()` return 575 | * `HPE_PAUSED_UPGRADE` 576 | * -1 - Error 577 | * `HPE_PAUSED` 578 | */ 579 | llhttp_cb on_headers_complete; 580 | 581 | /* Possible return values 0, -1, HPE_USER */ 582 | llhttp_data_cb on_body; 583 | 584 | /* Possible return values 0, -1, `HPE_PAUSED` */ 585 | llhttp_cb on_message_complete; 586 | llhttp_cb on_url_complete; 587 | llhttp_cb on_status_complete; 588 | llhttp_cb on_method_complete; 589 | llhttp_cb on_version_complete; 590 | llhttp_cb on_header_field_complete; 591 | llhttp_cb on_header_value_complete; 592 | llhttp_cb on_chunk_extension_name_complete; 593 | llhttp_cb on_chunk_extension_value_complete; 594 | 595 | /* When on_chunk_header is called, the current chunk length is stored 596 | * in parser->content_length. 597 | * Possible return values 0, -1, `HPE_PAUSED` 598 | */ 599 | llhttp_cb on_chunk_header; 600 | llhttp_cb on_chunk_complete; 601 | llhttp_cb on_reset; 602 | }; 603 | 604 | /* Initialize the parser with specific type and user settings. 605 | * 606 | * NOTE: lifetime of `settings` has to be at least the same as the lifetime of 607 | * the `parser` here. In practice, `settings` has to be either a static 608 | * variable or be allocated with `malloc`, `new`, etc. 609 | */ 610 | LLHTTP_EXPORT 611 | void llhttp_init(llhttp_t* parser, llhttp_type_t type, 612 | const llhttp_settings_t* settings); 613 | 614 | LLHTTP_EXPORT 615 | llhttp_t* llhttp_alloc(llhttp_type_t type); 616 | 617 | LLHTTP_EXPORT 618 | void llhttp_free(llhttp_t* parser); 619 | 620 | LLHTTP_EXPORT 621 | uint8_t llhttp_get_type(llhttp_t* parser); 622 | 623 | LLHTTP_EXPORT 624 | uint8_t llhttp_get_http_major(llhttp_t* parser); 625 | 626 | LLHTTP_EXPORT 627 | uint8_t llhttp_get_http_minor(llhttp_t* parser); 628 | 629 | LLHTTP_EXPORT 630 | uint8_t llhttp_get_method(llhttp_t* parser); 631 | 632 | LLHTTP_EXPORT 633 | int llhttp_get_status_code(llhttp_t* parser); 634 | 635 | LLHTTP_EXPORT 636 | uint8_t llhttp_get_upgrade(llhttp_t* parser); 637 | 638 | /* Reset an already initialized parser back to the start state, preserving the 639 | * existing parser type, callback settings, user data, and lenient flags. 640 | */ 641 | LLHTTP_EXPORT 642 | void llhttp_reset(llhttp_t* parser); 643 | 644 | /* Initialize the settings object */ 645 | LLHTTP_EXPORT 646 | void llhttp_settings_init(llhttp_settings_t* settings); 647 | 648 | /* Parse full or partial request/response, invoking user callbacks along the 649 | * way. 650 | * 651 | * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing 652 | * interrupts, and such errno is returned from `llhttp_execute()`. If 653 | * `HPE_PAUSED` was used as a errno, the execution can be resumed with 654 | * `llhttp_resume()` call. 655 | * 656 | * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` 657 | * is returned after fully parsing the request/response. If the user wishes to 658 | * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. 659 | * 660 | * NOTE: if this function ever returns a non-pause type error, it will continue 661 | * to return the same error upon each successive call up until `llhttp_init()` 662 | * is called. 663 | */ 664 | LLHTTP_EXPORT 665 | llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); 666 | 667 | /* This method should be called when the other side has no further bytes to 668 | * send (e.g. shutdown of readable side of the TCP connection.) 669 | * 670 | * Requests without `Content-Length` and other messages might require treating 671 | * all incoming bytes as the part of the body, up to the last byte of the 672 | * connection. This method will invoke `on_message_complete()` callback if the 673 | * request was terminated safely. Otherwise a error code would be returned. 674 | */ 675 | LLHTTP_EXPORT 676 | llhttp_errno_t llhttp_finish(llhttp_t* parser); 677 | 678 | /* Returns `1` if the incoming message is parsed until the last byte, and has 679 | * to be completed by calling `llhttp_finish()` on EOF 680 | */ 681 | LLHTTP_EXPORT 682 | int llhttp_message_needs_eof(const llhttp_t* parser); 683 | 684 | /* Returns `1` if there might be any other messages following the last that was 685 | * successfully parsed. 686 | */ 687 | LLHTTP_EXPORT 688 | int llhttp_should_keep_alive(const llhttp_t* parser); 689 | 690 | /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set 691 | * appropriate error reason. 692 | * 693 | * Important: do not call this from user callbacks! User callbacks must return 694 | * `HPE_PAUSED` if pausing is required. 695 | */ 696 | LLHTTP_EXPORT 697 | void llhttp_pause(llhttp_t* parser); 698 | 699 | /* Might be called to resume the execution after the pause in user's callback. 700 | * See `llhttp_execute()` above for details. 701 | * 702 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. 703 | */ 704 | LLHTTP_EXPORT 705 | void llhttp_resume(llhttp_t* parser); 706 | 707 | /* Might be called to resume the execution after the pause in user's callback. 708 | * See `llhttp_execute()` above for details. 709 | * 710 | * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` 711 | */ 712 | LLHTTP_EXPORT 713 | void llhttp_resume_after_upgrade(llhttp_t* parser); 714 | 715 | /* Returns the latest return error */ 716 | LLHTTP_EXPORT 717 | llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); 718 | 719 | /* Returns the verbal explanation of the latest returned error. 720 | * 721 | * Note: User callback should set error reason when returning the error. See 722 | * `llhttp_set_error_reason()` for details. 723 | */ 724 | LLHTTP_EXPORT 725 | const char* llhttp_get_error_reason(const llhttp_t* parser); 726 | 727 | /* Assign verbal description to the returned error. Must be called in user 728 | * callbacks right before returning the errno. 729 | * 730 | * Note: `HPE_USER` error code might be useful in user callbacks. 731 | */ 732 | LLHTTP_EXPORT 733 | void llhttp_set_error_reason(llhttp_t* parser, const char* reason); 734 | 735 | /* Returns the pointer to the last parsed byte before the returned error. The 736 | * pointer is relative to the `data` argument of `llhttp_execute()`. 737 | * 738 | * Note: this method might be useful for counting the number of parsed bytes. 739 | */ 740 | LLHTTP_EXPORT 741 | const char* llhttp_get_error_pos(const llhttp_t* parser); 742 | 743 | /* Returns textual name of error code */ 744 | LLHTTP_EXPORT 745 | const char* llhttp_errno_name(llhttp_errno_t err); 746 | 747 | /* Returns textual name of HTTP method */ 748 | LLHTTP_EXPORT 749 | const char* llhttp_method_name(llhttp_method_t method); 750 | 751 | /* Returns textual name of HTTP status */ 752 | LLHTTP_EXPORT 753 | const char* llhttp_status_name(llhttp_status_t status); 754 | 755 | /* Enables/disables lenient header value parsing (disabled by default). 756 | * 757 | * Lenient parsing disables header value token checks, extending llhttp's 758 | * protocol support to highly non-compliant clients/server. No 759 | * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when 760 | * lenient parsing is "on". 761 | * 762 | * **(USE AT YOUR OWN RISK)** 763 | */ 764 | LLHTTP_EXPORT 765 | void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); 766 | 767 | 768 | /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and 769 | * `Content-Length` headers (disabled by default). 770 | * 771 | * Normally `llhttp` would error when `Transfer-Encoding` is present in 772 | * conjunction with `Content-Length`. This error is important to prevent HTTP 773 | * request smuggling, but may be less desirable for small number of cases 774 | * involving legacy servers. 775 | * 776 | * **(USE AT YOUR OWN RISK)** 777 | */ 778 | LLHTTP_EXPORT 779 | void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); 780 | 781 | 782 | /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 783 | * requests responses. 784 | * 785 | * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) 786 | * the HTTP request/response after the request/response with `Connection: close` 787 | * and `Content-Length`. This is important to prevent cache poisoning attacks, 788 | * but might interact badly with outdated and insecure clients. With this flag 789 | * the extra request/response will be parsed normally. 790 | * 791 | * **(USE AT YOUR OWN RISK)** 792 | */ 793 | LLHTTP_EXPORT 794 | void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); 795 | 796 | /* Enables/disables lenient handling of `Transfer-Encoding` header. 797 | * 798 | * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value 799 | * and another value after it (either in a single header or in multiple 800 | * headers whose value are internally joined using `, `). 801 | * This is mandated by the spec to reliably determine request body size and thus 802 | * avoid request smuggling. 803 | * With this flag the extra value will be parsed normally. 804 | * 805 | * **(USE AT YOUR OWN RISK)** 806 | */ 807 | LLHTTP_EXPORT 808 | void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); 809 | 810 | #ifdef __cplusplus 811 | } /* extern "C" */ 812 | #endif 813 | #endif /* INCLUDE_LLHTTP_API_H_ */ 814 | 815 | #endif /* INCLUDE_LLHTTP_H_ */ 816 | --------------------------------------------------------------------------------