summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/linux.yml23
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/windows.yml23
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitignore12
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitmodules6
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Gemfile2
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/LICENSE-MIT20
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/README.md90
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Rakefile8
-rwxr-xr-xvendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/standalone.rb23
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/thin.rb58
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/.gitignore1
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/RubyHttpParserService.java18
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ext_help.h23
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/extconf.rb24
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java502
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ruby_http_parser.c569
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/.gitkeep0
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS32
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT48
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/README.md183
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/TODO28
-rwxr-xr-xvendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/build.xml74
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c2175
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp79
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h304
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml22
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java41
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java8
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java34
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java12
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java9
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java107
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java36
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java76
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java256
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java13
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java111
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java5
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java25
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java7
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java2161
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java83
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java374
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java51
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java69
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java52
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java16
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java48
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java212
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java62
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java117
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java27
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java127
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java236
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java59
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/test.c3425
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped845
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.utf817
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb6
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb13
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb15
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb33
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/AUTHORS68
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT19
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/README.md246
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/bench.c128
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c157
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c47
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.c2440
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp111
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.h433
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/test.c4456
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/http_parser.rb.gemspec32
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http/parser.rb1
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http_parser.rb21
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/compile.rake42
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/fixtures.rake71
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/spec.rake5
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/submodules.rake7
79 files changed, 21419 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/linux.yml b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/linux.yml
new file mode 100644
index 0000000..8b27239
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/linux.yml
@@ -0,0 +1,23 @@
+name: Testing on Ubuntu
+on:
+ - push
+ - pull_request
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: [ '3.1', '3.2', '3.3', '3.4', 'head' ]
+ os: [ 'ubuntu-latest' ]
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ - name: unit testing
+ run: |
+ gem install bundler rake
+ bundle install --jobs 4 --retry 3
+ bundle exec rake
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/windows.yml b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/windows.yml
new file mode 100644
index 0000000..b7d9424
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.github/workflows/windows.yml
@@ -0,0 +1,23 @@
+name: Testing on Windows
+on:
+ - push
+ - pull_request
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: [ '3.1', '3.2', '3.3', '3.4', 'head' ]
+ os: [ 'windows-latest' ]
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ - name: unit testing
+ run: |
+ gem install bundler rake
+ bundle install --jobs 4 --retry 3
+ bundle exec rake
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitignore b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitignore
new file mode 100644
index 0000000..e887a3e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitignore
@@ -0,0 +1,12 @@
+*.bundle
+*.bundle
+*.gem
+*.jar
+*.o
+*.rbc
+*.so
+*.swp
+Gemfile.lock
+Makefile
+tags
+tmp
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitmodules b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitmodules
new file mode 100644
index 0000000..7e46b5c
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "http-parser"]
+ path = ext/ruby_http_parser/vendor/http-parser
+ url = https://github.com/nodejs/http-parser.git
+[submodule "http-parser-java"]
+ path = ext/ruby_http_parser/vendor/http-parser-java
+ url = https://github.com/tmm1/http-parser.java
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Gemfile b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Gemfile
new file mode 100644
index 0000000..851fabc
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Gemfile
@@ -0,0 +1,2 @@
+source 'https://rubygems.org'
+gemspec
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/LICENSE-MIT b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/LICENSE-MIT
new file mode 100644
index 0000000..35f0bf0
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/LICENSE-MIT
@@ -0,0 +1,20 @@
+Copyright 2009,2010 Marc-André Cournoyer <macournoyer@gmail.com>
+Copyright 2010,2011 Aman Gupta <aman@tmm1.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/README.md b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/README.md
new file mode 100644
index 0000000..e29ef2c
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/README.md
@@ -0,0 +1,90 @@
+# http_parser.rb
+
+A simple callback-based HTTP request/response parser for writing http
+servers, clients and proxies.
+
+This gem is built on top of [joyent/http-parser](https://github.com/joyent/http-parser) and its java port [http-parser/http-parser.java](https://github.com/http-parser/http-parser.java).
+
+## Supported Platforms
+
+This gem aims to work on all major Ruby platforms, including:
+
+- MRI 1.8, 1.9 and 2.0; should work on MRI 2.4+
+- Rubinius
+- JRuby
+- win32
+
+## Usage
+
+```ruby
+require "http/parser"
+
+parser = Http::Parser.new
+
+parser.on_headers_complete = proc do
+ p parser.http_version
+
+ p parser.http_method # for requests
+ p parser.request_url
+
+ p parser.status_code # for responses
+
+ p parser.headers
+end
+
+parser.on_body = proc do |chunk|
+ # One chunk of the body
+ p chunk
+end
+
+parser.on_message_complete = proc do |env|
+ # Headers and body is all parsed
+ puts "Done!"
+end
+```
+
+# Feed raw data from the socket to the parser
+`parser << raw_data`
+
+## Advanced Usage
+
+### Accept callbacks on an object
+
+```ruby
+module MyHttpConnection
+ def connection_completed
+ @parser = Http::Parser.new(self)
+ end
+
+ def receive_data(data)
+ @parser << data
+ end
+
+ def on_message_begin
+ @headers = nil
+ @body = ''
+ end
+
+ def on_headers_complete(headers)
+ @headers = headers
+ end
+
+ def on_body(chunk)
+ @body << chunk
+ end
+
+ def on_message_complete
+ p [@headers, @body]
+ end
+end
+```
+
+### Stop parsing after headers
+
+```ruby
+parser = Http::Parser.new
+parser.on_headers_complete = proc{ :stop }
+
+offset = parser << request_data
+body = request_data[offset..-1]
+```
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Rakefile b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Rakefile
new file mode 100644
index 0000000..1820ad1
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/Rakefile
@@ -0,0 +1,8 @@
+require 'bundler/gem_tasks'
+
+# default task
+task :compile => :submodules
+task :default => [:compile, :spec]
+
+# load tasks
+Dir['tasks/*.rake'].sort.each { |f| load f }
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/standalone.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/standalone.rb
new file mode 100755
index 0000000..6b4dcb6
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/standalone.rb
@@ -0,0 +1,23 @@
+#!/usr/bin/env ruby
+$:.unshift File.dirname(__FILE__) + "/../lib"
+require "rubygems"
+require "http/parser"
+require "benchmark/ips"
+
+request = <<-REQUEST
+GET / HTTP/1.1
+Host: www.example.com
+Connection: keep-alive
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: en-US,en;q=0.8
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
+
+REQUEST
+request.gsub!(/\n/m, "\r\n")
+
+Benchmark.ips do |ips|
+ ips.report("instance") { Http::Parser.new }
+ ips.report("parsing") { Http::Parser.new << request }
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/thin.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/thin.rb
new file mode 100644
index 0000000..fe0dd6d
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/bench/thin.rb
@@ -0,0 +1,58 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+require "rubygems"
+require "thin_parser"
+require "http_parser"
+require "benchmark"
+require "stringio"
+
+data = "POST /postit HTTP/1.1\r\n" +
+ "Host: localhost:3000\r\n" +
+ "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9\r\n" +
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n" +
+ "Accept-Language: en-us,en;q=0.5\r\n" +
+ "Accept-Encoding: gzip,deflate\r\n" +
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
+ "Keep-Alive: 300\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Content-Type: text/html\r\n" +
+ "Content-Length: 37\r\n" +
+ "\r\n" +
+ "name=marc&email=macournoyer@gmail.com"
+
+def thin(data)
+ env = {"rack.input" => StringIO.new}
+ Thin::HttpParser.new.execute(env, data, 0)
+ env
+end
+
+def http_parser(data)
+ body = StringIO.new
+ env = nil
+
+ parser = HTTP::RequestParser.new
+ parser.on_headers_complete = proc { |e| env = e }
+ parser.on_body = proc { |c| body << c }
+ parser << data
+
+ env["rack-input"] = body
+ env
+end
+
+# p thin(data)
+# p http_parser(data)
+
+TESTS = 30_000
+Benchmark.bmbm do |results|
+ results.report("thin:") { TESTS.times { thin data } }
+ results.report("http-parser:") { TESTS.times { http_parser data } }
+end
+
+# On my MBP core duo 2.2Ghz
+# Rehearsal ------------------------------------------------
+# thin: 1.470000 0.000000 1.470000 ( 1.474737)
+# http-parser: 1.270000 0.020000 1.290000 ( 1.292758)
+# --------------------------------------- total: 2.760000sec
+#
+# user system total real
+# thin: 1.150000 0.030000 1.180000 ( 1.173767)
+# http-parser: 1.250000 0.010000 1.260000 ( 1.263796)
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/.gitignore b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/.gitignore
new file mode 100644
index 0000000..cb899d1
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/.gitignore
@@ -0,0 +1 @@
+ryah_http_parser.*
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/RubyHttpParserService.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/RubyHttpParserService.java
new file mode 100644
index 0000000..2ea3e8e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/RubyHttpParserService.java
@@ -0,0 +1,18 @@
+import java.io.IOException;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.runtime.load.BasicLibraryService;
+
+import org.ruby_http_parser.*;
+
+public class RubyHttpParserService implements BasicLibraryService {
+ public boolean basicLoad(final Ruby runtime) throws IOException {
+ RubyModule mHTTP = runtime.defineModule("HTTP");
+ RubyClass cParser = mHTTP.defineClassUnder("Parser", runtime.getObject(), RubyHttpParser.ALLOCATOR);
+ cParser.defineAnnotatedMethods(RubyHttpParser.class);
+ cParser.defineClassUnder("Error", runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator());
+ return true;
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ext_help.h b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ext_help.h
new file mode 100644
index 0000000..f9a0bf6
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ext_help.h
@@ -0,0 +1,23 @@
+#ifndef ext_help_h
+#define ext_help_h
+
+#define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be.");
+#ifdef TypedData_Get_Struct
+#define DATA_GET_STRUCT(from,type,name) TypedData_Get_Struct(from,type,&type##_type,name)
+#else
+#define DATA_GET_STRUCT(from,type,name) Data_Get_Struct(from,type,name)
+#endif
+#define DATA_GET(from,type,name) DATA_GET_STRUCT(from,type,name); RAISE_NOT_NULL(name);
+#define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T);
+
+/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */
+#ifndef RSTRING_PTR
+#define RSTRING_PTR(s) (RSTRING(s)->ptr)
+#endif
+
+/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */
+#ifndef RSTRING_LEN
+#define RSTRING_LEN(s) (RSTRING(s)->len)
+#endif
+
+#endif
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/extconf.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/extconf.rb
new file mode 100644
index 0000000..0433f9b
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/extconf.rb
@@ -0,0 +1,24 @@
+require 'mkmf'
+
+# check out code if it hasn't been already
+if Dir[File.expand_path('../vendor/http-parser/*', __FILE__)].empty?
+ Dir.chdir(File.expand_path('../../../', __FILE__)) do
+ xsystem 'git submodule init'
+ xsystem 'git submodule update'
+ end
+end
+
+# mongrel and http-parser both define http_parser_(init|execute), so we
+# rename functions in http-parser before using them.
+vendor_dir = File.expand_path('../vendor/http-parser/', __FILE__)
+src_dir = File.expand_path('../', __FILE__)
+%w[ http_parser.c http_parser.h ].each do |file|
+ File.open(File.join(src_dir, "ryah_#{file}"), 'w'){ |f|
+ f.write File.read(File.join(vendor_dir, file)).gsub('http_parser', 'ryah_http_parser')
+ }
+end
+
+$CFLAGS << " -I\"#{src_dir}\""
+
+dir_config("ruby_http_parser")
+create_makefile("ruby_http_parser")
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java
new file mode 100644
index 0000000..1b97269
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java
@@ -0,0 +1,502 @@
+package org.ruby_http_parser;
+
+import http_parser.HTTPException;
+import http_parser.HTTPMethod;
+import http_parser.HTTPParser;
+import http_parser.lolevel.HTTPCallback;
+import http_parser.lolevel.HTTPDataCallback;
+import http_parser.lolevel.ParserSettings;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jcodings.Encoding;
+import org.jcodings.specific.UTF8Encoding;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
+import org.jruby.RubyHash;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.RubySymbol;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+public class RubyHttpParser extends RubyObject {
+
+ @JRubyMethod(name = "strict?", module = true)
+ public static IRubyObject strict(IRubyObject recv) {
+ return recv.getRuntime().newBoolean(true);
+ }
+
+ public static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
+ return new RubyHttpParser(runtime, klass);
+ }
+ };
+
+ byte[] fetchBytes(ByteBuffer b, int pos, int len) {
+ byte[] by = new byte[len];
+ int saved = b.position();
+ b.position(pos);
+ b.get(by);
+ b.position(saved);
+ return by;
+ }
+
+ public class StopException extends RuntimeException {
+ }
+
+ private Ruby runtime;
+ private HTTPParser parser;
+ private ParserSettings settings;
+
+ private RubyClass eParserError;
+
+ private RubyHash headers;
+
+ private IRubyObject on_message_begin;
+ private IRubyObject on_headers_complete;
+ private IRubyObject on_body;
+ private IRubyObject on_message_complete;
+
+ private IRubyObject requestUrl;
+ private IRubyObject requestPath;
+ private IRubyObject queryString;
+ private IRubyObject fragment;
+
+ private IRubyObject header_value_type;
+ private IRubyObject upgradeData;
+
+ private IRubyObject callback_object;
+
+ private boolean completed;
+
+ private byte[] _current_header;
+ private byte[] _last_header;
+
+ private static final Encoding UTF8 = UTF8Encoding.INSTANCE;
+
+ private static final List<String> VALUE_TYPES = new ArrayList<String>(
+ Arrays.asList("mixed", "arrays", "strings")
+ );
+
+ public RubyHttpParser(final Ruby runtime, RubyClass clazz) {
+ super(runtime, clazz);
+
+ this.runtime = runtime;
+ this.eParserError = (RubyClass) runtime.getModule("HTTP").getClass("Parser").getConstant("Error");
+
+ this.on_message_begin = null;
+ this.on_headers_complete = null;
+ this.on_body = null;
+ this.on_message_complete = null;
+
+ this.callback_object = null;
+
+ this.completed = false;
+
+ this.header_value_type = runtime.getModule("HTTP").getClass("Parser")
+ .getInstanceVariable("@default_header_value_type");
+
+ initSettings();
+ init();
+ }
+
+ private void initSettings() {
+ this.settings = new ParserSettings();
+
+ this.settings.on_url = new HTTPDataCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
+ byte[] data = fetchBytes(buf, pos, len);
+ if (runtime.is1_9() || runtime.is2_0()) {
+ ((RubyString) requestUrl).cat(data, 0, data.length, UTF8);
+ } else {
+ ((RubyString) requestUrl).cat(data);
+ }
+ return 0;
+ }
+ };
+
+ this.settings.on_header_field = new HTTPDataCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
+ byte[] data = fetchBytes(buf, pos, len);
+
+ if (_current_header == null)
+ _current_header = data;
+ else {
+ byte[] tmp = new byte[_current_header.length + data.length];
+ System.arraycopy(_current_header, 0, tmp, 0, _current_header.length);
+ System.arraycopy(data, 0, tmp, _current_header.length, data.length);
+ _current_header = tmp;
+ }
+
+ return 0;
+ }
+ };
+ final RubySymbol arraysSym = runtime.newSymbol("arrays");
+ final RubySymbol mixedSym = runtime.newSymbol("mixed");
+ final RubySymbol stopSym = runtime.newSymbol("stop");
+ final RubySymbol resetSym = runtime.newSymbol("reset");
+ this.settings.on_header_value = new HTTPDataCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
+ byte[] data = fetchBytes(buf, pos, len);
+ ThreadContext context = headers.getRuntime().getCurrentContext();
+ IRubyObject key, val;
+ int new_field = 0;
+
+ if (_current_header != null) {
+ new_field = 1;
+ _last_header = _current_header;
+ _current_header = null;
+ }
+
+ key = RubyString.newString(runtime, new ByteList(_last_header, UTF8, false));
+ val = headers.op_aref(context, key);
+
+ if (new_field == 1) {
+ if (val.isNil()) {
+ if (header_value_type == arraysSym) {
+ headers.op_aset(context, key,
+ RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10, UTF8)));
+ } else {
+ headers.op_aset(context, key, RubyString.newStringLight(runtime, 10, UTF8));
+ }
+ } else {
+ if (header_value_type == mixedSym) {
+ if (val instanceof RubyString) {
+ headers.op_aset(context, key,
+ RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10, UTF8)));
+ } else {
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
+ }
+ } else if (header_value_type == arraysSym) {
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
+ } else {
+ if (runtime.is1_9() || runtime.is2_0()) {
+ ((RubyString) val).cat(',', UTF8).cat(' ', UTF8);
+ } else {
+ ((RubyString) val).cat(',').cat(' ');
+ }
+ }
+ }
+ val = headers.op_aref(context, key);
+ }
+
+ if (val instanceof RubyArray) {
+ val = ((RubyArray) val).entry(-1);
+ }
+
+ if (runtime.is1_9() || runtime.is2_0()) {
+ ((RubyString) val).cat(data, 0, data.length, UTF8);
+ } else {
+ ((RubyString) val).cat(data);
+ }
+
+ return 0;
+ }
+ };
+
+ this.settings.on_message_begin = new HTTPCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p) {
+ headers = new RubyHash(runtime);
+
+ if (runtime.is1_9() || runtime.is2_0()) {
+ requestUrl = RubyString.newEmptyString(runtime, UTF8);
+ requestPath = RubyString.newEmptyString(runtime, UTF8);
+ queryString = RubyString.newEmptyString(runtime, UTF8);
+ fragment = RubyString.newEmptyString(runtime, UTF8);
+ upgradeData = RubyString.newEmptyString(runtime, UTF8);
+ } else {
+ requestUrl = RubyString.newEmptyString(runtime);
+ requestPath = RubyString.newEmptyString(runtime);
+ queryString = RubyString.newEmptyString(runtime);
+ fragment = RubyString.newEmptyString(runtime);
+ upgradeData = RubyString.newEmptyString(runtime);
+ }
+
+ IRubyObject ret = runtime.getNil();
+
+ if (callback_object != null) {
+ if (((RubyObject) callback_object).respondsTo("on_message_begin")) {
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
+ ret = callback_object.callMethod(context, "on_message_begin");
+ }
+ } else if (on_message_begin != null) {
+ ThreadContext context = on_message_begin.getRuntime().getCurrentContext();
+ ret = on_message_begin.callMethod(context, "call");
+ }
+
+ if (ret == stopSym) {
+ throw new StopException();
+ } else {
+ return 0;
+ }
+ }
+ };
+ this.settings.on_message_complete = new HTTPCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p) {
+ IRubyObject ret = runtime.getNil();
+
+ completed = true;
+
+ if (callback_object != null) {
+ if (((RubyObject) callback_object).respondsTo("on_message_complete")) {
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
+ ret = callback_object.callMethod(context, "on_message_complete");
+ }
+ } else if (on_message_complete != null) {
+ ThreadContext context = on_message_complete.getRuntime().getCurrentContext();
+ ret = on_message_complete.callMethod(context, "call");
+ }
+
+ if (ret == stopSym) {
+ throw new StopException();
+ } else {
+ return 0;
+ }
+ }
+ };
+ this.settings.on_headers_complete = new HTTPCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p) {
+ IRubyObject ret = runtime.getNil();
+
+ if (callback_object != null) {
+ if (((RubyObject) callback_object).respondsTo("on_headers_complete")) {
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
+ ret = callback_object.callMethod(context, "on_headers_complete", headers);
+ }
+ } else if (on_headers_complete != null) {
+ ThreadContext context = on_headers_complete.getRuntime().getCurrentContext();
+ ret = on_headers_complete.callMethod(context, "call", headers);
+ }
+
+ if (ret == stopSym) {
+ throw new StopException();
+ } else if (ret == resetSym) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ };
+ this.settings.on_body = new HTTPDataCallback() {
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
+ IRubyObject ret = runtime.getNil();
+ byte[] data = fetchBytes(buf, pos, len);
+
+ if (callback_object != null) {
+ if (((RubyObject) callback_object).respondsTo("on_body")) {
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
+ ret = callback_object.callMethod(context, "on_body",
+ RubyString.newString(runtime, new ByteList(data, UTF8, false)));
+ }
+ } else if (on_body != null) {
+ ThreadContext context = on_body.getRuntime().getCurrentContext();
+ ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8, false)));
+ }
+
+ if (ret == stopSym) {
+ throw new StopException();
+ } else {
+ return 0;
+ }
+ }
+ };
+ }
+
+ private void init() {
+ this.parser = new HTTPParser();
+ this.parser.HTTP_PARSER_STRICT = true;
+ this.headers = null;
+
+ this.requestUrl = runtime.getNil();
+ this.requestPath = runtime.getNil();
+ this.queryString = runtime.getNil();
+ this.fragment = runtime.getNil();
+
+ this.upgradeData = runtime.getNil();
+ }
+
+ @JRubyMethod(name = "initialize")
+ public IRubyObject initialize() {
+ return this;
+ }
+
+ @JRubyMethod(name = "initialize")
+ public IRubyObject initialize(IRubyObject arg) {
+ callback_object = arg;
+ return initialize();
+ }
+
+ @JRubyMethod(name = "initialize")
+ public IRubyObject initialize(IRubyObject arg, IRubyObject arg2) {
+ header_value_type = arg2;
+ return initialize(arg);
+ }
+
+ @JRubyMethod(name = "on_message_begin=")
+ public IRubyObject set_on_message_begin(IRubyObject cb) {
+ on_message_begin = cb;
+ return cb;
+ }
+
+ @JRubyMethod(name = "on_headers_complete=")
+ public IRubyObject set_on_headers_complete(IRubyObject cb) {
+ on_headers_complete = cb;
+ return cb;
+ }
+
+ @JRubyMethod(name = "on_body=")
+ public IRubyObject set_on_body(IRubyObject cb) {
+ on_body = cb;
+ return cb;
+ }
+
+ @JRubyMethod(name = "on_message_complete=")
+ public IRubyObject set_on_message_complete(IRubyObject cb) {
+ on_message_complete = cb;
+ return cb;
+ }
+
+ @JRubyMethod(name = "<<")
+ public IRubyObject execute(IRubyObject data) {
+ RubyString str = (RubyString) data;
+ ByteList byteList = str.getByteList();
+ ByteBuffer buf = ByteBuffer.wrap(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
+ boolean stopped = false;
+
+ try {
+ this.parser.execute(this.settings, buf);
+ } catch (HTTPException e) {
+ throw new RaiseException(runtime, eParserError, e.getMessage(), true);
+ } catch (StopException e) {
+ stopped = true;
+ }
+
+ if (parser.getUpgrade()) {
+ byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position());
+ if (runtime.is1_9() || runtime.is2_0()) {
+ ((RubyString) upgradeData).cat(upData, 0, upData.length, UTF8);
+ } else {
+ ((RubyString) upgradeData).cat(upData);
+ }
+ } else if (buf.hasRemaining() && !completed) {
+ if (!stopped)
+ throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true);
+ }
+
+ return RubyNumeric.int2fix(runtime, buf.position());
+ }
+
+ @JRubyMethod(name = "keep_alive?")
+ public IRubyObject shouldKeepAlive() {
+ return runtime.newBoolean(parser.shouldKeepAlive());
+ }
+
+ @JRubyMethod(name = "upgrade?")
+ public IRubyObject shouldUpgrade() {
+ return runtime.newBoolean(parser.getUpgrade());
+ }
+
+ @JRubyMethod(name = "http_major")
+ public IRubyObject httpMajor() {
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
+ return runtime.getNil();
+ else
+ return RubyNumeric.int2fix(runtime, parser.getMajor());
+ }
+
+ @JRubyMethod(name = "http_minor")
+ public IRubyObject httpMinor() {
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
+ return runtime.getNil();
+ else
+ return RubyNumeric.int2fix(runtime, parser.getMinor());
+ }
+
+ @JRubyMethod(name = "http_version")
+ public IRubyObject httpVersion() {
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
+ return runtime.getNil();
+ else
+ return runtime.newArray(httpMajor(), httpMinor());
+ }
+
+ @JRubyMethod(name = "http_method")
+ public IRubyObject httpMethod() {
+ HTTPMethod method = parser.getHTTPMethod();
+ if (method != null)
+ return runtime.newString(new String(method.bytes));
+ else
+ return runtime.getNil();
+ }
+
+ @JRubyMethod(name = "status_code")
+ public IRubyObject statusCode() {
+ int code = parser.getStatusCode();
+ if (code != 0)
+ return RubyNumeric.int2fix(runtime, code);
+ else
+ return runtime.getNil();
+ }
+
+ @JRubyMethod(name = "headers")
+ public IRubyObject getHeaders() {
+ return headers == null ? runtime.getNil() : headers;
+ }
+
+ @JRubyMethod(name = "request_url")
+ public IRubyObject getRequestUrl() {
+ return requestUrl == null ? runtime.getNil() : requestUrl;
+ }
+
+ @JRubyMethod(name = "request_path")
+ public IRubyObject getRequestPath() {
+ return requestPath == null ? runtime.getNil() : requestPath;
+ }
+
+ @JRubyMethod(name = "query_string")
+ public IRubyObject getQueryString() {
+ return queryString == null ? runtime.getNil() : queryString;
+ }
+
+ @JRubyMethod(name = "fragment")
+ public IRubyObject getFragment() {
+ return fragment == null ? runtime.getNil() : fragment;
+ }
+
+ @JRubyMethod(name = "header_value_type")
+ public IRubyObject getHeaderValueType() {
+ return header_value_type == null ? runtime.getNil() : header_value_type;
+ }
+
+ @JRubyMethod(name = "header_value_type=")
+ public IRubyObject set_header_value_type(IRubyObject val) {
+ String valString = val.toString();
+ if (!VALUE_TYPES.contains(valString)) {
+ throw runtime.newArgumentError("Invalid header value type");
+ }
+ header_value_type = val;
+ return val;
+ }
+
+ @JRubyMethod(name = "upgrade_data")
+ public IRubyObject upgradeData() {
+ return upgradeData == null ? runtime.getNil() : upgradeData;
+ }
+
+ @JRubyMethod(name = "reset!")
+ public IRubyObject reset() {
+ init();
+ return runtime.getTrue();
+ }
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ruby_http_parser.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ruby_http_parser.c
new file mode 100644
index 0000000..c506e22
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/ruby_http_parser.c
@@ -0,0 +1,569 @@
+#include "ruby.h"
+#include "ext_help.h"
+#include "ryah_http_parser.h"
+
+#define GET_WRAPPER(N, from) ParserWrapper *N = (ParserWrapper *)(from)->data;
+#define HASH_CAT(h, k, ptr, len) \
+ do { \
+ VALUE __v = rb_hash_aref(h, k); \
+ if (__v != Qnil) { \
+ rb_str_cat(__v, ptr, len); \
+ } else { \
+ rb_hash_aset(h, k, rb_str_new(ptr, len)); \
+ } \
+ } while(0)
+
+typedef struct ParserWrapper {
+ ryah_http_parser parser;
+
+ VALUE status;
+ VALUE request_url;
+
+ VALUE headers;
+
+ VALUE upgrade_data;
+
+ VALUE on_message_begin;
+ VALUE on_headers_complete;
+ VALUE on_body;
+ VALUE on_message_complete;
+
+ VALUE callback_object;
+ VALUE stopped;
+ VALUE completed;
+
+ VALUE header_value_type;
+
+ VALUE last_field_name;
+ VALUE curr_field_name;
+
+ enum ryah_http_parser_type type;
+} ParserWrapper;
+
+void ParserWrapper_init(ParserWrapper *wrapper) {
+ ryah_http_parser_init(&wrapper->parser, wrapper->type);
+ wrapper->parser.status_code = 0;
+ wrapper->parser.http_major = 0;
+ wrapper->parser.http_minor = 0;
+
+ wrapper->status = Qnil;
+ wrapper->request_url = Qnil;
+
+ wrapper->upgrade_data = Qnil;
+
+ wrapper->headers = Qnil;
+ wrapper->completed = Qfalse;
+
+ wrapper->last_field_name = Qnil;
+ wrapper->curr_field_name = Qnil;
+}
+
+void ParserWrapper_mark(void *data) {
+ if(data) {
+ ParserWrapper *wrapper = (ParserWrapper *) data;
+ rb_gc_mark_maybe(wrapper->status);
+ rb_gc_mark_maybe(wrapper->request_url);
+ rb_gc_mark_maybe(wrapper->upgrade_data);
+ rb_gc_mark_maybe(wrapper->headers);
+ rb_gc_mark_maybe(wrapper->on_message_begin);
+ rb_gc_mark_maybe(wrapper->on_headers_complete);
+ rb_gc_mark_maybe(wrapper->on_body);
+ rb_gc_mark_maybe(wrapper->on_message_complete);
+ rb_gc_mark_maybe(wrapper->callback_object);
+ rb_gc_mark_maybe(wrapper->last_field_name);
+ rb_gc_mark_maybe(wrapper->curr_field_name);
+ }
+}
+
+void ParserWrapper_free(void *data) {
+ if(data) {
+ free(data);
+ }
+}
+
+static const rb_data_type_t ParserWrapper_type = {
+ "ParserWrapper",
+ {
+ ParserWrapper_mark,
+ ParserWrapper_free,
+ },
+};
+
+static VALUE cParser;
+static VALUE cRequestParser;
+static VALUE cResponseParser;
+
+static VALUE eParserError;
+
+static ID Icall;
+static ID Ion_message_begin;
+static ID Ion_headers_complete;
+static ID Ion_body;
+static ID Ion_message_complete;
+
+static VALUE Sstop;
+static VALUE Sreset;
+static VALUE Sarrays;
+static VALUE Sstrings;
+static VALUE Smixed;
+
+/** Callbacks **/
+
+int on_message_begin(ryah_http_parser *parser) {
+ GET_WRAPPER(wrapper, parser);
+
+ wrapper->status = rb_str_new2("");
+ wrapper->request_url = rb_str_new2("");
+ wrapper->headers = rb_hash_new();
+ wrapper->upgrade_data = rb_str_new2("");
+
+ VALUE ret = Qnil;
+
+ if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_begin)) {
+ ret = rb_funcall(wrapper->callback_object, Ion_message_begin, 0);
+ } else if (wrapper->on_message_begin != Qnil) {
+ ret = rb_funcall(wrapper->on_message_begin, Icall, 0);
+ }
+
+ if (ret == Sstop) {
+ wrapper->stopped = Qtrue;
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+int on_status(ryah_http_parser *parser, const char *at, size_t length) {
+ GET_WRAPPER(wrapper, parser);
+
+ if (at && length) {
+ if (wrapper->status == Qnil) {
+ wrapper->status = rb_str_new(at, length);
+ } else {
+ rb_str_cat(wrapper->status, at, length);
+ }
+ }
+ return 0;
+}
+
+int on_url(ryah_http_parser *parser, const char *at, size_t length) {
+ GET_WRAPPER(wrapper, parser);
+ if (at && length) {
+ if (wrapper->request_url == Qnil) {
+ wrapper->request_url = rb_str_new(at, length);
+ } else {
+ rb_str_cat(wrapper->request_url, at, length);
+ }
+ }
+ return 0;
+}
+
+int on_header_field(ryah_http_parser *parser, const char *at, size_t length) {
+ GET_WRAPPER(wrapper, parser);
+
+ if (wrapper->curr_field_name == Qnil) {
+ wrapper->last_field_name = Qnil;
+ wrapper->curr_field_name = rb_str_new(at, length);
+ } else {
+ rb_str_cat(wrapper->curr_field_name, at, length);
+ }
+ return 0;
+}
+
+int on_header_value(ryah_http_parser *parser, const char *at, size_t length) {
+ GET_WRAPPER(wrapper, parser);
+
+ int new_field = 0;
+ VALUE current_value;
+
+ if (wrapper->last_field_name == Qnil) {
+ new_field = 1;
+ wrapper->last_field_name = wrapper->curr_field_name;
+ wrapper->curr_field_name = Qnil;
+ }
+
+ current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name);
+
+ if (new_field == 1) {
+ if (current_value == Qnil) {
+ if (wrapper->header_value_type == Sarrays) {
+ rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(1, rb_str_new2("")));
+ } else {
+ rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_str_new2(""));
+ }
+ } else {
+ if (wrapper->header_value_type == Smixed) {
+ if (TYPE(current_value) == T_STRING) {
+ rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(2, current_value, rb_str_new2("")));
+ } else {
+ rb_ary_push(current_value, rb_str_new2(""));
+ }
+ } else if (wrapper->header_value_type == Sarrays) {
+ rb_ary_push(current_value, rb_str_new2(""));
+ } else {
+ rb_str_cat(current_value, ", ", 2);
+ }
+ }
+ current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name);
+ }
+
+ if (TYPE(current_value) == T_ARRAY) {
+ current_value = rb_ary_entry(current_value, -1);
+ }
+
+ rb_str_cat(current_value, at, length);
+
+ return 0;
+}
+
+int on_headers_complete(ryah_http_parser *parser) {
+ GET_WRAPPER(wrapper, parser);
+
+ VALUE ret = Qnil;
+
+ if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_headers_complete)) {
+ ret = rb_funcall(wrapper->callback_object, Ion_headers_complete, 1, wrapper->headers);
+ } else if (wrapper->on_headers_complete != Qnil) {
+ ret = rb_funcall(wrapper->on_headers_complete, Icall, 1, wrapper->headers);
+ }
+
+ if (ret == Sstop) {
+ wrapper->stopped = Qtrue;
+ return -1;
+ } else if (ret == Sreset){
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int on_body(ryah_http_parser *parser, const char *at, size_t length) {
+ GET_WRAPPER(wrapper, parser);
+
+ VALUE ret = Qnil;
+
+ if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_body)) {
+ ret = rb_funcall(wrapper->callback_object, Ion_body, 1, rb_str_new(at, length));
+ } else if (wrapper->on_body != Qnil) {
+ ret = rb_funcall(wrapper->on_body, Icall, 1, rb_str_new(at, length));
+ }
+
+ if (ret == Sstop) {
+ wrapper->stopped = Qtrue;
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+int on_message_complete(ryah_http_parser *parser) {
+ GET_WRAPPER(wrapper, parser);
+
+ VALUE ret = Qnil;
+ wrapper->completed = Qtrue;
+
+ if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_complete)) {
+ ret = rb_funcall(wrapper->callback_object, Ion_message_complete, 0);
+ } else if (wrapper->on_message_complete != Qnil) {
+ ret = rb_funcall(wrapper->on_message_complete, Icall, 0);
+ }
+
+ if (ret == Sstop) {
+ wrapper->stopped = Qtrue;
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static ryah_http_parser_settings settings = {
+ .on_message_begin = on_message_begin,
+ .on_status = on_status,
+ .on_url = on_url,
+ .on_header_field = on_header_field,
+ .on_header_value = on_header_value,
+ .on_headers_complete = on_headers_complete,
+ .on_body = on_body,
+ .on_message_complete = on_message_complete
+};
+
+VALUE Parser_alloc_by_type(VALUE klass, enum ryah_http_parser_type type) {
+ ParserWrapper *wrapper = ALLOC_N(ParserWrapper, 1);
+ wrapper->type = type;
+ wrapper->parser.data = wrapper;
+
+ wrapper->on_message_begin = Qnil;
+ wrapper->on_headers_complete = Qnil;
+ wrapper->on_body = Qnil;
+ wrapper->on_message_complete = Qnil;
+
+ wrapper->callback_object = Qnil;
+
+ ParserWrapper_init(wrapper);
+
+ return TypedData_Wrap_Struct(klass, &ParserWrapper_type, wrapper);
+}
+
+VALUE Parser_alloc(VALUE klass) {
+ return Parser_alloc_by_type(klass, HTTP_BOTH);
+}
+
+VALUE RequestParser_alloc(VALUE klass) {
+ return Parser_alloc_by_type(klass, HTTP_REQUEST);
+}
+
+VALUE ResponseParser_alloc(VALUE klass) {
+ return Parser_alloc_by_type(klass, HTTP_RESPONSE);
+}
+
+VALUE Parser_strict_p(VALUE klass) {
+ return HTTP_PARSER_STRICT == 1 ? Qtrue : Qfalse;
+}
+
+VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ VALUE default_header_value_type = Qnil;
+
+ if (argc > 0 && RB_TYPE_P(argv[argc-1], T_HASH)) {
+ ID keyword_ids[1];
+ keyword_ids[0] = rb_intern("default_header_value_type");
+ rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, &default_header_value_type);
+ if (default_header_value_type == Qundef) {
+ default_header_value_type = Qnil;
+ }
+ --argc;
+ }
+
+ if (argc == 1) {
+ wrapper->callback_object = argv[0];
+ }
+
+ if (argc == 2) {
+ wrapper->callback_object = argv[0];
+ default_header_value_type = argv[1];
+ }
+
+ if (default_header_value_type == Qnil) {
+ wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type");
+ } else {
+ wrapper->header_value_type = default_header_value_type;
+ }
+
+ return self;
+}
+
+VALUE Parser_execute(VALUE self, VALUE data) {
+ ParserWrapper *wrapper = NULL;
+
+ Check_Type(data, T_STRING);
+ char *ptr = RSTRING_PTR(data);
+ long len = RSTRING_LEN(data);
+
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ wrapper->stopped = Qfalse;
+ size_t nparsed = ryah_http_parser_execute(&wrapper->parser, &settings, ptr, len);
+
+ if (wrapper->parser.upgrade) {
+ if (RTEST(wrapper->stopped) && !RTEST(wrapper->completed))
+ nparsed += 1;
+
+ if (nparsed < len)
+ rb_str_cat(wrapper->upgrade_data, ptr + nparsed, len - nparsed);
+
+ } else if (nparsed != (size_t)len) {
+ if (!RTEST(wrapper->stopped) && !RTEST(wrapper->completed))
+ rb_raise(eParserError, "Could not parse data entirely (%zu != %zu)", nparsed, len);
+ else
+ nparsed += 1; // error states fail on the current character
+ }
+
+ return INT2FIX(nparsed);
+}
+
+VALUE Parser_set_on_message_begin(VALUE self, VALUE callback) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ wrapper->on_message_begin = callback;
+ return callback;
+}
+
+VALUE Parser_set_on_headers_complete(VALUE self, VALUE callback) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ wrapper->on_headers_complete = callback;
+ return callback;
+}
+
+VALUE Parser_set_on_body(VALUE self, VALUE callback) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ wrapper->on_body = callback;
+ return callback;
+}
+
+VALUE Parser_set_on_message_complete(VALUE self, VALUE callback) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ wrapper->on_message_complete = callback;
+ return callback;
+}
+
+VALUE Parser_keep_alive_p(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ return http_should_keep_alive(&wrapper->parser) == 1 ? Qtrue : Qfalse;
+}
+
+VALUE Parser_upgrade_p(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ return wrapper->parser.upgrade ? Qtrue : Qfalse;
+}
+
+VALUE Parser_http_version(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0)
+ return Qnil;
+ else
+ return rb_ary_new3(2, INT2FIX(wrapper->parser.http_major), INT2FIX(wrapper->parser.http_minor));
+}
+
+VALUE Parser_http_major(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0)
+ return Qnil;
+ else
+ return INT2FIX(wrapper->parser.http_major);
+}
+
+VALUE Parser_http_minor(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0)
+ return Qnil;
+ else
+ return INT2FIX(wrapper->parser.http_minor);
+}
+
+VALUE Parser_http_method(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ if (wrapper->parser.type == HTTP_REQUEST)
+ return rb_str_new2(http_method_str(wrapper->parser.method));
+ else
+ return Qnil;
+}
+
+VALUE Parser_status_code(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ if (wrapper->parser.status_code)
+ return INT2FIX(wrapper->parser.status_code);
+ else
+ return Qnil;
+}
+
+#define DEFINE_GETTER(name) \
+ VALUE Parser_##name(VALUE self) { \
+ ParserWrapper *wrapper = NULL; \
+ DATA_GET(self, ParserWrapper, wrapper); \
+ return wrapper->name; \
+ }
+
+DEFINE_GETTER(status);
+DEFINE_GETTER(request_url);
+DEFINE_GETTER(headers);
+DEFINE_GETTER(upgrade_data);
+DEFINE_GETTER(header_value_type);
+
+VALUE Parser_set_header_value_type(VALUE self, VALUE val) {
+ if (val != Sarrays && val != Sstrings && val != Smixed) {
+ rb_raise(rb_eArgError, "Invalid header value type");
+ }
+
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+ wrapper->header_value_type = val;
+ return wrapper->header_value_type;
+}
+
+VALUE Parser_reset(VALUE self) {
+ ParserWrapper *wrapper = NULL;
+ DATA_GET(self, ParserWrapper, wrapper);
+
+ ParserWrapper_init(wrapper);
+
+ return Qtrue;
+}
+
+void Init_ruby_http_parser() {
+#ifdef HAVE_RB_EXT_RACTOR_SAFE
+ rb_ext_ractor_safe(true);
+#endif
+
+ VALUE mHTTP = rb_define_module("HTTP");
+ cParser = rb_define_class_under(mHTTP, "Parser", rb_cObject);
+ cRequestParser = rb_define_class_under(mHTTP, "RequestParser", cParser);
+ cResponseParser = rb_define_class_under(mHTTP, "ResponseParser", cParser);
+
+ eParserError = rb_define_class_under(cParser, "Error", rb_eIOError);
+ Icall = rb_intern("call");
+ Ion_message_begin = rb_intern("on_message_begin");
+ Ion_headers_complete = rb_intern("on_headers_complete");
+ Ion_body = rb_intern("on_body");
+ Ion_message_complete = rb_intern("on_message_complete");
+ Sstop = ID2SYM(rb_intern("stop"));
+ Sreset = ID2SYM(rb_intern("reset"));
+
+ Sarrays = ID2SYM(rb_intern("arrays"));
+ Sstrings = ID2SYM(rb_intern("strings"));
+ Smixed = ID2SYM(rb_intern("mixed"));
+
+ rb_define_alloc_func(cParser, Parser_alloc);
+ rb_define_alloc_func(cRequestParser, RequestParser_alloc);
+ rb_define_alloc_func(cResponseParser, ResponseParser_alloc);
+
+ rb_define_singleton_method(cParser, "strict?", Parser_strict_p, 0);
+ rb_define_method(cParser, "initialize", Parser_initialize, -1);
+
+ rb_define_method(cParser, "on_message_begin=", Parser_set_on_message_begin, 1);
+ rb_define_method(cParser, "on_headers_complete=", Parser_set_on_headers_complete, 1);
+ rb_define_method(cParser, "on_body=", Parser_set_on_body, 1);
+ rb_define_method(cParser, "on_message_complete=", Parser_set_on_message_complete, 1);
+ rb_define_method(cParser, "<<", Parser_execute, 1);
+
+ rb_define_method(cParser, "keep_alive?", Parser_keep_alive_p, 0);
+ rb_define_method(cParser, "upgrade?", Parser_upgrade_p, 0);
+
+ rb_define_method(cParser, "http_version", Parser_http_version, 0);
+ rb_define_method(cParser, "http_major", Parser_http_major, 0);
+ rb_define_method(cParser, "http_minor", Parser_http_minor, 0);
+
+ rb_define_method(cParser, "http_method", Parser_http_method, 0);
+ rb_define_method(cParser, "status_code", Parser_status_code, 0);
+
+ rb_define_method(cParser, "status", Parser_status, 0);
+ rb_define_method(cParser, "request_url", Parser_request_url, 0);
+ rb_define_method(cParser, "headers", Parser_headers, 0);
+ rb_define_method(cParser, "upgrade_data", Parser_upgrade_data, 0);
+ rb_define_method(cParser, "header_value_type", Parser_header_value_type, 0);
+ rb_define_method(cParser, "header_value_type=", Parser_set_header_value_type, 1);
+
+ rb_define_method(cParser, "reset!", Parser_reset, 0);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/.gitkeep b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/.gitkeep
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS
new file mode 100644
index 0000000..abe99de
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS
@@ -0,0 +1,32 @@
+# Authors ordered by first contribution.
+Ryan Dahl <ry@tinyclouds.org>
+Jeremy Hinegardner <jeremy@hinegardner.org>
+Sergey Shepelev <temotor@gmail.com>
+Joe Damato <ice799@gmail.com>
+tomika <tomika_nospam@freemail.hu>
+Phoenix Sol <phoenix@burninglabs.com>
+Cliff Frey <cliff@meraki.com>
+Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
+Santiago Gala <sgala@apache.org>
+Tim Becker <tim.becker@syngenio.de>
+Jeff Terrace <jterrace@gmail.com>
+Ben Noordhuis <info@bnoordhuis.nl>
+Nathan Rajlich <nathan@tootallnate.net>
+Mark Nottingham <mnot@mnot.net>
+Aman Gupta <aman@tmm1.net>
+Tim Becker <tim.becker@kuriositaet.de>
+Sean Cunningham <sean.cunningham@mandiant.com>
+Peter Griess <pg@std.in>
+Salman Haq <salman.haq@asti-usa.com>
+Cliff Frey <clifffrey@gmail.com>
+Jon Kolb <jon@b0g.us>
+Fouad Mardini <f.mardini@gmail.com>
+Paul Querna <pquerna@apache.org>
+Felix Geisendörfer <felix@debuggable.com>
+koichik <koichik@improvement.jp>
+Andre Caron <andre.l.caron@gmail.com>
+Ivo Raisr <ivosh@ivosh.net>
+James McLaughlin <jamie@lacewing-project.org>
+David Gwynne <loki@animata.net>
+LE ROUX Thomas <thomas@procheo.fr>
+Randy Rizun <rrizun@ortivawireless.com>
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT
new file mode 100644
index 0000000..a0ae8dc
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT
@@ -0,0 +1,48 @@
+Copyright 2010 Tim Becker <tim.becker@kuriositaet.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+--- END OF LICENSE
+
+This code mainly based on code with the following license:
+
+
+http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
+Igor Sysoev.
+
+Additional changes are licensed under the same terms as NGINX and
+copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/README.md b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/README.md
new file mode 100644
index 0000000..0a6a432
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/README.md
@@ -0,0 +1,183 @@
+HTTP Parser
+===========
+
+This is a parser for HTTP written in Java, based quite heavily on
+the Ryan Dahl's C Version: `http-parser` available here:
+
+ http://github.com/ry/http-parser
+
+It parses both requests and responses. The parser is designed to be used
+in performance HTTP applications.
+
+Features:
+
+ * No dependencies (probably won't be able to keep it up)
+ * Handles persistent streams (keep-alive).
+ * Decodes chunked encoding.
+ * Upgrade support
+
+The parser extracts the following information from HTTP messages:
+
+ * Header fields and values
+ * Content-Length
+ * Request method
+ * Response status code
+ * Transfer-Encoding
+ * HTTP version
+ * Request URL
+ * Message body
+
+Building
+--------
+
+use `ant compile|test|jar`
+
+Usage
+-----
+
+ TODO: in the present form, usage of the Java version of the parser
+ shouldn't be too difficult to figure out for someone familiar with the
+ C version.
+
+ More documentation will follow shortly, in case you're looking for an
+ easy to use http library, this lib is probably not what you are
+ looking for anyway ...
+
+ All text after this paragraph (and most of the text above it) are from
+ the original C version of the README and are currently only here for
+ reference. In case you encounter any difficulties, find bugs, need
+ help or have suggestions, feel free to contact me at
+ (tim.becker@kuriositaet.de).
+
+
+One `http_parser` object is used per TCP connection. Initialize the struct
+using `http_parser_init()` and set the callbacks. That might look something
+like this for a request parser:
+
+ http_parser_settings settings;
+ settings.on_path = my_path_callback;
+ settings.on_header_field = my_header_field_callback;
+ /* ... */
+
+ http_parser *parser = malloc(sizeof(http_parser));
+ http_parser_init(parser, HTTP_REQUEST);
+ parser->data = my_socket;
+
+When data is received on the socket execute the parser and check for errors.
+
+ size_t len = 80*1024, nparsed;
+ char buf[len];
+ ssize_t recved;
+
+ recved = recv(fd, buf, len, 0);
+
+ if (recved < 0) {
+ /* Handle error. */
+ }
+
+ /* Start up / continue the parser.
+ * Note we pass recved==0 to signal that EOF has been recieved.
+ */
+ nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+ if (parser->upgrade) {
+ /* handle new protocol */
+ } else if (nparsed != recved) {
+ /* Handle error. Usually just close the connection. */
+ }
+
+HTTP needs to know where the end of the stream is. For example, sometimes
+servers send responses without Content-Length and expect the client to
+consume input (for the body) until EOF. To tell http_parser about EOF, give
+`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
+can still be encountered during an EOF, so one must still be prepared
+to receive them.
+
+Scalar valued message information such as `status_code`, `method`, and the
+HTTP version are stored in the parser structure. This data is only
+temporally stored in `http_parser` and gets reset on each new message. If
+this information is needed later, copy it out of the structure during the
+`headers_complete` callback.
+
+The parser decodes the transfer-encoding for both requests and responses
+transparently. That is, a chunked encoding is decoded before being sent to
+the on_body callback.
+
+
+The Special Problem of Upgrade
+------------------------------
+
+HTTP supports upgrading the connection to a different protocol. An
+increasingly common example of this is the Web Socket protocol which sends
+a request like
+
+ GET /demo HTTP/1.1
+ Upgrade: WebSocket
+ Connection: Upgrade
+ Host: example.com
+ Origin: http://example.com
+ WebSocket-Protocol: sample
+
+followed by non-HTTP data.
+
+(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
+information the Web Socket protocol.)
+
+To support this, the parser will treat this as a normal HTTP message without a
+body. Issuing both on_headers_complete and on_message_complete callbacks. However
+http_parser_execute() will stop parsing at the end of the headers and return.
+
+The user is expected to check if `parser->upgrade` has been set to 1 after
+`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
+offset by the return value of `http_parser_execute()`.
+
+
+Callbacks
+---------
+
+During the `http_parser_execute()` call, the callbacks set in
+`http_parser_settings` will be executed. The parser maintains state and
+never looks behind, so buffering the data is not necessary. If you need to
+save certain data for later usage, you can do that from the callbacks.
+
+There are two types of callbacks:
+
+* notification `typedef int (*http_cb) (http_parser*);`
+ Callbacks: on_message_begin, on_headers_complete, on_message_complete.
+* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
+ Callbacks: (requests only) on_uri,
+ (common) on_header_field, on_header_value, on_body;
+
+Callbacks must return 0 on success. Returning a non-zero value indicates
+error to the parser, making it exit immediately.
+
+In case you parse HTTP message in chunks (i.e. `read()` request line
+from socket, parse, read half headers, parse, etc) your data callbacks
+may be called more than once. Http-parser guarantees that data pointer is only
+valid for the lifetime of callback. You can also `read()` into a heap allocated
+buffer to avoid copying memory around if this fits your application.
+
+Reading headers may be a tricky task if you read/parse headers partially.
+Basically, you need to remember whether last header callback was field or value
+and apply following logic:
+
+ (on_header_field and on_header_value shortened to on_h_*)
+ ------------------------ ------------ --------------------------------------------
+ | State (prev. callback) | Callback | Description/action |
+ ------------------------ ------------ --------------------------------------------
+ | nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
+ | | | into it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_field | New header started. |
+ | | | Copy current name,value buffers to headers |
+ | | | list and allocate new buffer for new name |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_field | Previous name continues. Reallocate name |
+ | | | buffer and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_value | Value for current header started. Allocate |
+ | | | new buffer and copy callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_value | Value continues. Reallocate value buffer |
+ | | | and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/TODO b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/TODO
new file mode 100644
index 0000000..eb46a08
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/TODO
@@ -0,0 +1,28 @@
+decide how to handle errs per default:
+ - ry: "set state to dead", return `read`
+ - current: call on_error w/ details, if no on_error handler set,
+ throw Exception, else call on_error and behave like orig...
+
+some tests from test.c left to port
+ (scan ...)
+documentation
+
+hi level callback interface
+eventloop
+state() as a function (?)
+ - perhaps, the idea being to be able to log/debug better...
+more tests
+ - in particular, port available c tests
+impl bits of servlet api.
+
+DONE
+
+Sun Jul 18 12:19:18 CEST 2010
+
+error handling
+ - consider callback based error handling and the current highlevel
+ "nice" logging moved to high level http impl.
+ - use Exceptions "ProtocolException"?
+
+better testing
+ - no junit to avoid dependencies
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/build.xml b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/build.xml
new file mode 100755
index 0000000..d2c6af4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/build.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+
+<project name="http-parser" default="compile" basedir=".">
+
+<echo message="pulling in property files"/>
+<property file="build.properties"/>
+<property name="classes.dir" value="classes"/>
+<property name="assemble.dir" value="assemble"/>
+<property name="lib.dir" value="lib"/>
+
+
+
+<target name="prepare">
+ <mkdir dir="${classes.dir}" />
+ <mkdir dir="${assemble.dir}" />
+ <mkdir dir="${lib.dir}" />
+</target>
+
+<target name="clean">
+ <delete dir="${classes.dir}"/>
+ <delete dir="lib"/>
+ <delete dir="doc"/>
+ <delete dir="${assemble.dir}"/>
+</target>
+
+<target name="compile" depends="prepare">
+ <javac srcdir="src" destdir="${classes.dir}" debug='true'>
+ <classpath>
+ <pathelement path="${classpath}"/>
+ <pathelement location="ext/http-parser.jar"/>
+ <pathelement location="ext/primitives.jar"/>
+ </classpath>
+ </javac>
+</target>
+
+<target name="jar" depends="compile">
+ <copy todir="${assemble.dir}">
+ <fileset dir="classes"/>
+ </copy>
+ <jar basedir="${assemble.dir}" destfile="lib/${ant.project.name}.jar"/>
+</target>
+
+<target name="run" depends="jar">
+ <echo message="don't know how to run"/>
+</target>
+
+<target name="doc" depends="prepare">
+ <javadoc sourcepath="src/impl" destdir="doc">
+ </javadoc>
+</target>
+
+<target name="test_permutations" depends="compile">
+ <java classname="http_parser.lolevel.TestLoaderNG">
+ <arg value="tests.dumped"/>
+ <classpath>
+ <pathelement location="classes/"/>
+ <pathelement location="ext/primitives.jar/"/>
+ </classpath>
+ </java>
+</target>
+
+<target name="test_unit" depends="compile">
+ <java classname="http_parser.lolevel.Test">
+ <arg value="tests.dumped"/>
+ <classpath>
+ <pathelement location="classes/"/>
+ <pathelement location="ext/primitives.jar/"/>
+ </classpath>
+ </java>
+</target>
+
+<target name="test" depends="test_permutations, test_unit"/>
+
+</project>
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c
new file mode 100644
index 0000000..e961ae8
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c
@@ -0,0 +1,2175 @@
+/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
+ *
+ * Additional changes are licensed under the same terms as NGINX and
+ * copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
+
+#define SET_ERRNO(e) \
+do { \
+ parser->http_errno = (e); \
+} while(0)
+
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser)) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
+ } \
+ } \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (FOR##_mark) { \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
+ } \
+ } \
+ FOR##_mark = NULL; \
+ } \
+} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR) \
+do { \
+ if (!FOR##_mark) { \
+ FOR##_mark = p; \
+ } \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+ {
+#define XX(num, name, string) #string,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, '!', 0, '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', 0,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'x', 'y', 'z', 0, 0, 0, '^', '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };
+
+
+static const int8_t unhex[256] =
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
+
+#undef T
+
+enum state
+ { s_dead = 1 /* important that this is > 0 */
+
+ , s_start_req_or_res
+ , s_res_or_resp_H
+ , s_start_res
+ , s_res_H
+ , s_res_HT
+ , s_res_HTT
+ , s_res_HTTP
+ , s_res_first_http_major
+ , s_res_http_major
+ , s_res_first_http_minor
+ , s_res_http_minor
+ , s_res_first_status_code
+ , s_res_status_code
+ , s_res_status
+ , s_res_line_almost_done
+
+ , s_start_req
+
+ , s_req_method
+ , s_req_spaces_before_url
+ , s_req_schema
+ , s_req_schema_slash
+ , s_req_schema_slash_slash
+ , s_req_server_start
+ , s_req_server
+ , s_req_server_with_at
+ , s_req_path
+ , s_req_query_string_start
+ , s_req_query_string
+ , s_req_fragment_start
+ , s_req_fragment
+ , s_req_http_start
+ , s_req_http_H
+ , s_req_http_HT
+ , s_req_http_HTT
+ , s_req_http_HTTP
+ , s_req_first_http_major
+ , s_req_http_major
+ , s_req_first_http_minor
+ , s_req_http_minor
+ , s_req_line_almost_done
+
+ , s_header_field_start
+ , s_header_field
+ , s_header_value_start
+ , s_header_value
+ , s_header_value_lws
+
+ , s_header_almost_done
+
+ , s_chunk_size_start
+ , s_chunk_size
+ , s_chunk_parameters
+ , s_chunk_size_almost_done
+
+ , s_headers_almost_done
+ , s_headers_done
+
+ /* Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+
+ , s_chunk_data
+ , s_chunk_data_almost_done
+ , s_chunk_data_done
+
+ , s_body_identity
+ , s_body_identity_eof
+
+ , s_message_done
+ };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+ { h_general = 0
+ , h_C
+ , h_CO
+ , h_CON
+
+ , h_matching_connection
+ , h_matching_proxy_connection
+ , h_matching_content_length
+ , h_matching_transfer_encoding
+ , h_matching_upgrade
+
+ , h_connection
+ , h_content_length
+ , h_transfer_encoding
+ , h_upgrade
+
+ , h_matching_transfer_encoding_chunked
+ , h_matching_connection_keep_alive
+ , h_matching_connection_close
+
+ , h_transfer_encoding_chunked
+ , h_connection_keep_alive
+ , h_connection_close
+ };
+
+enum http_host_state
+ {
+ s_http_host_dead = 1
+ , s_http_userinfo_start
+ , s_http_userinfo
+ , s_http_host_start
+ , s_http_host_v6_start
+ , s_http_host
+ , s_http_host_v6
+ , s_http_host_v6_end
+ , s_http_host_port_start
+ , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode */
+#define CR '\r'
+#define LF '\n'
+#define LOWER(c) (unsigned char)(c | 0x20)
+#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+ (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c) (tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) \
+ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c) \
+ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond) \
+do { \
+ if (cond) { \
+ SET_ERRNO(HPE_STRICT); \
+ goto error; \
+ } \
+} while (0)
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+/* Map errno values to strings for human-readable output */
+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
+static struct {
+ const char *name;
+ const char *description;
+} http_strerror_tab[] = {
+ HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
+};
+#undef HTTP_STRERROR_GEN
+
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return s_dead;
+ }
+
+#if HTTP_PARSER_STRICT
+ if (ch == '\t' || ch == '\f') {
+ return s_dead;
+ }
+#endif
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return s_req_path;
+ }
+
+ if (IS_ALPHA(ch)) {
+ return s_req_schema;
+ }
+
+ break;
+
+ case s_req_schema:
+ if (IS_ALPHA(ch)) {
+ return s;
+ }
+
+ if (ch == ':') {
+ return s_req_schema_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return s_req_schema_slash_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return s_req_server_start;
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return s_dead;
+ }
+
+ /* FALLTHROUGH */
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return s_req_path;
+ }
+
+ if (ch == '?') {
+ return s_req_query_string_start;
+ }
+
+ if (ch == '@') {
+ return s_req_server_with_at;
+ }
+
+ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return s_req_server;
+ }
+
+ break;
+
+ case s_req_path:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_query_string_start;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_query_string;
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return s_req_query_string;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_fragment_start:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_fragment;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_fragment;
+
+ case '#':
+ return s;
+ }
+
+ break;
+
+ case s_req_fragment:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return s;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return s_dead;
+}
+
+size_t http_parser_execute (http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len)
+{
+ char c, ch;
+ int8_t unhex_val;
+ const char *p = data;
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *url_mark = 0;
+ const char *body_mark = 0;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return 0;
+ }
+
+ if (len == 0) {
+ switch (parser->state) {
+ case s_body_identity_eof:
+ /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+ * we got paused.
+ */
+ CALLBACK_NOTIFY_NOADVANCE(message_complete);
+ return 0;
+
+ case s_dead:
+ case s_start_req_or_res:
+ case s_start_res:
+ case s_start_req:
+ return 0;
+
+ default:
+ SET_ERRNO(HPE_INVALID_EOF_STATE);
+ return 1;
+ }
+ }
+
+
+ if (parser->state == s_header_field)
+ header_field_mark = data;
+ if (parser->state == s_header_value)
+ header_value_mark = data;
+ switch (parser->state) {
+ case s_req_path:
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ url_mark = data;
+ break;
+ }
+
+ for (p=data; p != data + len; p++) {
+ ch = *p;
+
+ if (PARSING_HEADER(parser->state)) {
+ ++parser->nread;
+ /* Buffer overflow attack */
+ if (parser->nread > HTTP_MAX_HEADER_SIZE) {
+ SET_ERRNO(HPE_HEADER_OVERFLOW);
+ goto error;
+ }
+ }
+
+ reexecute_byte:
+ switch (parser->state) {
+
+ case s_dead:
+ /* this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ if (ch == CR || ch == LF)
+ break;
+
+ SET_ERRNO(HPE_CLOSED_CONNECTION);
+ goto error;
+
+ case s_start_req_or_res:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (ch == 'H') {
+ parser->state = s_res_or_resp_H;
+
+ CALLBACK_NOTIFY(message_begin);
+ } else {
+ parser->type = HTTP_REQUEST;
+ parser->state = s_start_req;
+ goto reexecute_byte;
+ }
+
+ break;
+ }
+
+ case s_res_or_resp_H:
+ if (ch == 'T') {
+ parser->type = HTTP_RESPONSE;
+ parser->state = s_res_HT;
+ } else {
+ if (ch != 'E') {
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ parser->type = HTTP_REQUEST;
+ parser->method = HTTP_HEAD;
+ parser->index = 2;
+ parser->state = s_req_method;
+ }
+ break;
+
+ case s_start_res:
+ {
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ switch (ch) {
+ case 'H':
+ parser->state = s_res_H;
+ break;
+
+ case CR:
+ case LF:
+ break;
+
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ CALLBACK_NOTIFY(message_begin);
+ break;
+ }
+
+ case s_res_H:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_res_HT;
+ break;
+
+ case s_res_HT:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_res_HTT;
+ break;
+
+ case s_res_HTT:
+ STRICT_CHECK(ch != 'P');
+ parser->state = s_res_HTTP;
+ break;
+
+ case s_res_HTTP:
+ STRICT_CHECK(ch != '/');
+ parser->state = s_res_first_http_major;
+ break;
+
+ case s_res_first_http_major:
+ if (ch < '0' || ch > '9') {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ parser->state = s_res_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_res_http_major:
+ {
+ if (ch == '.') {
+ parser->state = s_res_first_http_minor;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_res_first_http_minor:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ parser->state = s_res_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_res_http_minor:
+ {
+ if (ch == ' ') {
+ parser->state = s_res_first_status_code;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_res_first_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ if (ch == ' ') {
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ parser->status_code = ch - '0';
+ parser->state = s_res_status_code;
+ break;
+ }
+
+ case s_res_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ switch (ch) {
+ case ' ':
+ parser->state = s_res_status;
+ break;
+ case CR:
+ parser->state = s_res_line_almost_done;
+ break;
+ case LF:
+ parser->state = s_header_field_start;
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ break;
+ }
+
+ parser->status_code *= 10;
+ parser->status_code += ch - '0';
+
+ if (parser->status_code > 999) {
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_res_status:
+ /* the human readable status. e.g. "NOT FOUND"
+ * we are not humans so just ignore this */
+ if (ch == CR) {
+ parser->state = s_res_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ break;
+ }
+ break;
+
+ case s_res_line_almost_done:
+ STRICT_CHECK(ch != LF);
+ parser->state = s_header_field_start;
+ CALLBACK_NOTIFY(status_complete);
+ break;
+
+ case s_start_req:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (!IS_ALPHA(ch)) {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ parser->method = (enum http_method) 0;
+ parser->index = 1;
+ switch (ch) {
+ case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+ case 'D': parser->method = HTTP_DELETE; break;
+ case 'G': parser->method = HTTP_GET; break;
+ case 'H': parser->method = HTTP_HEAD; break;
+ case 'L': parser->method = HTTP_LOCK; break;
+ case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
+ case 'N': parser->method = HTTP_NOTIFY; break;
+ case 'O': parser->method = HTTP_OPTIONS; break;
+ case 'P': parser->method = HTTP_POST;
+ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+ break;
+ case 'R': parser->method = HTTP_REPORT; break;
+ case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
+ case 'T': parser->method = HTTP_TRACE; break;
+ case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
+ default:
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+ parser->state = s_req_method;
+
+ CALLBACK_NOTIFY(message_begin);
+
+ break;
+ }
+
+ case s_req_method:
+ {
+ const char *matcher;
+ if (ch == '\0') {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[parser->index] == '\0') {
+ parser->state = s_req_spaces_before_url;
+ } else if (ch == matcher[parser->index]) {
+ ; /* nada */
+ } else if (parser->method == HTTP_CONNECT) {
+ if (parser->index == 1 && ch == 'H') {
+ parser->method = HTTP_CHECKOUT;
+ } else if (parser->index == 2 && ch == 'P') {
+ parser->method = HTTP_COPY;
+ } else {
+ goto error;
+ }
+ } else if (parser->method == HTTP_MKCOL) {
+ if (parser->index == 1 && ch == 'O') {
+ parser->method = HTTP_MOVE;
+ } else if (parser->index == 1 && ch == 'E') {
+ parser->method = HTTP_MERGE;
+ } else if (parser->index == 1 && ch == '-') {
+ parser->method = HTTP_MSEARCH;
+ } else if (parser->index == 2 && ch == 'A') {
+ parser->method = HTTP_MKACTIVITY;
+ } else {
+ goto error;
+ }
+ } else if (parser->method == HTTP_SUBSCRIBE) {
+ if (parser->index == 1 && ch == 'E') {
+ parser->method = HTTP_SEARCH;
+ } else {
+ goto error;
+ }
+ } else if (parser->index == 1 && parser->method == HTTP_POST) {
+ if (ch == 'R') {
+ parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+ } else if (ch == 'U') {
+ parser->method = HTTP_PUT; /* or HTTP_PURGE */
+ } else if (ch == 'A') {
+ parser->method = HTTP_PATCH;
+ } else {
+ goto error;
+ }
+ } else if (parser->index == 2) {
+ if (parser->method == HTTP_PUT) {
+ if (ch == 'R') parser->method = HTTP_PURGE;
+ } else if (parser->method == HTTP_UNLOCK) {
+ if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE;
+ }
+ } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+ parser->method = HTTP_PROPPATCH;
+ } else {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ ++parser->index;
+ break;
+ }
+
+ case s_req_spaces_before_url:
+ {
+ if (ch == ' ') break;
+
+ MARK(url);
+ if (parser->method == HTTP_CONNECT) {
+ parser->state = s_req_server_start;
+ }
+
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ {
+ switch (ch) {
+ /* No whitespace allowed here */
+ case ' ':
+ case CR:
+ case LF:
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ default:
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+
+ break;
+ }
+
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_path:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ {
+ switch (ch) {
+ case ' ':
+ parser->state = s_req_http_start;
+ CALLBACK_DATA(url);
+ break;
+ case CR:
+ case LF:
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ parser->state = (ch == CR) ?
+ s_req_line_almost_done :
+ s_header_field_start;
+ CALLBACK_DATA(url);
+ break;
+ default:
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+ break;
+ }
+
+ case s_req_http_start:
+ switch (ch) {
+ case 'H':
+ parser->state = s_req_http_H;
+ break;
+ case ' ':
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+ break;
+
+ case s_req_http_H:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HT;
+ break;
+
+ case s_req_http_HT:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HTT;
+ break;
+
+ case s_req_http_HTT:
+ STRICT_CHECK(ch != 'P');
+ parser->state = s_req_http_HTTP;
+ break;
+
+ case s_req_http_HTTP:
+ STRICT_CHECK(ch != '/');
+ parser->state = s_req_first_http_major;
+ break;
+
+ /* first digit of major HTTP version */
+ case s_req_first_http_major:
+ if (ch < '1' || ch > '9') {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ parser->state = s_req_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_req_http_major:
+ {
+ if (ch == '.') {
+ parser->state = s_req_first_http_minor;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_req_first_http_minor:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ parser->state = s_req_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_req_http_minor:
+ {
+ if (ch == CR) {
+ parser->state = s_req_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ /* XXX allow spaces after digit? */
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* end of request line */
+ case s_req_line_almost_done:
+ {
+ if (ch != LF) {
+ SET_ERRNO(HPE_LF_EXPECTED);
+ goto error;
+ }
+
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ case s_header_field_start:
+ {
+ if (ch == CR) {
+ parser->state = s_headers_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ parser->state = s_headers_almost_done;
+ goto reexecute_byte;
+ }
+
+ c = TOKEN(ch);
+
+ if (!c) {
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ MARK(header_field);
+
+ parser->index = 0;
+ parser->state = s_header_field;
+
+ switch (c) {
+ case 'c':
+ parser->header_state = h_C;
+ break;
+
+ case 'p':
+ parser->header_state = h_matching_proxy_connection;
+ break;
+
+ case 't':
+ parser->header_state = h_matching_transfer_encoding;
+ break;
+
+ case 'u':
+ parser->header_state = h_matching_upgrade;
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_field:
+ {
+ c = TOKEN(ch);
+
+ if (c) {
+ switch (parser->header_state) {
+ case h_general:
+ break;
+
+ case h_C:
+ parser->index++;
+ parser->header_state = (c == 'o' ? h_CO : h_general);
+ break;
+
+ case h_CO:
+ parser->index++;
+ parser->header_state = (c == 'n' ? h_CON : h_general);
+ break;
+
+ case h_CON:
+ parser->index++;
+ switch (c) {
+ case 'n':
+ parser->header_state = h_matching_connection;
+ break;
+ case 't':
+ parser->header_state = h_matching_content_length;
+ break;
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case h_matching_connection:
+ parser->index++;
+ if (parser->index > sizeof(CONNECTION)-1
+ || c != CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case h_matching_proxy_connection:
+ parser->index++;
+ if (parser->index > sizeof(PROXY_CONNECTION)-1
+ || c != PROXY_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* content-length */
+
+ case h_matching_content_length:
+ parser->index++;
+ if (parser->index > sizeof(CONTENT_LENGTH)-1
+ || c != CONTENT_LENGTH[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+ parser->header_state = h_content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case h_matching_transfer_encoding:
+ parser->index++;
+ if (parser->index > sizeof(TRANSFER_ENCODING)-1
+ || c != TRANSFER_ENCODING[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+ parser->header_state = h_transfer_encoding;
+ }
+ break;
+
+ /* upgrade */
+
+ case h_matching_upgrade:
+ parser->index++;
+ if (parser->index > sizeof(UPGRADE)-1
+ || c != UPGRADE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(UPGRADE)-2) {
+ parser->header_state = h_upgrade;
+ }
+ break;
+
+ case h_connection:
+ case h_content_length:
+ case h_transfer_encoding:
+ case h_upgrade:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ assert(0 && "Unknown header_state");
+ break;
+ }
+ break;
+ }
+
+ if (ch == ':') {
+ parser->state = s_header_value_start;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ if (ch == CR) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ case s_header_value_start:
+ {
+ if (ch == ' ' || ch == '\t') break;
+
+ MARK(header_value);
+
+ parser->state = s_header_value;
+ parser->index = 0;
+
+ if (ch == CR) {
+ parser->header_state = h_general;
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ c = LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_upgrade:
+ parser->flags |= F_UPGRADE;
+ parser->header_state = h_general;
+ break;
+
+ case h_transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ parser->header_state = h_matching_transfer_encoding_chunked;
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ case h_content_length:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = ch - '0';
+ break;
+
+ case h_connection:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ parser->header_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ parser->header_state = h_matching_connection_close;
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_value:
+ {
+
+ if (ch == CR) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA_NOADVANCE(header_value);
+ goto reexecute_byte;
+ }
+
+ c = LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_general:
+ break;
+
+ case h_connection:
+ case h_transfer_encoding:
+ assert(0 && "Shouldn't get here.");
+ break;
+
+ case h_content_length:
+ {
+ uint64_t t;
+
+ if (ch == ' ') break;
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 10;
+ t += ch - '0';
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ /* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_chunked:
+ parser->index++;
+ if (parser->index > sizeof(CHUNKED)-1
+ || c != CHUNKED[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CHUNKED)-2) {
+ parser->header_state = h_transfer_encoding_chunked;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case h_matching_connection_keep_alive:
+ parser->index++;
+ if (parser->index > sizeof(KEEP_ALIVE)-1
+ || c != KEEP_ALIVE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+ parser->header_state = h_connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case h_matching_connection_close:
+ parser->index++;
+ if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CLOSE)-2) {
+ parser->header_state = h_connection_close;
+ }
+ break;
+
+ case h_transfer_encoding_chunked:
+ case h_connection_keep_alive:
+ case h_connection_close:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ parser->state = s_header_value;
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ parser->state = s_header_value_lws;
+
+ switch (parser->header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ case s_header_value_lws:
+ {
+ if (ch == ' ' || ch == '\t')
+ parser->state = s_header_value_start;
+ else
+ {
+ parser->state = s_header_field_start;
+ goto reexecute_byte;
+ }
+ break;
+ }
+
+ case s_headers_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ if (parser->flags & F_TRAILING) {
+ /* End of a chunked request */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ break;
+ }
+
+ parser->state = s_headers_done;
+
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ parser->upgrade =
+ (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ *
+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
+ */
+ if (settings->on_headers_complete) {
+ switch (settings->on_headers_complete(parser)) {
+ case 0:
+ break;
+
+ case 1:
+ parser->flags |= F_SKIPBODY;
+ break;
+
+ default:
+ SET_ERRNO(HPE_CB_headers_complete);
+ return p - data; /* Error */
+ }
+ }
+
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return p - data;
+ }
+
+ goto reexecute_byte;
+ }
+
+ case s_headers_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ /* Exit, the rest of the connect is in a different protocol. */
+ if (parser->upgrade) {
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ return (p - data) + 1;
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header */
+ parser->state = s_chunk_size_start;
+ } else {
+ if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->content_length != ULLONG_MAX) {
+ /* Content-Length header given and non-zero */
+ parser->state = s_body_identity;
+ } else {
+ if (parser->type == HTTP_REQUEST ||
+ !http_message_needs_eof(parser)) {
+ /* Assume content-length 0 - read the next */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else {
+ /* Read body until EOF */
+ parser->state = s_body_identity_eof;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case s_body_identity:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* The difference between advancing content_length and p is because
+ * the latter will automaticaly advance on the next loop iteration.
+ * Further, if content_length ends up at 0, we want to see the last
+ * byte again for our message complete callback.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_message_done;
+
+ /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+ *
+ * The alternative to doing this is to wait for the next byte to
+ * trigger the data callback, just as in every other case. The
+ * problem with this is that this makes it difficult for the test
+ * harness to distinguish between complete-on-EOF and
+ * complete-on-length. It's not clear that this distinction is
+ * important for applications, but let's keep it for now.
+ */
+ CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+ goto reexecute_byte;
+ }
+
+ break;
+ }
+
+ /* read until EOF */
+ case s_body_identity_eof:
+ MARK(body);
+ p = data + len - 1;
+
+ break;
+
+ case s_message_done:
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ break;
+
+ case s_chunk_size_start:
+ {
+ assert(parser->nread == 1);
+ assert(parser->flags & F_CHUNKED);
+
+ unhex_val = unhex[(unsigned char)ch];
+ if (unhex_val == -1) {
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ parser->content_length = unhex_val;
+ parser->state = s_chunk_size;
+ break;
+ }
+
+ case s_chunk_size:
+ {
+ uint64_t t;
+
+ assert(parser->flags & F_CHUNKED);
+
+ if (ch == CR) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+
+ unhex_val = unhex[(unsigned char)ch];
+
+ if (unhex_val == -1) {
+ if (ch == ';' || ch == ' ') {
+ parser->state = s_chunk_parameters;
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 16;
+ t += unhex_val;
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case s_chunk_parameters:
+ {
+ assert(parser->flags & F_CHUNKED);
+ /* just ignore this shit. TODO check for overflow */
+ if (ch == CR) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+ break;
+ }
+
+ case s_chunk_size_almost_done:
+ {
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ if (parser->content_length == 0) {
+ parser->flags |= F_TRAILING;
+ parser->state = s_header_field_start;
+ } else {
+ parser->state = s_chunk_data;
+ }
+ break;
+ }
+
+ case s_chunk_data:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* See the explanation in s_body_identity for why the content
+ * length and data pointers are managed this way.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_chunk_data_almost_done;
+ }
+
+ break;
+ }
+
+ case s_chunk_data_almost_done:
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length == 0);
+ STRICT_CHECK(ch != CR);
+ parser->state = s_chunk_data_done;
+ CALLBACK_DATA(body);
+ break;
+
+ case s_chunk_data_done:
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+ parser->nread = 0;
+ parser->state = s_chunk_size_start;
+ break;
+
+ default:
+ assert(0 && "unhandled state");
+ SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
+ goto error;
+ }
+ }
+
+ /* Run callbacks for any marks that we have leftover after we ran our of
+ * bytes. There should be at most one of these set, so it's OK to invoke
+ * them in series (unset marks will not result in callbacks).
+ *
+ * We use the NOADVANCE() variety of callbacks here because 'p' has already
+ * overflowed 'data' and this allows us to correct for the off-by-one that
+ * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+ * value that's in-bounds).
+ */
+
+ assert(((header_field_mark ? 1 : 0) +
+ (header_value_mark ? 1 : 0) +
+ (url_mark ? 1 : 0) +
+ (body_mark ? 1 : 0)) <= 1);
+
+ CALLBACK_DATA_NOADVANCE(header_field);
+ CALLBACK_DATA_NOADVANCE(header_value);
+ CALLBACK_DATA_NOADVANCE(url);
+ CALLBACK_DATA_NOADVANCE(body);
+
+ return len;
+
+error:
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
+ SET_ERRNO(HPE_UNKNOWN);
+ }
+
+ return (p - data);
+}
+
+
+/* Does the parser need to see an EOF to find the end of the message? */
+int
+http_message_needs_eof (const http_parser *parser)
+{
+ if (parser->type == HTTP_REQUEST) {
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 || /* Not Modified */
+ parser->flags & F_SKIPBODY) { /* response to a HEAD request */
+ return 0;
+ }
+
+ if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
+{
+ if (parser->http_major > 0 && parser->http_minor > 0) {
+ /* HTTP/1.1 */
+ if (parser->flags & F_CONNECTION_CLOSE) {
+ return 0;
+ }
+ } else {
+ /* HTTP/1.0 or earlier */
+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+ return 0;
+ }
+ }
+
+ return !http_message_needs_eof(parser);
+}
+
+
+const char *
+http_method_str (enum http_method m)
+{
+ return ELEM_AT(method_strings, m, "<unknown>");
+}
+
+
+void
+http_parser_init (http_parser *parser, enum http_parser_type t)
+{
+ void *data = parser->data; /* preserve application data */
+ memset(parser, 0, sizeof(*parser));
+ parser->data = data;
+ parser->type = t;
+ parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+ parser->http_errno = HPE_OK;
+}
+
+const char *
+http_errno_name(enum http_errno err) {
+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+ return http_strerror_tab[err].name;
+}
+
+const char *
+http_errno_description(enum http_errno err) {
+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+ return http_strerror_tab[err].description;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+ switch(s) {
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ if (ch == '@') {
+ return s_http_host_start;
+ }
+
+ if (IS_USERINFO_CHAR(ch)) {
+ return s_http_userinfo;
+ }
+ break;
+
+ case s_http_host_start:
+ if (ch == '[') {
+ return s_http_host_v6_start;
+ }
+
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ break;
+
+ case s_http_host:
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_end:
+ if (ch == ':') {
+ return s_http_host_port_start;
+ }
+
+ break;
+
+ case s_http_host_v6:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_start:
+ if (IS_HEX(ch) || ch == ':' || ch == '.') {
+ return s_http_host_v6;
+ }
+
+ break;
+
+ case s_http_host_port:
+ case s_http_host_port_start:
+ if (IS_NUM(ch)) {
+ return s_http_host_port;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+ enum http_host_state s;
+
+ const char *p;
+ size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+ u->field_data[UF_HOST].len = 0;
+
+ s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+ for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+ enum http_host_state new_s = http_parse_host_char(s, *p);
+
+ if (new_s == s_http_host_dead) {
+ return 1;
+ }
+
+ switch(new_s) {
+ case s_http_host:
+ if (s != s_http_host) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6:
+ if (s != s_http_host_v6) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ u->field_data[UF_PORT].off = p - buf;
+ u->field_data[UF_PORT].len = 0;
+ u->field_set |= (1 << UF_PORT);
+ }
+ u->field_data[UF_PORT].len++;
+ break;
+
+ case s_http_userinfo:
+ if (s != s_http_userinfo) {
+ u->field_data[UF_USERINFO].off = p - buf ;
+ u->field_data[UF_USERINFO].len = 0;
+ u->field_set |= (1 << UF_USERINFO);
+ }
+ u->field_data[UF_USERINFO].len++;
+ break;
+
+ default:
+ break;
+ }
+ s = new_s;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (s) {
+ case s_http_host_start:
+ case s_http_host_v6_start:
+ case s_http_host_v6:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+ struct http_parser_url *u)
+{
+ enum state s;
+ const char *p;
+ enum http_parser_url_fields uf, old_uf;
+ int found_at = 0;
+
+ u->port = u->field_set = 0;
+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+ uf = old_uf = UF_MAX;
+
+ for (p = buf; p < buf + buflen; p++) {
+ s = parse_url_char(s, *p);
+
+ /* Figure out the next field that we're operating on */
+ switch (s) {
+ case s_dead:
+ return 1;
+
+ /* Skip delimeters */
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_query_string_start:
+ case s_req_fragment_start:
+ continue;
+
+ case s_req_schema:
+ uf = UF_SCHEMA;
+ break;
+
+ case s_req_server_with_at:
+ found_at = 1;
+
+ /* FALLTROUGH */
+ case s_req_server:
+ uf = UF_HOST;
+ break;
+
+ case s_req_path:
+ uf = UF_PATH;
+ break;
+
+ case s_req_query_string:
+ uf = UF_QUERY;
+ break;
+
+ case s_req_fragment:
+ uf = UF_FRAGMENT;
+ break;
+
+ default:
+ assert(!"Unexpected state");
+ return 1;
+ }
+
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ u->field_data[uf].len++;
+ continue;
+ }
+
+ u->field_data[uf].off = p - buf;
+ u->field_data[uf].len = 1;
+
+ u->field_set |= (1 << uf);
+ old_uf = uf;
+ }
+
+ /* host must be present if there is a schema */
+ /* parsing http:///toto will fail */
+ if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
+ if (http_parse_host(buf, u, found_at) != 0) {
+ return 1;
+ }
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_PORT)) {
+ /* Don't bother with endp; we've already validated the string */
+ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+
+ u->port = (uint16_t) v;
+ }
+
+ return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+ /* Users should only be pausing/unpausing a parser that is not in an error
+ * state. In non-debug builds, there's not much that we can do about this
+ * other than ignore it.
+ */
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+ HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+ } else {
+ assert(0 && "Attempting to pause parser in error state");
+ }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+ return parser->state == s_message_done;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp
new file mode 100644
index 0000000..c6eada7
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp
@@ -0,0 +1,79 @@
+# This file is used with the GYP meta build system.
+# http://code.google.com/p/gyp/
+# To build try this:
+# svn co http://gyp.googlecode.com/svn/trunk gyp
+# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp
+# ./out/Debug/test
+{
+ 'target_defaults': {
+ 'default_configuration': 'Debug',
+ 'configurations': {
+ # TODO: hoist these out and put them somewhere common, because
+ # RuntimeLibrary MUST MATCH across the entire project
+ 'Debug': {
+ 'defines': [ 'DEBUG', '_DEBUG' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 1, # static debug
+ },
+ },
+ },
+ 'Release': {
+ 'defines': [ 'NDEBUG' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 0, # static release
+ },
+ },
+ }
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ },
+ 'VCLibrarianTool': {
+ },
+ 'VCLinkerTool': {
+ 'GenerateDebugInformation': 'true',
+ },
+ },
+ 'conditions': [
+ ['OS == "win"', {
+ 'defines': [
+ 'WIN32'
+ ],
+ }]
+ ],
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'http_parser',
+ 'type': 'static_library',
+ 'include_dirs': [ '.' ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [ '.' ],
+ },
+ 'defines': [ 'HTTP_PARSER_STRICT=0' ],
+ 'sources': [ './http_parser.c', ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # Compile as C++. http_parser.c is actually C99, but C++ is
+ # close enough in this case.
+ 'CompileAs': 2,
+ },
+ },
+ }]
+ ],
+ },
+
+ {
+ 'target_name': 'test',
+ 'type': 'executable',
+ 'dependencies': [ 'http_parser' ],
+ 'sources': [ 'test.c' ]
+ }
+ ]
+}
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h
new file mode 100644
index 0000000..2fff4bd
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h
@@ -0,0 +1,304 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 0
+
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
+#include <BaseTsd.h>
+#include <stddef.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#endif
+
+/* Maximium header size allowed */
+#define HTTP_MAX_HEADER_SIZE (80*1024)
+
+
+typedef struct http_parser http_parser;
+typedef struct http_parser_settings http_parser_settings;
+
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * http_data_cb does not return data chunks. It will be call arbitrarally
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+typedef int (*http_cb) (http_parser*);
+
+
+/* Request Methods */
+#define HTTP_METHOD_MAP(XX) \
+ XX(0, DELETE, DELETE) \
+ XX(1, GET, GET) \
+ XX(2, HEAD, HEAD) \
+ XX(3, POST, POST) \
+ XX(4, PUT, PUT) \
+ /* pathological */ \
+ XX(5, CONNECT, CONNECT) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(7, TRACE, TRACE) \
+ /* webdav */ \
+ XX(8, COPY, COPY) \
+ XX(9, LOCK, LOCK) \
+ XX(10, MKCOL, MKCOL) \
+ XX(11, MOVE, MOVE) \
+ XX(12, PROPFIND, PROPFIND) \
+ XX(13, PROPPATCH, PROPPATCH) \
+ XX(14, SEARCH, SEARCH) \
+ XX(15, UNLOCK, UNLOCK) \
+ /* subversion */ \
+ XX(16, REPORT, REPORT) \
+ XX(17, MKACTIVITY, MKACTIVITY) \
+ XX(18, CHECKOUT, CHECKOUT) \
+ XX(19, MERGE, MERGE) \
+ /* upnp */ \
+ XX(20, MSEARCH, M-SEARCH) \
+ XX(21, NOTIFY, NOTIFY) \
+ XX(22, SUBSCRIBE, SUBSCRIBE) \
+ XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
+ /* RFC-5789 */ \
+ XX(24, PATCH, PATCH) \
+ XX(25, PURGE, PURGE) \
+
+enum http_method
+ {
+#define XX(num, name, string) HTTP_##name = num,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+
+/* Flag values for http_parser.flags field */
+enum flags
+ { F_CHUNKED = 1 << 0
+ , F_CONNECTION_KEEP_ALIVE = 1 << 1
+ , F_CONNECTION_CLOSE = 1 << 2
+ , F_TRAILING = 1 << 3
+ , F_UPGRADE = 1 << 4
+ , F_SKIPBODY = 1 << 5
+ };
+
+
+/* Map for errno-related constants
+ *
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define HTTP_ERRNO_MAP(XX) \
+ /* No error */ \
+ XX(OK, "success") \
+ \
+ /* Callback-related errors */ \
+ XX(CB_message_begin, "the on_message_begin callback failed") \
+ XX(CB_status_complete, "the on_status_complete callback failed") \
+ XX(CB_url, "the on_url callback failed") \
+ XX(CB_header_field, "the on_header_field callback failed") \
+ XX(CB_header_value, "the on_header_value callback failed") \
+ XX(CB_headers_complete, "the on_headers_complete callback failed") \
+ XX(CB_body, "the on_body callback failed") \
+ XX(CB_message_complete, "the on_message_complete callback failed") \
+ \
+ /* Parsing-related errors */ \
+ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
+ XX(HEADER_OVERFLOW, \
+ "too many header bytes seen; overflow detected") \
+ XX(CLOSED_CONNECTION, \
+ "data received after completed connection: close message") \
+ XX(INVALID_VERSION, "invalid HTTP version") \
+ XX(INVALID_STATUS, "invalid HTTP status code") \
+ XX(INVALID_METHOD, "invalid HTTP method") \
+ XX(INVALID_URL, "invalid URL") \
+ XX(INVALID_HOST, "invalid host") \
+ XX(INVALID_PORT, "invalid port") \
+ XX(INVALID_PATH, "invalid path") \
+ XX(INVALID_QUERY_STRING, "invalid query string") \
+ XX(INVALID_FRAGMENT, "invalid fragment") \
+ XX(LF_EXPECTED, "LF character expected") \
+ XX(INVALID_HEADER_TOKEN, "invalid character in header") \
+ XX(INVALID_CONTENT_LENGTH, \
+ "invalid character in content-length header") \
+ XX(INVALID_CHUNK_SIZE, \
+ "invalid character in chunk size header") \
+ XX(INVALID_CONSTANT, "invalid constant string") \
+ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
+ XX(STRICT, "strict mode assertion failed") \
+ XX(PAUSED, "parser is paused") \
+ XX(UNKNOWN, "an unknown error occurred")
+
+
+/* Define HPE_* values for each errno value above */
+#define HTTP_ERRNO_GEN(n, s) HPE_##n,
+enum http_errno {
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+};
+#undef HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
+
+
+struct http_parser {
+ /** PRIVATE **/
+ unsigned char type : 2; /* enum http_parser_type */
+ unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
+ unsigned char state; /* enum state from http_parser.c */
+ unsigned char header_state; /* enum header_state from http_parser.c */
+ unsigned char index; /* index into current matcher */
+
+ uint32_t nread; /* # bytes read in various scenarios */
+ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
+
+ /** READ-ONLY **/
+ unsigned short http_major;
+ unsigned short http_minor;
+ unsigned short status_code; /* responses only */
+ unsigned char method; /* requests only */
+ unsigned char http_errno : 7;
+
+ /* 1 = Upgrade header was present and the parser has exited because of that.
+ * 0 = No upgrade header present.
+ * Should be checked when http_parser_execute() returns in addition to
+ * error checking.
+ */
+ unsigned char upgrade : 1;
+
+ /** PUBLIC **/
+ void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+ http_cb on_message_begin;
+ http_data_cb on_url;
+ http_cb on_status_complete;
+ http_data_cb on_header_field;
+ http_data_cb on_header_value;
+ http_cb on_headers_complete;
+ http_data_cb on_body;
+ http_cb on_message_complete;
+};
+
+
+enum http_parser_url_fields
+ { UF_SCHEMA = 0
+ , UF_HOST = 1
+ , UF_PORT = 2
+ , UF_PATH = 3
+ , UF_QUERY = 4
+ , UF_FRAGMENT = 5
+ , UF_USERINFO = 6
+ , UF_MAX = 7
+ };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+ uint16_t field_set; /* Bitmask of (1 << UF_*) values */
+ uint16_t port; /* Converted UF_PORT string */
+
+ struct {
+ uint16_t off; /* Offset into buffer in which field starts */
+ uint16_t len; /* Length of run in buffer */
+ } field_data[UF_MAX];
+};
+
+
+void http_parser_init(http_parser *parser, enum http_parser_type type);
+
+
+size_t http_parser_execute(http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns 0, then this should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+int http_should_keep_alive(const http_parser *parser);
+
+/* Returns a string version of the HTTP method. */
+const char *http_method_str(enum http_method m);
+
+/* Return a string name of the given error */
+const char *http_errno_name(enum http_errno err);
+
+/* Return a string description of the given error */
+const char *http_errno_description(enum http_errno err);
+
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, size_t buflen,
+ int is_connect,
+ struct http_parser_url *u);
+
+/* Pause or un-pause the parser; a nonzero value pauses */
+void http_parser_pause(http_parser *parser, int paused);
+
+/* Checks if this is the final chunk of the body. */
+int http_body_is_final(const http_parser *parser);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml
new file mode 100644
index 0000000..741121a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/impl" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../ext/primitives.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ </component>
+</module>
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java
new file mode 100644
index 0000000..774179f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java
@@ -0,0 +1,41 @@
+package http_parser;
+
+public class FieldData {
+ public int off;
+ public int len;
+
+ public FieldData(){}
+
+ public FieldData(int off, int len){
+ this.off = off;
+ this.len = len;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FieldData fieldData = (FieldData) o;
+
+ if (len != fieldData.len) return false;
+ if (off != fieldData.off) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = off;
+ result = 31 * result + len;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FieldData{" +
+ "off=" + off +
+ ", len=" + len +
+ '}';
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java
new file mode 100644
index 0000000..5380b0f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java
@@ -0,0 +1,8 @@
+package http_parser;
+
+public abstract class HTTPCallback implements http_parser.lolevel.HTTPCallback{
+ public int cb (http_parser.lolevel.HTTPParser parser) {
+ return this.cb((HTTPParser)parser);
+ }
+ public abstract int cb (HTTPParser parser);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java
new file mode 100644
index 0000000..bfe576f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java
@@ -0,0 +1,34 @@
+package http_parser;
+
+import java.nio.ByteBuffer;
+
+public abstract class HTTPDataCallback implements http_parser.lolevel.HTTPDataCallback{
+ /*
+ Very raw and extremly foolhardy! DANGER!
+ The whole Buffer concept is difficult enough to grasp as it is,
+ we pass in a buffer with an arbitrary position.
+
+ The interesting data is located at position pos and is len
+ bytes long.
+
+ The contract of this callback is that the buffer is
+ returned in the state that it was passed in, so implementing
+ this require good citizenship, you'll need to remember the current
+ position, change the position to get at the data you're interested
+ in and then set the position back to how you found it...
+
+ Therefore: there is an abstract implementation that implements
+ cb as described above, and provides a new callback
+ with signature @see cb(byte[], int, int)
+ */
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
+ byte [] by = new byte[len];
+ int saved = buf.position();
+ buf.position(pos);
+ buf.get(by);
+ buf.position(saved);
+ return cb((HTTPParser)p, by, 0, len);
+ }
+
+ public abstract int cb(HTTPParser p, byte[] by, int pos, int len);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java
new file mode 100644
index 0000000..a74206e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java
@@ -0,0 +1,12 @@
+package http_parser;
+
+
+import java.nio.ByteBuffer;
+
+public abstract class HTTPErrorCallback implements http_parser.lolevel.HTTPErrorCallback{
+ public void cb (http_parser.lolevel.HTTPParser parser, String mes, ByteBuffer buf, int initial_position) {
+ this.cb((HTTPParser)parser, Util.error(mes, buf, initial_position));
+ }
+
+ public abstract void cb(HTTPParser parser, String error);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java
new file mode 100644
index 0000000..9ccaf14
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java
@@ -0,0 +1,9 @@
+package http_parser;
+
+@SuppressWarnings("serial")
+public class HTTPException extends RuntimeException {
+
+public HTTPException(String mes) {
+ super(mes);
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java
new file mode 100644
index 0000000..7c080c1
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java
@@ -0,0 +1,107 @@
+package http_parser;
+
+import java.nio.charset.Charset;
+
+public enum HTTPMethod {
+ HTTP_DELETE("DELETE")// = 0
+ , HTTP_GET("GET")
+ , HTTP_HEAD("HEAD")
+ , HTTP_POST("POST")
+ , HTTP_PUT("PUT")
+ , HTTP_PATCH("PATCH")
+ /* pathological */
+ , HTTP_CONNECT("CONNECT")
+ , HTTP_OPTIONS("OPTIONS")
+ , HTTP_TRACE("TRACE")
+ /* webdav */
+ , HTTP_COPY("COPY")
+ , HTTP_LOCK("LOCK")
+ , HTTP_MKCOL("MKCOL")
+ , HTTP_MOVE("MOVE")
+ , HTTP_PROPFIND("PROPFIND")
+ , HTTP_PROPPATCH("PROPPATCH")
+ , HTTP_UNLOCK("UNLOCK")
+ , HTTP_REPORT("REPORT")
+ , HTTP_MKACTIVITY("MKACTIVITY")
+ , HTTP_CHECKOUT("CHECKOUT")
+ , HTTP_MERGE("MERGE")
+ , HTTP_MSEARCH("M-SEARCH")
+ , HTTP_NOTIFY("NOTIFY")
+ , HTTP_SUBSCRIBE("SUBSCRIBE")
+ , HTTP_UNSUBSCRIBE("UNSUBSCRIBE")
+ , HTTP_PURGE("PURGE")
+ ;
+
+ private static Charset ASCII;
+ static {
+ ASCII = Charset.forName("US-ASCII");;
+ }
+ public byte[] bytes;
+
+ HTTPMethod(String name) {
+ // good grief, Charlie Brown, the following is necessary because
+ // java is retarded:
+ // illegal reference to static field from initializer
+ // this.bytes = name.getBytes(ASCII);
+ // yet it's not illegal to reference static fields from
+ // methods called from initializer.
+ init(name);
+ }
+ public static HTTPMethod parse(String s) {
+ if ("HTTP_DELETE".equalsIgnoreCase(s)) {return HTTP_DELETE;}
+ else if ("DELETE".equalsIgnoreCase(s)) {return HTTP_DELETE;}
+ else if ("HTTP_GET".equalsIgnoreCase(s)) {return HTTP_GET;}
+ else if ("GET".equalsIgnoreCase(s)) {return HTTP_GET;}
+ else if ("HTTP_HEAD".equalsIgnoreCase(s)) {return HTTP_HEAD;}
+ else if ("HEAD".equalsIgnoreCase(s)) {return HTTP_HEAD;}
+ else if ("HTTP_POST".equalsIgnoreCase(s)) {return HTTP_POST;}
+ else if ("POST".equalsIgnoreCase(s)) {return HTTP_POST;}
+ else if ("HTTP_PUT".equalsIgnoreCase(s)) {return HTTP_PUT;}
+ else if ("PUT".equalsIgnoreCase(s)) {return HTTP_PUT;}
+ else if ("HTTP_PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;}
+ else if ("PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;}
+ else if ("HTTP_CONNECT".equalsIgnoreCase(s)) {return HTTP_CONNECT;}
+ else if ("CONNECT".equalsIgnoreCase(s)) {return HTTP_CONNECT;}
+ else if ("HTTP_OPTIONS".equalsIgnoreCase(s)) {return HTTP_OPTIONS;}
+ else if ("OPTIONS".equalsIgnoreCase(s)) {return HTTP_OPTIONS;}
+ else if ("HTTP_TRACE".equalsIgnoreCase(s)) {return HTTP_TRACE;}
+ else if ("TRACE".equalsIgnoreCase(s)) {return HTTP_TRACE;}
+ else if ("HTTP_COPY".equalsIgnoreCase(s)) {return HTTP_COPY;}
+ else if ("COPY".equalsIgnoreCase(s)) {return HTTP_COPY;}
+ else if ("HTTP_LOCK".equalsIgnoreCase(s)) {return HTTP_LOCK;}
+ else if ("LOCK".equalsIgnoreCase(s)) {return HTTP_LOCK;}
+ else if ("HTTP_MKCOL".equalsIgnoreCase(s)) {return HTTP_MKCOL;}
+ else if ("MKCOL".equalsIgnoreCase(s)) {return HTTP_MKCOL;}
+ else if ("HTTP_MOVE".equalsIgnoreCase(s)) {return HTTP_MOVE;}
+ else if ("MOVE".equalsIgnoreCase(s)) {return HTTP_MOVE;}
+ else if ("HTTP_PROPFIND".equalsIgnoreCase(s)){return HTTP_PROPFIND;}
+ else if ("PROPFIND".equalsIgnoreCase(s)) {return HTTP_PROPFIND;}
+ else if ("HTTP_PROPPATCH".equalsIgnoreCase(s)){return HTTP_PROPPATCH;}
+ else if ("PROPPATCH".equalsIgnoreCase(s)) {return HTTP_PROPPATCH;}
+ else if ("HTTP_UNLOCK".equalsIgnoreCase(s)) {return HTTP_UNLOCK;}
+ else if ("UNLOCK".equalsIgnoreCase(s)) {return HTTP_UNLOCK;}
+ else if ("HTTP_REPORT".equalsIgnoreCase(s)) {return HTTP_REPORT;}
+ else if ("REPORT".equalsIgnoreCase(s)){return HTTP_REPORT;}
+ else if ("HTTP_MKACTIVITY".equalsIgnoreCase(s)) {return HTTP_MKACTIVITY;}
+ else if ("MKACTIVITY".equalsIgnoreCase(s)){return HTTP_MKACTIVITY;}
+ else if ("HTTP_CHECKOUT".equalsIgnoreCase(s)) {return HTTP_CHECKOUT;}
+ else if ("CHECKOUT".equalsIgnoreCase(s)){return HTTP_CHECKOUT;}
+ else if ("HTTP_MERGE".equalsIgnoreCase(s)) {return HTTP_MERGE;}
+ else if ("MERGE".equalsIgnoreCase(s)){return HTTP_MERGE;}
+ else if ("HTTP_MSEARCH".equalsIgnoreCase(s)) {return HTTP_MSEARCH;}
+ else if ("M-SEARCH".equalsIgnoreCase(s)) {return HTTP_MSEARCH;}
+ else if ("HTTP_NOTIFY".equalsIgnoreCase(s)) {return HTTP_NOTIFY;}
+ else if ("NOTIFY".equalsIgnoreCase(s)) {return HTTP_NOTIFY;}
+ else if ("HTTP_SUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_SUBSCRIBE;}
+ else if ("SUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_SUBSCRIBE;}
+ else if ("HTTP_UNSUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_UNSUBSCRIBE;}
+ else if ("UNSUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_UNSUBSCRIBE;}
+ else if ("PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;}
+ else if ("PURGE".equalsIgnoreCase(s)) {return HTTP_PURGE;}
+ else {return null;}
+ }
+ void init (String name) {
+ ASCII = null == ASCII ? Charset.forName("US-ASCII") : ASCII;
+ this.bytes = name.getBytes(ASCII);
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java
new file mode 100644
index 0000000..7ab4fb4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java
@@ -0,0 +1,36 @@
+package http_parser;
+
+import java.nio.ByteBuffer;
+
+public class HTTPParser extends http_parser.lolevel.HTTPParser {
+
+ public HTTPParser() { super(); }
+ public HTTPParser(ParserType type) { super(type); }
+
+ public int getMajor() {
+ return super.http_major;
+ }
+
+ public int getMinor() {
+ return super.http_minor;
+ }
+
+ public int getStatusCode() {
+ return super.status_code;
+ }
+
+ public HTTPMethod getHTTPMethod() {
+ return super.method;
+ }
+
+ public boolean getUpgrade() {
+ return super.upgrade;
+ }
+
+ public boolean shouldKeepAlive() {
+ return super.http_should_keep_alive();
+ }
+ public void execute(ParserSettings settings, ByteBuffer data) {
+ this.execute(settings.getLoLevelSettings(), data);
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java
new file mode 100644
index 0000000..d371634
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java
@@ -0,0 +1,76 @@
+package http_parser;
+
+import http_parser.lolevel.*;
+import http_parser.lolevel.HTTPParser;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ */
+public class HTTPParserUrl {
+
+ public int field_set;
+ public int port;
+
+ public FieldData[] field_data = new FieldData[]{
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0)
+ }; //UF_MAX
+
+ public HTTPParserUrl(){}
+
+ public HTTPParserUrl(int field_set, int port, FieldData[] field_data){
+ this.field_set = field_set;
+ this.port = port;
+ this.field_data = field_data;
+ }
+
+ public String getFieldValue(HTTPParser.UrlFields field, ByteBuffer data) throws UnsupportedEncodingException {
+ FieldData fd = this.field_data[field.getIndex()];
+ if(fd.off == 0 & fd.len == 0) return "";
+ byte[] dst = new byte[fd.len];
+ int current_pos = data.position();
+ data.position(fd.off);
+ data.get(dst,0,fd.len);
+ data.position(current_pos);
+ String v = new String(dst, "UTF8");
+ return v;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HTTPParserUrl that = (HTTPParserUrl) o;
+
+ if (field_set != that.field_set) return false;
+ if (port != that.port) return false;
+ if (!Arrays.equals(field_data, that.field_data)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = field_set;
+ result = 31 * result + port;
+ result = 31 * result + Arrays.hashCode(field_data);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "HTTPParserUrl{" +
+ "field_set=" + field_set +
+ ", port=" + port +
+ ", field_data=" + (field_data == null ? null : Arrays.asList(field_data)) +
+ '}';
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java
new file mode 100644
index 0000000..9a5e6e9
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java
@@ -0,0 +1,256 @@
+package http_parser;
+
+
+
+import primitive.collection.ByteList;
+
+public class ParserSettings extends http_parser.lolevel.ParserSettings {
+
+ public HTTPCallback on_message_begin;
+ public HTTPDataCallback on_path;
+ public HTTPDataCallback on_query_string;
+ public HTTPDataCallback on_url;
+ public HTTPDataCallback on_fragment;
+ public HTTPCallback on_status_complete;
+ public HTTPDataCallback on_header_field;
+ public HTTPDataCallback on_header_value;
+
+ public HTTPCallback on_headers_complete;
+ public HTTPDataCallback on_body;
+ public HTTPCallback on_message_complete;
+
+ public HTTPErrorCallback on_error;
+
+ private HTTPCallback _on_message_begin;
+ private HTTPDataCallback _on_path;
+ private HTTPDataCallback _on_query_string;
+ private HTTPDataCallback _on_url;
+ private HTTPDataCallback _on_fragment;
+ private HTTPCallback _on_status_complete;
+ private HTTPDataCallback _on_header_field;
+ private HTTPDataCallback _on_header_value;
+ private HTTPCallback _on_headers_complete;
+ private HTTPDataCallback _on_body;
+ private HTTPCallback _on_message_complete;
+ private HTTPErrorCallback _on_error;
+
+ private http_parser.lolevel.ParserSettings settings;
+
+ protected ByteList field = new ByteList();
+ protected ByteList value = new ByteList();
+ protected ByteList body = new ByteList();
+
+ public ParserSettings() {
+ this.settings = new http_parser.lolevel.ParserSettings();
+ createMirrorCallbacks();
+ attachCallbacks();
+ }
+
+ protected http_parser.lolevel.ParserSettings getLoLevelSettings() {
+ return this.settings;
+ }
+
+ private void createMirrorCallbacks() {
+ this._on_message_begin = new HTTPCallback() {
+ public int cb(HTTPParser p) {
+ if (null != ParserSettings.this.on_message_begin) {
+ return ParserSettings.this.on_message_begin.cb(p);
+ }
+ return 0;
+ }
+ };
+ this._on_path = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ if (null != ParserSettings.this.on_path) {
+ return ParserSettings.this.on_path.cb(p, by, pos, len);
+ }
+ return 0;
+ }
+ };
+ this._on_query_string = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ if (null != ParserSettings.this.on_query_string) {
+ return ParserSettings.this.on_query_string.cb(p, by, pos, len);
+ }
+ return 0;
+ }
+ };
+ this._on_url = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ if (null != ParserSettings.this.on_url) {
+ return ParserSettings.this.on_url.cb(p, by, pos, len);
+ }
+ return 0;
+ }
+ };
+ this._on_fragment = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ if (null != ParserSettings.this.on_fragment) {
+ return ParserSettings.this.on_fragment.cb(p, by, pos, len);
+ }
+ return 0;
+ }
+ };
+ this._on_status_complete = new HTTPCallback() {
+ @Override
+ public int cb(HTTPParser p) {
+ if (null != ParserSettings.this.on_status_complete) {
+ return ParserSettings.this.on_status_complete.cb(p);
+ }
+ return 0;
+ }
+ };
+ this._on_error = new HTTPErrorCallback() {
+ @Override
+ public void cb(HTTPParser parser, String error) {
+ if (null != ParserSettings.this.on_error) {
+ ParserSettings.this.on_error.cb(parser, error);
+ } else {
+ throw new HTTPException(error);
+ }
+
+ }
+ };
+
+
+
+// (on_header_field and on_header_value shortened to on_h_*)
+// ------------------------ ------------ --------------------------------------------
+// | State (prev. callback) | Callback | Description/action |
+// ------------------------ ------------ --------------------------------------------
+// | nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
+// | | | into it |
+// ------------------------ ------------ --------------------------------------------
+// | value | on_h_field | New header started. |
+// | | | Copy current name,value buffers to headers |
+// | | | list and allocate new buffer for new name |
+// ------------------------ ------------ --------------------------------------------
+// | field | on_h_field | Previous name continues. Reallocate name |
+// | | | buffer and append callback data to it |
+// ------------------------ ------------ --------------------------------------------
+// | field | on_h_value | Value for current header started. Allocate |
+// | | | new buffer and copy callback data to it |
+// ------------------------ ------------ --------------------------------------------
+// | value | on_h_value | Value continues. Reallocate value buffer |
+// | | | and append callback data to it |
+// ------------------------ ------------ --------------------------------------------
+ this._on_header_field = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ // previous value complete, call on_value with full value, reset value.
+ if (0 != ParserSettings.this.value.size()) {
+ // check we're even interested...
+ if (null != ParserSettings.this.on_header_value) {
+ byte [] valueArr = ParserSettings.this.value.toArray();
+ int ret = ParserSettings.this.on_header_value.cb(p, valueArr, 0, valueArr.length);
+ if (0 != ret) {
+ return ret;
+ }
+ ParserSettings.this.value.clear();
+ }
+ }
+
+ if (null == ParserSettings.this.on_header_field) {
+ return 0;
+ }
+
+ ParserSettings.this.field.addAll(by);
+ return 0;
+ }
+ };
+ this._on_header_value = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+
+ // previous field complete, call on_field with full field value, reset field.
+ if (0 != ParserSettings.this.field.size()) {
+ // check we're even interested...
+ if (null != ParserSettings.this.on_header_field) {
+ byte [] fieldArr = ParserSettings.this.field.toArray();
+ int ret = ParserSettings.this.on_header_field.cb(p, fieldArr, 0, fieldArr.length);
+ if (0 != ret) {
+ return ret;
+ }
+ ParserSettings.this.field.clear();
+ }
+ }
+
+ if (null == ParserSettings.this.on_header_value) {
+ return 0;
+ }
+ ParserSettings.this.value.addAll(by);
+ return 0;
+ }
+ };
+ this._on_headers_complete = new HTTPCallback() {
+ @Override
+ public int cb(HTTPParser parser) {
+ // is there an uncompleted value ... ?
+ if (0 != ParserSettings.this.value.size()) {
+ // check we're even interested...
+ if (null != ParserSettings.this.on_header_value) {
+ byte [] valueArr = ParserSettings.this.value.toArray();
+ int ret = ParserSettings.this.on_header_value.cb(parser, valueArr, 0, valueArr.length);
+ if (0 != ret) {
+ return ret;
+ }
+ ParserSettings.this.value.clear();
+ }
+ }
+ if (null != ParserSettings.this.on_headers_complete) {
+ return ParserSettings.this.on_headers_complete.cb(parser);
+ }
+ return 0;
+ }
+
+ };
+ this._on_body = new HTTPDataCallback() {
+ @Override
+ public int cb(HTTPParser p, byte[] by, int pos, int len) {
+ if (null != ParserSettings.this.on_body) {
+ ParserSettings.this.body.addAll(by, pos, len);
+ }
+ return 0;
+ }
+ };
+
+ this._on_message_complete = new HTTPCallback() {
+ @Override
+ public int cb(HTTPParser parser) {
+ if (null != ParserSettings.this.on_body) {
+ byte [] body = ParserSettings.this.body.toArray();
+ int ret = ParserSettings.this.on_body.cb(parser, body, 0, body.length);
+ if (0!=ret) {
+ return ret;
+ }
+ ParserSettings.this.body.clear();
+ }
+ if (null != ParserSettings.this.on_message_complete) {
+ return ParserSettings.this.on_message_complete.cb(parser);
+ }
+ return 0;
+ }
+ };
+
+ }
+
+ private void attachCallbacks() {
+ // these are certainly set, because we mirror them ...
+ this.settings.on_message_begin = this._on_message_begin;
+ this.settings.on_path = this._on_path;
+ this.settings.on_query_string = this._on_query_string;
+ this.settings.on_url = this._on_url;
+ this.settings.on_fragment = this._on_fragment;
+ this.settings.on_status_complete = this._on_status_complete;
+ this.settings.on_header_field = this._on_header_field;
+ this.settings.on_header_value = this._on_header_value;
+ this.settings.on_headers_complete = this._on_headers_complete;
+ this.settings.on_body = this._on_body;
+ this.settings.on_message_complete = this._on_message_complete;
+ this.settings.on_error = this._on_error;
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java
new file mode 100644
index 0000000..a51f5b4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java
@@ -0,0 +1,13 @@
+package http_parser;
+
+public enum ParserType {
+HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH;
+
+ public static ParserType parse(String s) {
+ if ("HTTP_REQUEST".equalsIgnoreCase(s)) { return HTTP_REQUEST; }
+ else if ("HTTP_RESPONSE".equalsIgnoreCase(s)) { return HTTP_RESPONSE; }
+ else if ("HTTP_BOTH".equalsIgnoreCase(s)) { return HTTP_BOTH; }
+ else { return null; }
+ }
+}
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java
new file mode 100644
index 0000000..575003a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java
@@ -0,0 +1,111 @@
+package http_parser;
+
+import java.nio.ByteBuffer;
+
+public class Util {
+// public static String toString(http_parser.lolevel.HTTPParser p) {
+// StringBuilder builder = new StringBuilder();
+//
+// // the stuff up to the break is ephermeral and only meaningful
+// // while the parser is parsing. In general, this method is
+// // probably only useful during debugging.
+//
+// builder.append("state :"); builder.append(p.state); builder.append("\n");
+// builder.append("header_state :"); builder.append(p.header_state); builder.append("\n");
+// builder.append("strict :"); builder.append(p.strict); builder.append("\n");
+// builder.append("index :"); builder.append(p.index); builder.append("\n");
+// builder.append("flags :"); builder.append(p.flags); builder.append("\n");
+// builder.append("nread :"); builder.append(p.nread); builder.append("\n");
+// builder.append("content_length :"); builder.append(p.content_length); builder.append("\n");
+//
+//
+// builder.append("type :"); builder.append(p.type); builder.append("\n");
+// builder.append("http_major :"); builder.append(p.http_major); builder.append("\n");
+// builder.append("http_minor :"); builder.append(p.http_minor); builder.append("\n");
+// builder.append("status_code :"); builder.append(p.status_code); builder.append("\n");
+// builder.append("method :"); builder.append(p.method); builder.append("\n");
+// builder.append("upgrade :"); builder.append(p.upgrade); builder.append("\n");
+//
+// return builder.toString();
+//
+// }
+
+ public static String error (String mes, ByteBuffer b, int beginning) {
+ // the error message should look like this:
+ //
+ // Bla expected something, but it's not there (mes)
+ // GEt / HTTP 1_1
+ // ............^.
+ //
+ // |----------------- 72 -------------------------|
+
+ // This is ridiculously complicated and probably riddled with
+ // off-by-one errors, should be moved into high level interface.
+ // TODO.
+
+ // also: need to keep track of the initial buffer position in
+ // execute so that we don't screw up any `mark()` that may have
+ // been set outside of our control to be nice.
+
+ final int mes_width = 72;
+ int p = b.position(); // error position
+ int end = b.limit(); // this is the end
+ int m = end - beginning; // max mes length
+
+ StringBuilder builder = new StringBuilder();
+ int p_adj = p;
+
+ byte [] orig = new byte[0];
+ if (m <= mes_width) {
+ orig = new byte[m];
+ b.position(beginning);
+ b.get(orig, 0, m);
+ p_adj = p-beginning;
+
+
+ } else {
+ // we'll need to trim bit off the beginning and/or end
+ orig = new byte[mes_width];
+ // three possibilities:
+ // a.) plenty of stuff around p
+ // b.) plenty of stuff in front of p
+ // c.) plenty of stuff behind p
+ // CAN'T be not enough stuff aorund p in total, because
+ // m>meswidth (see if to this else)
+
+ int before = p-beginning;
+ int after = end - p;
+ if ( (before > mes_width/2) && (after > mes_width/2)) {
+ // plenty of stuff in front of and behind error
+ p_adj = mes_width/2;
+ b.position(p - mes_width/2);
+ b.get(orig, 0, mes_width);
+ } else if (before <= mes_width/2) {
+ // take all of the begining.
+ b.position(beginning);
+ // and as much of the rest as possible
+
+ b.get(orig, 0, mes_width);
+
+ } else {
+ // plenty of stuff before
+ before = end-mes_width;
+ b.position(before);
+ p_adj = p - before;
+ b.get(orig, 0, mes_width);
+ }
+ }
+
+ builder.append(new String(orig));
+ builder.append("\n");
+ for (int i = 0; i!= p_adj; ++i) {
+ builder.append(".");
+ }
+ builder.append("^");
+
+
+ b.position(p); // restore position
+ return builder.toString();
+
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java
new file mode 100644
index 0000000..95c29b3
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java
@@ -0,0 +1,5 @@
+package http_parser.lolevel;
+
+public interface HTTPCallback {
+ public int cb (HTTPParser parser);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java
new file mode 100644
index 0000000..6cad156
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java
@@ -0,0 +1,25 @@
+package http_parser.lolevel;
+
+import java.nio.ByteBuffer;
+
+public interface HTTPDataCallback {
+ /*
+ very raw and extremly foolhardy! DANGER!
+ The whole Buffer concept is difficult enough to grasp as it is,
+ we pass in a buffer with an arbitrary position.
+
+ The interesting data is located at position pos and is len
+ bytes long.
+
+ The contract of this callback is that the buffer is
+ returned in the state that it was passed in, so implementing
+ this require good citizenship, you'll need to remember the current
+ position, change the position to get at the data you're interested
+ in and then set the position back to how you found it...
+
+ //TODO: there should be an abstract implementation that implements
+ cb as described above, marks it final an provides a new callback
+ with signature cb(byte[], int, int)
+ */
+ public int cb(HTTPParser p, ByteBuffer buf, int pos, int len);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java
new file mode 100644
index 0000000..d38d9d4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java
@@ -0,0 +1,7 @@
+package http_parser.lolevel;
+
+import java.nio.ByteBuffer;
+
+public interface HTTPErrorCallback {
+ public void cb (HTTPParser parser, String mes, ByteBuffer buf, int initial_position);
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java
new file mode 100644
index 0000000..42022ec
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java
@@ -0,0 +1,2161 @@
+package http_parser.lolevel;
+
+import java.nio.ByteBuffer;
+import http_parser.HTTPException;
+import http_parser.HTTPMethod;
+import http_parser.HTTPParserUrl;
+import http_parser.ParserType;
+import static http_parser.lolevel.HTTPParser.C.*;
+import static http_parser.lolevel.HTTPParser.State.*;
+
+public class HTTPParser {
+ /* lots of unsigned chars here, not sure what
+ to about them, `bytes` in java suck... */
+
+ ParserType type;
+ State state;
+ HState header_state;
+ boolean strict;
+
+ int index;
+ int flags; // TODO
+
+ int nread;
+ long content_length;
+
+ int p_start; // updated each call to execute to indicate where the buffer was before we began calling it.
+
+ /** READ-ONLY **/
+ public int http_major;
+ public int http_minor;
+ public int status_code; /* responses only */
+ public HTTPMethod method; /* requests only */
+
+ /* true = Upgrade header was present and the parser has exited because of that.
+ * false = No upgrade header present.
+ * Should be checked when http_parser_execute() returns in addition to
+ * error checking.
+ */
+ public boolean upgrade;
+
+ /** PUBLIC **/
+ // TODO : this is used in c to maintain application state.
+ // is this even necessary? we have state in java ?
+ // consider
+ // Object data; /* A pointer to get hook to the "connection" or "socket" object */
+
+
+ /*
+ * technically we could combine all of these (except for url_mark) into one
+ * variable, saving stack space, but it seems more clear to have them
+ * separated.
+ */
+ int header_field_mark = -1;
+ int header_value_mark = -1;
+ int url_mark = -1;
+ int body_mark = -1;
+
+ /**
+ * Construct a Parser for ParserType.HTTP_BOTH, meaning it
+ * determines whether it's parsing a request or a response.
+ */
+ public HTTPParser() {
+ this(ParserType.HTTP_BOTH);
+ }
+
+ /**
+ * Construct a Parser and initialise it to parse either
+ * requests or responses.
+ */
+ public HTTPParser(ParserType type) {
+ this.type = type;
+ switch(type) {
+ case HTTP_REQUEST:
+ this.state = State.start_req;
+ break;
+ case HTTP_RESPONSE:
+ this.state = State.start_res;
+ break;
+ case HTTP_BOTH:
+ this.state = State.start_req_or_res;
+ break;
+ default:
+ throw new HTTPException("can't happen, invalid ParserType enum");
+ }
+ }
+
+ /*
+ * Utility to facilitate System.out.println style debugging (the way god intended)
+ */
+ static void p(Object o) {System.out.println(o);}
+
+ /** Comment from C version follows
+ *
+ * Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+ public State parse_url_char(byte ch) {
+
+ int chi = ch & 0xff; // utility, ch without signedness for table lookups.
+
+ if(SPACE == ch){
+ throw new HTTPException("space as url char");
+ }
+
+ switch(state) {
+ case req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+ if(SLASH == ch || STAR == ch){
+ return req_path;
+ }
+ if(isAtoZ(ch)){
+ return req_schema;
+ }
+ break;
+ case req_schema:
+ if(isAtoZ(ch)){
+ return req_schema;
+ }
+ if(COLON == ch){
+ return req_schema_slash;
+ }
+ break;
+ case req_schema_slash:
+ if(SLASH == ch){
+ return req_schema_slash_slash;
+ }
+ break;
+ case req_schema_slash_slash:
+ if(SLASH == ch){
+ return req_host_start;
+ }
+ break;
+ case req_host_start:
+ if (ch == (byte)'[') {
+ return req_host_v6_start;
+ }
+ if (isHostChar(ch)) {
+ return req_host;
+ }
+ break;
+
+ case req_host:
+ if (isHostChar(ch)) {
+ return req_host;
+ }
+
+ /* FALLTHROUGH */
+ case req_host_v6_end:
+ switch (ch) {
+ case ':':
+ return req_port_start;
+ case '/':
+ return req_path;
+ case '?':
+ return req_query_string_start;
+ }
+ break;
+
+ case req_host_v6:
+ if (ch == ']') {
+ return req_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case req_host_v6_start:
+ if (isHex(ch) || ch == ':') {
+ return req_host_v6;
+ }
+ break;
+
+ case req_port:
+ switch (ch) {
+ case '/':
+ return req_path;
+ case '?':
+ return req_query_string_start;
+ }
+
+ /* FALLTHROUGH */
+ case req_port_start:
+ if (isDigit(ch)) {
+ return req_port;
+ }
+ break;
+
+ case req_path:
+ if (isNormalUrlChar(chi)) {
+ return req_path;
+ }
+ switch (ch) {
+ case '?':
+ return req_query_string_start;
+ case '#':
+ return req_fragment_start;
+ }
+
+ break;
+
+ case req_query_string_start:
+ case req_query_string:
+ if (isNormalUrlChar(chi)) {
+ return req_query_string;
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return req_query_string;
+
+ case '#':
+ return req_fragment_start;
+ }
+
+ break;
+
+ case req_fragment_start:
+ if (isNormalUrlChar(chi)) {
+ return req_fragment;
+ }
+ switch (ch) {
+ case '?':
+ return req_fragment;
+
+ case '#':
+ return req_fragment_start;
+ }
+ break;
+
+ case req_fragment:
+ if (isNormalUrlChar(ch)) {
+ return req_fragment;
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return req_fragment;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return dead;
+ }
+
+ /** Execute the parser with the currently available data contained in
+ * the buffer. The buffers position() and limit() need to be set
+ * correctly (obviously) and a will be updated approriately when the
+ * method returns to reflect the consumed data.
+ */
+ public int execute(ParserSettings settings, ByteBuffer data) {
+
+ int p = data.position();
+ this.p_start = p; // this is used for pretty printing errors.
+ // and returning the amount of processed bytes.
+
+
+ // In case the headers don't provide information about the content
+ // length, `execute` needs to be called with an empty buffer to
+ // indicate that all the data has been send be the client/server,
+ // else there is no way of knowing the message is complete.
+ int len = (data.limit() - data.position());
+ if (0 == len) {
+// if (State.body_identity_eof == state) {
+// settings.call_on_message_complete(this);
+// }
+ switch (state) {
+ case body_identity_eof:
+ settings.call_on_message_complete(this);
+ return data.position() - this.p_start;
+
+ case dead:
+ case start_req_or_res:
+ case start_res:
+ case start_req:
+ return data.position() - this.p_start;
+
+ default:
+ // should we really consider this an error!?
+ throw new HTTPException("empty bytes! "+state); // error
+ }
+ }
+
+
+ // in case the _previous_ call to the parser only has data to get to
+ // the middle of certain fields, we need to update marks to point at
+ // the beginning of the current buffer.
+ switch (state) {
+ case header_field:
+ header_field_mark = p;
+ break;
+ case header_value:
+ header_value_mark = p;
+ break;
+ case req_path:
+ case req_schema:
+ case req_schema_slash:
+ case req_schema_slash_slash:
+ case req_host_start:
+ case req_host_v6_start:
+ case req_host_v6:
+ case req_host_v6_end:
+ case req_host:
+ case req_port_start:
+ case req_port:
+ case req_query_string_start:
+ case req_query_string:
+ case req_fragment_start:
+ case req_fragment:
+ url_mark = p;
+ break;
+ }
+ boolean reexecute = false;
+ int pe = 0;
+ byte ch = 0;
+ int chi = 0;
+ byte c = -1;
+ int to_read = 0;
+
+ // this is where the work gets done, traverse the available data...
+ while (data.position() != data.limit() || reexecute) {
+// p(state + ": r: " + reexecute + " :: " +p );
+
+ if(!reexecute){
+ p = data.position();
+ pe = data.limit();
+ ch = data.get(); // the current character to process.
+ chi = ch & 0xff; // utility, ch without signedness for table lookups.
+ c = -1; // utility variably used for up- and downcasing etc.
+ to_read = 0; // used to keep track of how much of body, etc. is left to read
+
+ if (parsing_header(state)) {
+ ++nread;
+ if (nread > HTTP_MAX_HEADER_SIZE) {
+ return error(settings, "possible buffer overflow", data);
+ }
+ }
+ }
+ reexecute = false;
+// p(state + " ::: " + ch + " : " + (((CR == ch) || (LF == ch)) ? ch : ("'" + (char)ch + "'")) +": "+p );
+
+ switch (state) {
+ /*
+ * this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ case dead:
+ if (CR == ch || LF == ch){
+ break;
+ }
+ return error(settings, "Connection already closed", data);
+
+
+
+ case start_req_or_res:
+ if (CR == ch || LF == ch){
+ break;
+ }
+ flags = 0;
+ content_length = -1;
+
+ if (H == ch) {
+ state = State.res_or_resp_H;
+ } else {
+ type = ParserType.HTTP_REQUEST;
+ method = start_req_method_assign(ch);
+ if (null == method) {
+ return error(settings, "invalid method", data);
+ }
+ index = 1;
+ state = State.req_method;
+ }
+ settings.call_on_message_begin(this);
+ break;
+
+
+
+ case res_or_resp_H:
+ if (T == ch) {
+ type = ParserType.HTTP_RESPONSE;
+ state = State.res_HT;
+ } else {
+ if (E != ch) {
+ return error(settings, "not E", data);
+ }
+ type = ParserType.HTTP_REQUEST;
+ method = HTTPMethod.HTTP_HEAD;
+ index = 2;
+ state = State.req_method;
+ }
+ break;
+
+
+
+ case start_res:
+ flags = 0;
+ content_length = -1;
+
+ switch(ch) {
+ case H:
+ state = State.res_H;
+ break;
+ case CR:
+ case LF:
+ break;
+ default:
+ return error(settings, "Not H or CR/LF", data);
+ }
+
+ settings.call_on_message_begin(this);
+ break;
+
+
+
+ case res_H:
+ if (strict && T != ch) {
+ return error(settings, "Not T", data);
+ }
+ state = State.res_HT;
+ break;
+ case res_HT:
+ if (strict && T != ch) {
+return error(settings, "Not T2", data);
+ }
+ state = State.res_HTT;
+ break;
+ case res_HTT:
+ if (strict && P != ch) {
+return error(settings, "Not P", data);
+ }
+ state = State.res_HTTP;
+ break;
+ case res_HTTP:
+ if (strict && SLASH != ch) {
+return error(settings, "Not '/'", data);
+ }
+ state = State.res_first_http_major;
+ break;
+
+
+
+ case res_first_http_major:
+ if (!isDigit(ch)) {
+return error(settings, "Not a digit", data);
+ }
+ http_major = (int) ch - 0x30;
+ state = State.res_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case res_http_major:
+ if (DOT == ch) {
+ state = State.res_first_http_minor;
+ break;
+ }
+ if (!isDigit(ch)) {
+return error(settings, "Not a digit", data);
+ }
+ http_major *= 10;
+ http_major += (ch - 0x30);
+
+ if (http_major > 999) {
+return error(settings, "invalid http major version: ", data);
+ }
+ break;
+
+ /* first digit of minor HTTP version */
+ case res_first_http_minor:
+ if (!isDigit(ch)) {
+return error(settings, "Not a digit", data);
+ }
+ http_minor = (int)ch - 0x30;
+ state = State.res_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case res_http_minor:
+ if (SPACE == ch) {
+ state = State.res_first_status_code;
+ break;
+ }
+ if (!isDigit(ch)) {
+return error(settings, "Not a digit", data);
+ }
+ http_minor *= 10;
+ http_minor += (ch - 0x30);
+ if (http_minor > 999) {
+return error(settings, "invalid http minor version: ", data);
+ }
+ break;
+
+
+
+ case res_first_status_code:
+ if (!isDigit(ch)) {
+ if (SPACE == ch) {
+ break;
+ }
+return error(settings, "Not a digit (status code)", data);
+ }
+ status_code = (int)ch - 0x30;
+ state = State.res_status_code;
+ break;
+
+ case res_status_code:
+ if (!isDigit(ch)) {
+ switch(ch) {
+ case SPACE:
+ state = State.res_status;
+ break;
+ case CR:
+ state = State.res_line_almost_done;
+ break;
+ case LF:
+ state = State.header_field_start;
+ break;
+ default:
+return error(settings, "not a valid status code", data);
+ }
+ break;
+ }
+ status_code *= 10;
+ status_code += (int)ch - 0x30;
+ if (status_code > 999) {
+return error(settings, "ridiculous status code:", data);
+ }
+
+ if (status_code > 99) {
+ settings.call_on_status_complete(this);
+ }
+ break;
+
+ case res_status:
+ /* the human readable status. e.g. "NOT FOUND"
+ * we are not humans so just ignore this
+ * we are not men, we are devo. */
+
+ if (CR == ch) {
+ state = State.res_line_almost_done;
+ break;
+ }
+ if (LF == ch) {
+ state = State.header_field_start;
+ break;
+ }
+ break;
+
+ case res_line_almost_done:
+ if (strict && LF != ch) {
+return error(settings, "not LF", data);
+ }
+ state = State.header_field_start;
+ break;
+
+
+
+ case start_req:
+ if (CR==ch || LF == ch) {
+ break;
+ }
+ flags = 0;
+ content_length = -1;
+
+ if(!isAtoZ(ch)){
+ return error(settings, "invalid method", data);
+ }
+
+ method = start_req_method_assign(ch);
+ if (null == method) {
+ return error(settings, "invalid method", data);
+ }
+ index = 1;
+ state = State.req_method;
+
+ settings.call_on_message_begin(this);
+ break;
+
+
+
+ case req_method:
+ if (0 == ch) {
+ return error(settings, "NULL in method", data);
+ }
+
+ byte [] arr = method.bytes;
+
+ if (SPACE == ch && index == arr.length) {
+ state = State.req_spaces_before_url;
+ } else if (arr[index] == ch) {
+ // wuhu!
+ } else if (HTTPMethod.HTTP_CONNECT == method) {
+ if (1 == index && H == ch) {
+ method = HTTPMethod.HTTP_CHECKOUT;
+ } else if (2 == index && P == ch) {
+ method = HTTPMethod.HTTP_COPY;
+ }
+ } else if (HTTPMethod.HTTP_MKCOL == method) {
+ if (1 == index && O == ch) {
+ method = HTTPMethod.HTTP_MOVE;
+ } else if (1 == index && E == ch) {
+ method = HTTPMethod.HTTP_MERGE;
+ } else if (1 == index && DASH == ch) { /* M-SEARCH */
+ method = HTTPMethod.HTTP_MSEARCH;
+ } else if (2 == index && A == ch) {
+ method = HTTPMethod.HTTP_MKACTIVITY;
+ }
+ } else if (1 == index && HTTPMethod.HTTP_POST == method) {
+ if(R == ch) {
+ method = HTTPMethod.HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+ }else if(U == ch){
+ method = HTTPMethod.HTTP_PUT; /* or HTTP_PURGE */
+ }else if(A == ch){
+ method = HTTPMethod.HTTP_PATCH;
+ }
+ } else if (2 == index) {
+ if(HTTPMethod.HTTP_PUT == method) {
+ if(R == ch){
+ method = HTTPMethod.HTTP_PURGE;
+ }
+ }else if(HTTPMethod.HTTP_UNLOCK == method){
+ if(S == ch){
+ method = HTTPMethod.HTTP_UNSUBSCRIBE;
+ }
+ }
+ }else if(4 == index && HTTPMethod.HTTP_PROPFIND == method && P == ch){
+ method = HTTPMethod.HTTP_PROPPATCH;
+ } else {
+ return error(settings, "Invalid HTTP method", data);
+ }
+
+ ++index;
+ break;
+
+
+
+ /******************* URL *******************/
+ case req_spaces_before_url:
+ if (SPACE == ch) {
+ break;
+ }
+ url_mark = p;
+ if(HTTPMethod.HTTP_CONNECT == method){
+ state = req_host_start;
+ }
+
+ state = parse_url_char(ch);
+ if(state == dead){
+ return error(settings, "Invalid something", data);
+ }
+ break;
+
+
+ case req_schema:
+ case req_schema_slash:
+ case req_schema_slash_slash:
+ case req_host_start:
+ case req_host_v6_start:
+ case req_host_v6:
+ case req_port_start:
+ switch (ch) {
+ /* No whitespace allowed here */
+ case SPACE:
+ case CR:
+ case LF:
+ return error(settings, "unexpected char in path", data);
+ default:
+ state = parse_url_char(ch);
+ if(dead == state){
+ return error(settings, "unexpected char in path", data);
+ }
+ }
+ break;
+
+ case req_host:
+ case req_host_v6_end:
+ case req_port:
+ case req_path:
+ case req_query_string_start:
+ case req_query_string:
+ case req_fragment_start:
+ case req_fragment:
+ switch (ch) {
+ case SPACE:
+ settings.call_on_url(this, data, url_mark, p-url_mark);
+ settings.call_on_path(this, data, url_mark, p - url_mark);
+ url_mark = -1;
+ state = State.req_http_start;
+ break;
+ case CR:
+ case LF:
+ http_major = 0;
+ http_minor = 9;
+ state = (CR == ch) ? req_line_almost_done : header_field_start;
+ settings.call_on_url(this, data, url_mark, p-url_mark); //TODO check params!!!
+ settings.call_on_path(this, data, url_mark, p-url_mark);
+ url_mark = -1;
+ break;
+ default:
+ state = parse_url_char(ch);
+ if(dead == state){
+ return error(settings, "unexpected char in path", data);
+ }
+ }
+ break;
+ /******************* URL *******************/
+
+
+
+ /******************* HTTP 1.1 *******************/
+ case req_http_start:
+ switch (ch) {
+ case H:
+ state = State.req_http_H;
+ break;
+ case SPACE:
+ break;
+ default:
+ return error(settings, "error in req_http_H", data);
+ }
+ break;
+
+ case req_http_H:
+ if (strict && T != ch) {
+ return error(settings, "unexpected char", data);
+ }
+ state = State.req_http_HT;
+ break;
+
+ case req_http_HT:
+ if (strict && T != ch) {
+ return error(settings, "unexpected char", data);
+ }
+ state = State.req_http_HTT;
+ break;
+
+ case req_http_HTT:
+ if (strict && P != ch) {
+ return error(settings, "unexpected char", data);
+ }
+ state = State.req_http_HTTP;
+ break;
+
+ case req_http_HTTP:
+ if (strict && SLASH != ch) {
+ return error(settings, "unexpected char", data);
+ }
+ state = req_first_http_major;
+ break;
+
+ /* first digit of major HTTP version */
+ case req_first_http_major:
+ if (!isDigit(ch)) {
+return error(settings, "non digit in http major", data);
+ }
+ http_major = (int)ch - 0x30;
+ state = State.req_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case req_http_major:
+ if (DOT == ch) {
+ state = State.req_first_http_minor;
+ break;
+ }
+
+ if (!isDigit(ch)) {
+return error(settings, "non digit in http major", data);
+ }
+
+ http_major *= 10;
+ http_major += (int)ch - 0x30;
+
+ if (http_major > 999) {
+return error(settings, "ridiculous http major", data);
+ };
+ break;
+
+ /* first digit of minor HTTP version */
+ case req_first_http_minor:
+ if (!isDigit(ch)) {
+return error(settings, "non digit in http minor", data);
+ }
+ http_minor = (int)ch - 0x30;
+ state = State.req_http_minor;
+ break;
+
+ case req_http_minor:
+ if (ch == CR) {
+ state = State.req_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ state = State.header_field_start;
+ break;
+ }
+
+ /* XXX allow spaces after digit? */
+
+ if (!isDigit(ch)) {
+return error(settings, "non digit in http minor", data);
+ }
+
+ http_minor *= 10;
+ http_minor += (int)ch - 0x30;
+
+
+ if (http_minor > 999) {
+return error(settings, "ridiculous http minor", data);
+ };
+
+ break;
+
+ /* end of request line */
+ case req_line_almost_done:
+ {
+ if (ch != LF) {
+return error(settings, "missing LF after request line", data);
+ }
+ state = header_field_start;
+ break;
+ }
+
+ /******************* HTTP 1.1 *******************/
+
+
+
+ /******************* Header *******************/
+ case header_field_start:
+ {
+ if (ch == CR) {
+ state = headers_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ state = State.headers_almost_done;
+ reexecute = true;
+ break;
+ }
+
+ c = token(ch);
+
+ if (0 == c) {
+ return error(settings, "invalid char in header:", data);
+ }
+
+ header_field_mark = p;
+
+ index = 0;
+ state = State.header_field;
+
+ switch (c) {
+ case C:
+ header_state = HState.C;
+ break;
+
+ case P:
+ header_state = HState.matching_proxy_connection;
+ break;
+
+ case T:
+ header_state = HState.matching_transfer_encoding;
+ break;
+
+ case U:
+ header_state = HState.matching_upgrade;
+ break;
+
+ default:
+ header_state = HState.general;
+ break;
+ }
+ break;
+ }
+
+
+
+ case header_field:
+ {
+ c = token(ch);
+ if (0 != c) {
+ switch (header_state) {
+ case general:
+ break;
+
+ case C:
+ index++;
+ header_state = (O == c ? HState.CO : HState.general);
+ break;
+
+ case CO:
+ index++;
+ header_state = (N == c ? HState.CON : HState.general);
+ break;
+
+ case CON:
+ index++;
+ switch (c) {
+ case N:
+ header_state = HState.matching_connection;
+ break;
+ case T:
+ header_state = HState.matching_content_length;
+ break;
+ default:
+ header_state = HState.general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case matching_connection:
+ index++;
+ if (index > CONNECTION.length || c != CONNECTION[index]) {
+ header_state = HState.general;
+ } else if (index == CONNECTION.length-1) {
+ header_state = HState.connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case matching_proxy_connection:
+ index++;
+ if (index > PROXY_CONNECTION.length || c != PROXY_CONNECTION[index]) {
+ header_state = HState.general;
+ } else if (index == PROXY_CONNECTION.length-1) {
+ header_state = HState.connection;
+ }
+ break;
+
+ /* content-length */
+
+ case matching_content_length:
+ index++;
+ if (index > CONTENT_LENGTH.length || c != CONTENT_LENGTH[index]) {
+ header_state = HState.general;
+ } else if (index == CONTENT_LENGTH.length-1) {
+ header_state = HState.content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case matching_transfer_encoding:
+ index++;
+ if (index > TRANSFER_ENCODING.length || c != TRANSFER_ENCODING[index]) {
+ header_state = HState.general;
+ } else if (index == TRANSFER_ENCODING.length-1) {
+ header_state = HState.transfer_encoding;
+ }
+ break;
+
+ /* upgrade */
+
+ case matching_upgrade:
+ index++;
+ if (index > UPGRADE.length || c != UPGRADE[index]) {
+ header_state = HState.general;
+ } else if (index == UPGRADE.length-1) {
+ header_state = HState.upgrade;
+ }
+ break;
+
+ case connection:
+ case content_length:
+ case transfer_encoding:
+ case upgrade:
+ if (SPACE != ch) header_state = HState.general;
+ break;
+
+ default:
+return error(settings, "Unknown Header State", data);
+ } // switch: header_state
+ break;
+ } // 0 != c
+
+ if (COLON == ch) {
+ settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
+ header_field_mark = -1;
+
+ state = State.header_value_start;
+ break;
+ }
+
+ if (CR == ch) {
+ state = State.header_almost_done;
+ settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
+
+ header_field_mark = -1;
+ break;
+ }
+
+ if (ch == LF) {
+ settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
+ header_field_mark = -1;
+
+ state = State.header_field_start;
+ break;
+ }
+
+return error(settings, "invalid header field", data);
+ }
+
+
+
+ case header_value_start:
+ {
+ if ((SPACE == ch) || (TAB == ch)) break;
+
+ header_value_mark = p;
+
+ state = State.header_value;
+ index = 0;
+
+
+ if (CR == ch) {
+ settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
+ header_value_mark = -1;
+
+ header_state = HState.general;
+ state = State.header_almost_done;
+ break;
+ }
+
+ if (LF == ch) {
+ settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
+ header_value_mark = -1;
+
+ state = State.header_field_start;
+ break;
+ }
+
+
+ c = upper(ch);
+
+ switch (header_state) {
+ case upgrade:
+ flags |= F_UPGRADE;
+ header_state = HState.general;
+ break;
+
+ case transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if (C == c) {
+ header_state = HState.matching_transfer_encoding_chunked;
+ } else {
+ header_state = HState.general;
+ }
+ break;
+
+ case content_length:
+ if (!isDigit(ch)) {
+return error(settings, "Content-Length not numeric", data);
+ }
+ content_length = (int)ch - 0x30;
+ break;
+
+ case connection:
+ /* looking for 'Connection: keep-alive' */
+ if (K == c) {
+ header_state = HState.matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (C == c) {
+ header_state = HState.matching_connection_close;
+ } else {
+ header_state = HState.general;
+ }
+ break;
+
+ default:
+ header_state = HState.general;
+ break;
+ }
+ break;
+ } // header value start
+
+
+
+ case header_value:
+ {
+
+ if (CR == ch) {
+ settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
+ header_value_mark = -1;
+
+ state = State.header_almost_done;
+ break;
+ }
+
+ if (LF == ch) {
+ settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
+ header_value_mark = -1;
+ state = header_almost_done;
+ reexecute = true;
+ break;
+ }
+
+ c = upper(ch);
+ switch (header_state) {
+ case general:
+ break;
+
+ case connection:
+ case transfer_encoding:
+return error(settings, "Shouldn't be here", data);
+
+ case content_length:
+ if (SPACE == ch) {
+ break;
+ }
+ if (!isDigit(ch)) {
+return error(settings, "Content-Length not numeric", data);
+ }
+
+ long t = content_length;
+ t *= 10;
+ t += (long)ch - 0x30;
+
+ /* Overflow? */
+ // t will wrap and become negative ...
+ if (t < content_length) {
+ return error(settings, "Invalid content length", data);
+ }
+ content_length = t;
+ break;
+
+ /* Transfer-Encoding: chunked */
+ case matching_transfer_encoding_chunked:
+ index++;
+ if (index > CHUNKED.length || c != CHUNKED[index]) {
+ header_state = HState.general;
+ } else if (index == CHUNKED.length-1) {
+ header_state = HState.transfer_encoding_chunked;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case matching_connection_keep_alive:
+ index++;
+ if (index > KEEP_ALIVE.length || c != KEEP_ALIVE[index]) {
+ header_state = HState.general;
+ } else if (index == KEEP_ALIVE.length-1) {
+ header_state = HState.connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case matching_connection_close:
+ index++;
+ if (index > CLOSE.length || c != CLOSE[index]) {
+ header_state = HState.general;
+ } else if (index == CLOSE.length-1) {
+ header_state = HState.connection_close;
+ }
+ break;
+
+ case transfer_encoding_chunked:
+ case connection_keep_alive:
+ case connection_close:
+ if (SPACE != ch) header_state = HState.general;
+ break;
+
+ default:
+ state = State.header_value;
+ header_state = HState.general;
+ break;
+ }
+ break;
+ } // header_value
+
+
+
+ case header_almost_done:
+ if (!header_almost_done(ch)) {
+ return error(settings, "incorrect header ending, expecting LF", data);
+ }
+ break;
+
+ case header_value_lws:
+ if (SPACE == ch || TAB == ch ){
+ state = header_value_start;
+ } else {
+ state = header_field_start;
+ reexecute = true;
+ }
+ break;
+
+ case headers_almost_done:
+ if (LF != ch) {
+ return error(settings, "header not properly completed", data);
+ }
+ if (0 != (flags & F_TRAILING)) {
+ /* End of a chunked request */
+ state = new_message();
+ settings.call_on_headers_complete(this);
+ settings.call_on_message_complete(this);
+ break;
+ }
+
+ state = headers_done;
+
+ if (0 != (flags & F_UPGRADE) || HTTPMethod.HTTP_CONNECT == method) {
+ upgrade = true;
+ }
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ */
+
+ /* (responses to HEAD request contain a CONTENT-LENGTH header
+ * but no content)
+ *
+ * Consider what to do here: I don't like the idea of the callback
+ * interface having a different contract in the case of HEAD
+ * responses. The alternatives would be either to:
+ *
+ * a.) require the header_complete callback to implement a different
+ * interface or
+ *
+ * b.) provide an overridden execute(bla, bla, boolean
+ * parsingHeader) implementation ...
+ */
+
+ /*TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO */
+ if (null != settings.on_headers_complete) {
+ settings.call_on_headers_complete(this);
+ //return;
+ }
+
+ // if (null != settings.on_headers_complete) {
+ // switch (settings.on_headers_complete.cb(parser)) {
+ // case 0:
+ // break;
+ //
+ // case 1:
+ // flags |= F_SKIPBODY;
+ // break;
+ //
+ // default:
+ // return p - data; /* Error */ // TODO // RuntimeException ?
+ // }
+ // }
+ reexecute = true;
+ break;
+
+ case headers_done:
+ if (strict && (LF != ch)) {
+ return error(settings, "STRICT CHECK", data); //TODO correct error msg
+ }
+
+ nread = 0;
+
+ // Exit, the rest of the connect is in a different protocol.
+ if (upgrade) {
+ state = new_message();
+ settings.call_on_message_complete(this);
+ return data.position()-this.p_start;
+ }
+
+ if (0 != (flags & F_SKIPBODY)) {
+ state = new_message();
+ settings.call_on_message_complete(this);
+ } else if (0 != (flags & F_CHUNKED)) {
+ /* chunked encoding - ignore Content-Length header */
+ state = State.chunk_size_start;
+ } else {
+ if (content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ state = new_message();
+ settings.call_on_message_complete(this);
+ } else if (content_length != -1) {
+ /* Content-Length header given and non-zero */
+ state = State.body_identity;
+ } else {
+ if (type == ParserType.HTTP_REQUEST || !http_message_needs_eof()) {
+ /* Assume content-length 0 - read the next */
+ state = new_message();
+ settings.call_on_message_complete(this);
+ } else {
+ /* Read body until EOF */
+ state = State.body_identity_eof;
+ }
+ }
+ }
+
+ break;
+ /******************* Header *******************/
+
+
+
+
+ /******************* Body *******************/
+ case body_identity:
+ to_read = min(pe - p, content_length); //TODO change to use buffer?
+ body_mark = p;
+
+ if (to_read > 0) {
+ settings.call_on_body(this, data, p, to_read);
+ data.position(p+to_read);
+ content_length -= to_read;
+
+ if (content_length == 0) {
+ state = message_done;
+ reexecute = true;
+ }
+ }
+ break;
+
+
+
+ case body_identity_eof:
+ to_read = pe - p; // TODO change to use buffer ?
+ if (to_read > 0) {
+ settings.call_on_body(this, data, p, to_read);
+ data.position(p+to_read);
+ }
+ break;
+
+ case message_done:
+ state = new_message();
+ settings.call_on_message_complete(this);
+ break;
+ /******************* Body *******************/
+
+
+
+ /******************* Chunk *******************/
+ case chunk_size_start:
+ if (1 != this.nread) {
+return error(settings, "nread != 1 (chunking)", data);
+
+ }
+ if (0 == (flags & F_CHUNKED)) {
+return error(settings, "not chunked", data);
+ }
+
+ c = UNHEX[chi];
+ if (c == -1) {
+return error(settings, "invalid hex char in chunk content length", data);
+ }
+ content_length = c;
+ state = State.chunk_size;
+ break;
+
+
+
+ case chunk_size:
+ if (0 == (flags & F_CHUNKED)) {
+ return error(settings, "not chunked", data);
+ }
+
+ if (CR == ch) {
+ state = State.chunk_size_almost_done;
+ break;
+ }
+
+ c = UNHEX[chi];
+
+ if (c == -1) {
+ if (SEMI == ch || SPACE == ch) {
+ state = State.chunk_parameters;
+ break;
+ }
+ return error(settings, "invalid hex char in chunk content length", data);
+ }
+ long t = content_length;
+
+ t *= 16;
+ t += c;
+ if(t < content_length){
+ return error(settings, "invalid content length", data);
+ }
+ content_length = t;
+ break;
+
+
+
+ case chunk_parameters:
+ if (0 == (flags & F_CHUNKED)) {
+return error(settings, "not chunked", data);
+ }
+ /* just ignore this shit. TODO check for overflow */
+ if (CR == ch) {
+ state = State.chunk_size_almost_done;
+ break;
+ }
+ break;
+
+
+
+ case chunk_size_almost_done:
+ if (0 == (flags & F_CHUNKED)) {
+return error(settings, "not chunked", data);
+ }
+ if (strict && LF != ch) {
+return error(settings, "expected LF at end of chunk size", data);
+ }
+
+ this.nread = 0;
+
+ if (0 == content_length) {
+ flags |= F_TRAILING;
+ state = State.header_field_start;
+ } else {
+ state = State.chunk_data;
+ }
+ break;
+
+
+
+ case chunk_data:
+ //TODO Apply changes from C version for s_chunk_data
+ if (0 == (flags & F_CHUNKED)) {
+ return error(settings, "not chunked", data);
+ }
+
+ to_read = min(pe-p, content_length);
+ if (to_read > 0) {
+ settings.call_on_body(this, data, p, to_read);
+ data.position(p+to_read);
+ }
+
+ if (to_read == content_length) {
+ state = State.chunk_data_almost_done;
+ }
+
+ content_length -= to_read;
+ break;
+
+
+
+ case chunk_data_almost_done:
+ if (0 == (flags & F_CHUNKED)) {
+return error(settings, "not chunked", data);
+ }
+ if (strict && CR != ch) {
+return error(settings, "chunk data terminated incorrectly, expected CR", data);
+ }
+ state = State.chunk_data_done;
+ //TODO CALLBACK_DATA(body)
+ // settings.call_on_body(this, data,p,?);
+ break;
+
+
+
+ case chunk_data_done:
+ if (0 == (flags & F_CHUNKED)) {
+return error(settings, "not chunked", data);
+ }
+ if (strict && LF != ch) {
+return error(settings, "chunk data terminated incorrectly, expected LF", data);
+ }
+ state = State.chunk_size_start;
+ break;
+ /******************* Chunk *******************/
+
+
+
+ default:
+return error(settings, "unhandled state", data);
+
+ } // switch
+ } // while
+
+ p = data.position();
+
+
+ /* Reaching this point assumes that we only received part of a
+ * message, inform the callbacks about the progress made so far*/
+
+ settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark);
+ settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark);
+ settings.call_on_url (this, data, url_mark, p-url_mark);
+ settings.call_on_path (this, data, url_mark, p-url_mark);
+
+ return data.position()-this.p_start;
+ } // execute
+
+ int error (ParserSettings settings, String mes, ByteBuffer data) {
+ settings.call_on_error(this, mes, data, this.p_start);
+ this.state = State.dead;
+ return data.position()-this.p_start;
+ }
+
+ public boolean http_message_needs_eof() {
+ if(type == ParserType.HTTP_REQUEST){
+ return false;
+ }
+ /* See RFC 2616 section 4.4 */
+ if ((status_code / 100 == 1) || /* 1xx e.g. Continue */
+ (status_code == 204) || /* No Content */
+ (status_code == 304) || /* Not Modified */
+ (flags & F_SKIPBODY) != 0) { /* response to a HEAD request */
+ return false;
+ }
+ if ((flags & F_CHUNKED) != 0 || content_length != -1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns true, then this will be should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+ public boolean http_should_keep_alive() {
+ if (http_major > 0 && http_minor > 0) {
+ /* HTTP/1.1 */
+ if ( 0 != (flags & F_CONNECTION_CLOSE) ) {
+ return false;
+ }
+ } else {
+ /* HTTP/1.0 or earlier */
+ if ( 0 == (flags & F_CONNECTION_KEEP_ALIVE) ) {
+ return false;
+ }
+ }
+ return !http_message_needs_eof();
+ }
+
+ public int parse_url(ByteBuffer data, boolean is_connect, HTTPParserUrl u) {
+
+ UrlFields uf = UrlFields.UF_MAX;
+ UrlFields old_uf = UrlFields.UF_MAX;
+ u.port = 0;
+ u.field_set = 0;
+ state = (is_connect ? State.req_host_start : State.req_spaces_before_url);
+ int p_init = data.position();
+ int p = 0;
+ byte ch = 0;
+ while (data.position() != data.limit()) {
+ p = data.position();
+ ch = data.get();
+ state = parse_url_char(ch);
+ switch(state) {
+ case dead:
+ return 1;
+
+ /* Skip delimeters */
+ case req_schema_slash:
+ case req_schema_slash_slash:
+ case req_host_start:
+ case req_host_v6_start:
+ case req_host_v6_end:
+ case req_port_start:
+ case req_query_string_start:
+ case req_fragment_start:
+ continue;
+
+ case req_schema:
+ uf = UrlFields.UF_SCHEMA;
+ break;
+
+ case req_host:
+ case req_host_v6:
+ uf = UrlFields.UF_HOST;
+ break;
+
+ case req_port:
+ uf = UrlFields.UF_PORT;
+ break;
+
+ case req_path:
+ uf = UrlFields.UF_PATH;
+ break;
+
+ case req_query_string:
+ uf = UrlFields.UF_QUERY;
+ break;
+
+ case req_fragment:
+ uf = UrlFields.UF_FRAGMENT;
+ break;
+
+ default:
+ return 1;
+ }
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ u.field_data[uf.getIndex()].len++;
+ continue;
+ }
+
+ u.field_data[uf.getIndex()].off = p - p_init;
+ u.field_data[uf.getIndex()].len = 1;
+
+ u.field_set |= (1 << uf.getIndex());
+ old_uf = uf;
+
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect && u.field_set != ((1 << UrlFields.UF_HOST.getIndex())|(1 << UrlFields.UF_PORT.getIndex()))) {
+ return 1;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (state) {
+ case req_host_v6_start:
+ case req_host_v6:
+ case req_host_v6_end:
+ case req_host:
+ case req_port_start:
+ return 1;
+ default:
+ break;
+ }
+
+ if (0 != (u.field_set & (1 << UrlFields.UF_PORT.getIndex()))) {
+ /* Don't bother with endp; we've already validated the string */
+ int v = strtoi(data, p_init + u.field_data[UrlFields.UF_PORT.getIndex()].off);
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+
+ u.port = v;
+ }
+
+ return 0;
+ }
+
+ //hacky reimplementation of srttoul, tailored for our simple needs
+ //we only need to parse port val, so no negative values etc
+ int strtoi(ByteBuffer data, int start_pos) {
+ data.position(start_pos);
+ byte ch;
+ String str = "";
+ while(data.position() < data.limit()) {
+ ch = data.get();
+ if(Character.isWhitespace((char)ch)){
+ continue;
+ }
+ if(isDigit(ch)){
+ str = str + (char)ch; //TODO replace with something less hacky
+ }else{
+ break;
+ }
+ }
+ return Integer.parseInt(str);
+ }
+
+ boolean isDigit(byte b) {
+ if (b >= 0x30 && b <=0x39) {
+ return true;
+ }
+ return false;
+ }
+
+ boolean isHex(byte b) {
+ return isDigit(b) || (lower(b) >= 0x61 /*a*/ && lower(b) <= 0x66 /*f*/);
+ }
+
+ boolean isAtoZ(byte b) {
+ byte c = lower(b);
+ return (c>= 0x61 /*a*/ && c <= 0x7a /*z*/);
+ }
+
+
+ byte lower (byte b) {
+ return (byte)(b|0x20);
+ }
+
+ byte upper(byte b) {
+ char c = (char)(b);
+ return (byte)Character.toUpperCase(c);
+ }
+
+ byte token(byte b) {
+ if(!strict){
+ return (b == (byte)' ') ? (byte)' ' : (byte)tokens[b] ;
+ }else{
+ return (byte)tokens[b];
+ }
+ }
+
+ boolean isHostChar(byte ch){
+ if(!strict){
+ return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch || UNDER == ch ;
+ }else{
+ return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch;
+ }
+ }
+
+ boolean isNormalUrlChar(int chi) {
+ if(!strict){
+ return (chi > 0x80) || normal_url_char[chi];
+ }else{
+ return normal_url_char[chi];
+ }
+ }
+
+ HTTPMethod start_req_method_assign(byte c){
+ switch (c) {
+ case C: return HTTPMethod.HTTP_CONNECT; /* or COPY, CHECKOUT */
+ case D: return HTTPMethod.HTTP_DELETE;
+ case G: return HTTPMethod.HTTP_GET;
+ case H: return HTTPMethod.HTTP_HEAD;
+ case L: return HTTPMethod.HTTP_LOCK;
+ case M: return HTTPMethod.HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */
+ case N: return HTTPMethod.HTTP_NOTIFY;
+ case O: return HTTPMethod.HTTP_OPTIONS;
+ case P: return HTTPMethod.HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+ case R: return HTTPMethod.HTTP_REPORT;
+ case S: return HTTPMethod.HTTP_SUBSCRIBE;
+ case T: return HTTPMethod.HTTP_TRACE;
+ case U: return HTTPMethod.HTTP_UNLOCK; /* or UNSUBSCRIBE */
+ }
+ return null; // ugh.
+ }
+
+ boolean header_almost_done(byte ch) {
+ if (strict && LF != ch) {
+ return false;
+ }
+
+ state = State.header_value_lws;
+ // TODO java enums support some sort of bitflag mechanism !?
+ switch (header_state) {
+ case connection_keep_alive:
+ flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case connection_close:
+ flags |= F_CONNECTION_CLOSE;
+ break;
+ case transfer_encoding_chunked:
+ flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+// boolean headers_almost_done (byte ch, ParserSettings settings) {
+// } // headers_almost_done
+
+
+ final int min (int a, int b) {
+ return a < b ? a : b;
+ }
+
+ final int min (int a, long b) {
+ return a < b ? a : (int)b;
+ }
+
+ /* probably not the best place to hide this ... */
+ public boolean HTTP_PARSER_STRICT;
+ State new_message() {
+ if (HTTP_PARSER_STRICT){
+ return http_should_keep_alive() ? start_state() : State.dead;
+ } else {
+ return start_state();
+ }
+
+ }
+
+ State start_state() {
+ return type == ParserType.HTTP_REQUEST ? State.start_req : State.start_res;
+ }
+
+
+ boolean parsing_header(State state) {
+
+ switch (state) {
+ case chunk_data :
+ case chunk_data_almost_done :
+ case chunk_data_done :
+ case body_identity :
+ case body_identity_eof :
+ case message_done :
+ return false;
+
+ }
+ return true;
+ }
+
+ /* "Dial C for Constants" */
+ static class C {
+ static final int HTTP_MAX_HEADER_SIZE = 80 * 1024;
+
+ static final int F_CHUNKED = 1 << 0;
+ static final int F_CONNECTION_KEEP_ALIVE = 1 << 1;
+ static final int F_CONNECTION_CLOSE = 1 << 2;
+ static final int F_TRAILING = 1 << 3;
+ static final int F_UPGRADE = 1 << 4;
+ static final int F_SKIPBODY = 1 << 5;
+
+ static final byte [] UPCASE = {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x2d,0x00,0x2f,
+ 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, 0x38,0x39,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
+ 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, 0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x5f,
+ 0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
+ 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, 0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ };
+ static final byte [] CONNECTION = {
+ 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
+ };
+ static final byte [] PROXY_CONNECTION = {
+ 0x50, 0x52, 0x4f, 0x58, 0x59, 0x2d, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
+ };
+ static final byte [] CONTENT_LENGTH = {
+ 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x2d, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48,
+ };
+ static final byte [] TRANSFER_ENCODING = {
+ 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x2d, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47,
+ };
+ static final byte [] UPGRADE = {
+ 0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x45,
+ };
+ static final byte [] CHUNKED = {
+ 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x45, 0x44,
+ };
+ static final byte [] KEEP_ALIVE = {
+ 0x4b, 0x45, 0x45, 0x50, 0x2d, 0x41, 0x4c, 0x49, 0x56, 0x45,
+ };
+ static final byte [] CLOSE = {
+ 0x43, 0x4c, 0x4f, 0x53, 0x45,
+ };
+
+ /* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+
+ static final char [] tokens = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, '!', 0, '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', 0 ,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'X', 'Y', 'Z', 0, 0, 0, 0, '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'X', 'Y', 'Z', 0, '|', 0, '~', 0,
+/* hi bit set, not ascii */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, };
+
+ static final byte [] UNHEX =
+ { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+ static final boolean [] normal_url_char = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ false, false, false, false, false, false, false, false,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ false, false, false, false, false, false, false, false,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ false, false, false, false, false, false, false, false,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ false, false, false, false, false, false, false, false,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ false, true, true, false, true, true, true, true,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ true, true, true, true, true, true, true, true,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ true, true, true, true, true, true, true, true,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ true, true, true, true, true, true, true, false,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ true, true, true, true, true, true, true, true,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ true, true, true, true, true, true, true, true,
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ true, true, true, true, true, true, true, true,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ true, true, true, true, true, true, true, true,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ true, true, true, true, true, true, true, true,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ true, true, true, true, true, true, true, true,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ true, true, true, true, true, true, true, true,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ true, true, true, true, true, true, true, false,
+
+/* hi bit set, not ascii */
+/* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8
+ * encoded paths. This is out of spec, but clients generate this and most other
+ * HTTP servers support it. We should, too. */
+
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true,
+
+ };
+
+ public static final byte A = 0x41;
+ public static final byte B = 0x42;
+ public static final byte C = 0x43;
+ public static final byte D = 0x44;
+ public static final byte E = 0x45;
+ public static final byte F = 0x46;
+ public static final byte G = 0x47;
+ public static final byte H = 0x48;
+ public static final byte I = 0x49;
+ public static final byte J = 0x4a;
+ public static final byte K = 0x4b;
+ public static final byte L = 0x4c;
+ public static final byte M = 0x4d;
+ public static final byte N = 0x4e;
+ public static final byte O = 0x4f;
+ public static final byte P = 0x50;
+ public static final byte Q = 0x51;
+ public static final byte R = 0x52;
+ public static final byte S = 0x53;
+ public static final byte T = 0x54;
+ public static final byte U = 0x55;
+ public static final byte V = 0x56;
+ public static final byte W = 0x57;
+ public static final byte X = 0x58;
+ public static final byte Y = 0x59;
+ public static final byte Z = 0x5a;
+ public static final byte UNDER = 0x5f;
+ public static final byte CR = 0x0d;
+ public static final byte LF = 0x0a;
+ public static final byte DOT = 0x2e;
+ public static final byte SPACE = 0x20;
+ public static final byte TAB = 0x09;
+ public static final byte SEMI = 0x3b;
+ public static final byte COLON = 0x3a;
+ public static final byte HASH = 0x23;
+ public static final byte QMARK = 0x3f;
+ public static final byte SLASH = 0x2f;
+ public static final byte DASH = 0x2d;
+ public static final byte STAR = 0x2a;
+ public static final byte NULL = 0x00;
+ }
+
+ enum State {
+
+ dead
+
+ , start_req_or_res
+ , res_or_resp_H
+ , start_res
+ , res_H
+ , res_HT
+ , res_HTT
+ , res_HTTP
+ , res_first_http_major
+ , res_http_major
+ , res_first_http_minor
+ , res_http_minor
+ , res_first_status_code
+ , res_status_code
+ , res_status
+ , res_line_almost_done
+
+ , start_req
+
+ , req_method
+ , req_spaces_before_url
+ , req_schema
+ , req_schema_slash
+ , req_schema_slash_slash
+ , req_host_start
+ , req_host_v6_start
+ , req_host_v6
+ , req_host_v6_end
+ , req_host
+ , req_port_start
+ , req_port
+ , req_path
+ , req_query_string_start
+ , req_query_string
+ , req_fragment_start
+ , req_fragment
+ , req_http_start
+ , req_http_H
+ , req_http_HT
+ , req_http_HTT
+ , req_http_HTTP
+ , req_first_http_major
+ , req_http_major
+ , req_first_http_minor
+ , req_http_minor
+ , req_line_almost_done
+
+ , header_field_start
+ , header_field
+ , header_value_start
+ , header_value
+ , header_value_lws
+
+ , header_almost_done
+
+ , chunk_size_start
+ , chunk_size
+ , chunk_parameters
+ , chunk_size_almost_done
+
+ , headers_almost_done
+ , headers_done
+// This space intentionally not left blank, comment from c, for orientation...
+// the c version uses <= s_header_almost_done in java, we list the states explicitly
+// in `parsing_header()`
+/* Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+ , chunk_data
+ , chunk_data_almost_done
+ , chunk_data_done
+
+ , body_identity
+ , body_identity_eof
+ , message_done
+
+ }
+ enum HState {
+ general
+ , C
+ , CO
+ , CON
+
+ , matching_connection
+ , matching_proxy_connection
+ , matching_content_length
+ , matching_transfer_encoding
+ , matching_upgrade
+
+ , connection
+ , content_length
+ , transfer_encoding
+ , upgrade
+
+ , matching_transfer_encoding_chunked
+ , matching_connection_keep_alive
+ , matching_connection_close
+
+ , transfer_encoding_chunked
+ , connection_keep_alive
+ , connection_close
+ }
+ public enum UrlFields {
+ UF_SCHEMA(0)
+ , UF_HOST(1)
+ , UF_PORT(2)
+ , UF_PATH(3)
+ , UF_QUERY(4)
+ , UF_FRAGMENT(5)
+ , UF_MAX(6);
+
+
+ private final int index;
+
+ private UrlFields(int index) {
+ this.index = index;
+ }
+ public int getIndex() {
+ return index;
+ }
+
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java
new file mode 100644
index 0000000..1ebdd4f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java
@@ -0,0 +1,83 @@
+package http_parser.lolevel;
+import java.nio.ByteBuffer;
+import http_parser.HTTPException;
+public class ParserSettings {
+
+ public HTTPCallback on_message_begin;
+ public HTTPDataCallback on_path;
+ public HTTPDataCallback on_query_string;
+ public HTTPDataCallback on_url;
+ public HTTPDataCallback on_fragment;
+ public HTTPCallback on_status_complete;
+ public HTTPDataCallback on_header_field;
+ public HTTPDataCallback on_header_value;
+ public HTTPCallback on_headers_complete;
+ public HTTPDataCallback on_body;
+ public HTTPCallback on_message_complete;
+ public HTTPErrorCallback on_error;
+
+ void call_on_message_begin (HTTPParser p) {
+ call_on(on_message_begin, p);
+ }
+
+ void call_on_message_complete (HTTPParser p) {
+ call_on(on_message_complete, p);
+ }
+
+ // this one is a little bit different:
+ // the current `position` of the buffer is the location of the
+ // error, `ini_pos` indicates where the position of
+ // the buffer when it was passed to the `execute` method of the parser, i.e.
+ // using this information and `limit` we'll know all the valid data
+ // in the buffer around the error we can use to print pretty error
+ // messages.
+ void call_on_error (HTTPParser p, String mes, ByteBuffer buf, int ini_pos) {
+ if (null != on_error) {
+ on_error.cb(p, mes, buf, ini_pos);
+ return;
+ }
+ // if on_error gets called it MUST throw an exception, else the parser
+ // will attempt to continue parsing, which it can't because it's
+ // in an invalid state.
+ throw new HTTPException(mes);
+ }
+
+ void call_on_header_field (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_header_field, p, buf, pos, len);
+ }
+ void call_on_query_string (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_query_string, p, buf, pos, len);
+ }
+ void call_on_fragment (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_fragment, p, buf, pos, len);
+ }
+ void call_on_status_complete(HTTPParser p) {
+ call_on(on_status_complete, p);
+ }
+ void call_on_path (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_path, p, buf, pos, len);
+ }
+ void call_on_header_value (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_header_value, p, buf, pos, len);
+ }
+ void call_on_url (HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_url, p, buf, pos, len);
+ }
+ void call_on_body(HTTPParser p, ByteBuffer buf, int pos, int len) {
+ call_on(on_body, p, buf, pos, len);
+ }
+ void call_on_headers_complete(HTTPParser p) {
+ call_on(on_headers_complete, p);
+ }
+ void call_on (HTTPCallback cb, HTTPParser p) {
+ // cf. CALLBACK2 macro
+ if (null != cb) {
+ cb.cb(p);
+ }
+ }
+ void call_on (HTTPDataCallback cb, HTTPParser p, ByteBuffer buf, int pos, int len) {
+ if (null != cb && -1 != pos) {
+ cb.cb(p,buf,pos,len);
+ }
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java
new file mode 100644
index 0000000..62f0a0e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java
@@ -0,0 +1,374 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.io.*;
+import java.util.*;
+
+import http_parser.HTTPMethod;
+import http_parser.HTTPParserUrl;
+import http_parser.ParserType;
+import http_parser.lolevel.TestLoaderNG.Header;
+import http_parser.lolevel.TestLoaderNG.LastHeader;
+
+import primitive.collection.ByteList;
+
+import static http_parser.lolevel.Util.str;
+
+public class Message {
+ String name;
+ byte [] raw;
+ ParserType type;
+ HTTPMethod method;
+ int status_code;
+ String request_path; // byte [] ?
+ String request_url;
+ String fragment ;
+ String query_string;
+ byte [] body;
+ int body_size;
+ int num_headers;
+ LastHeader last_header_element;
+ Map<String,String> header;
+ List<Header> headers;
+ boolean should_keep_alive;
+
+ byte[] upgrade;
+ boolean upgrade() {
+ return null != upgrade;
+ }
+
+ int http_major;
+ int http_minor;
+
+ boolean message_begin_called;
+ boolean headers_complete_called;
+ boolean message_complete_called;
+ boolean message_complete_on_eof;
+
+
+ Map<String,String> parsed_header;
+ String currHField;
+ String currHValue;
+ byte [] pbody;
+ int num_called;
+
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("NAME: "); b.append(name);b.append("\n");
+ b.append("type: "); b.append(type);b.append("\n");
+ b.append("method: "); b.append(method);b.append("\n");
+ b.append("status_code: "); b.append(status_code);b.append("\n");
+ b.append("request_path: "); b.append(request_path);b.append("\n");
+ b.append("request_url: "); b.append(request_url);b.append("\n");
+ b.append("fragment: "); b.append(fragment);b.append("\n");
+ b.append("query_string: "); b.append(query_string);b.append("\n");
+ b.append("body:\n"); b.append(new String(body));b.append("\n");
+ b.append("should_keep_alive: "); b.append(should_keep_alive);b.append("\n");
+ b.append("upgrade: "); b.append(upgrade);b.append("\n");
+ b.append("http_major: "); b.append(http_major);b.append("\n");
+ b.append("http_minor: "); b.append(http_minor);b.append("\n");
+ b.append("message_complete_called: "); b.append(message_complete_called);b.append("\n");
+ return b.toString();
+ }
+
+ Message () {
+ this.header = new HashMap<String, String>();
+ this.headers = new LinkedList<Header>();
+ reset();
+ }
+ /*
+ *prepare this Test Instance for reuse.
+ * */
+ void reset () {
+ this.parsed_header = new HashMap<String, String>();
+ this.pbody = null;
+ this.num_called = 0;
+
+ }
+ void check (boolean val, String mes) {
+ if (!val) {
+ //p(name+" : "+mes);
+ throw new RuntimeException(name+" : "+mes);
+ }
+ }
+
+
+ HTTPDataCallback getCB (final String value, final String mes, final TestSettings settings) {
+ return new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len){
+ // if ("url".equals(mes)){
+ // p("pos"+pos);
+ // p("len"+len);
+ // if (8==pos && 5 == len && "connect request".equals(name)) {
+ // //throw new RuntimeException(name);
+ // }
+ // }
+ //String str = str(b, pos, len);
+ ByteList list = settings.map.get(mes);
+ for (int i=0; i!=len; ++i) {
+ list.add(b.get(pos+i));
+ }
+ //settings.map.put(mes, prev_val + str);
+ //check(value.equals(str), "incorrect "+mes+": "+str);
+ if (-1 == pos) {
+ throw new RuntimeException("he?");
+ }
+ return 0;
+ }
+ };
+ }
+
+ void execute () {
+ p(name);
+ ByteBuffer buf = ByteBuffer.wrap(raw);
+ HTTPParser p = new HTTPParser();
+ TestSettings s = settings();
+
+
+
+ p.execute(s, buf);
+ if (!p.upgrade) {
+ // call execute again, else parser can't know message is done
+ // if no content length is set.
+ p.execute(s, buf);
+ }
+ if (!s.success) {
+ throw new RuntimeException("Test: "+name+" failed");
+ }
+ } // execute
+
+ void execute_permutations() {
+ /*
+ |-|---------------|
+ |--|--------------|
+ |---|-------------|
+ (...)
+ |---------------|-|
+ |-----------------|
+ */
+ p(name);
+ for (int i = 2; i != raw.length; ++i) {
+ // p(i);
+ HTTPParser p = new HTTPParser();
+ TestSettings s = settings();
+ ByteBuffer buf = ByteBuffer.wrap(raw);
+ int olimit = buf.limit();
+ buf.limit(i);
+
+ parse(p,s,buf);
+ if (!p.upgrade) {
+ buf.position(i);
+ buf.limit(olimit);
+
+ parse(p,s,buf);
+ if (!p.upgrade) {
+ parse(p,s,buf);
+ } else {
+ if (!upgrade()) {
+ throw new RuntimeException("Test:"+name+"parsed as upgrade, is not");
+ }
+ }
+
+ } else {
+ if (!upgrade()) {
+ throw new RuntimeException("Test:"+name+"parsed as upgrade, is not");
+ }
+ }
+ if (!s.success) {
+ p(this);
+ throw new RuntimeException("Test: "+name+" failed");
+ }
+ reset();
+ }
+ //System.exit(0);
+ } // execute_permutations
+ void parse(HTTPParser p, ParserSettings s, ByteBuffer b) {
+ //p("About to parse: "+b.position() + "->" + b.limit());
+ p.execute(s, b);
+ }
+
+ TestSettings settings() {
+ final TestSettings s = new TestSettings();
+ s.on_url = getCB(request_url, "url", s);
+ s.on_message_begin = new HTTPCallback() {
+ public int cb (HTTPParser p) {
+ message_begin_called = true;
+ return -1;
+ }
+ };
+ s.on_header_field = new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len){
+ if (null != currHValue && null == currHField) {
+ throw new RuntimeException(name+": shouldn't happen");
+ }
+ if (null != currHField) {
+ if (null == currHValue) {
+ currHField += str(b,pos,len);
+ return 0;
+ } else {
+ parsed_header.put(currHField, currHValue);
+ currHField = null;
+ currHValue = null;
+ }
+ }
+ currHField = str(b,pos,len);
+ return 0;
+ }
+ };
+ s.on_header_value = new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len){
+ if (null == currHField) {
+ throw new RuntimeException(name+" :shouldn't happen field");
+ }
+ if (null == currHValue) {
+ currHValue = str(b,pos,len);
+ } else {
+ currHValue += str(b, pos, len);
+ }
+ return 0;
+ }
+ };
+ s.on_headers_complete = new HTTPCallback() {
+ public int cb (HTTPParser p) {
+ headers_complete_called = true;
+ String parsed_path = null;
+ String parsed_query = null;
+ String parsed_url = null;
+ String parsed_frag = null;
+
+ try {
+ parsed_url = new String(s.map.get("url").toArray(), "UTF8");
+
+ HTTPParserUrl u = new HTTPParserUrl();
+ HTTPParser pp = new HTTPParser();
+ ByteBuffer data = Util.buffer(parsed_url);
+ pp.parse_url(data,false, u);
+
+ parsed_path = u.getFieldValue(HTTPParser.UrlFields.UF_PATH, data);
+ parsed_query = u.getFieldValue(HTTPParser.UrlFields.UF_QUERY, data);
+ parsed_frag = u.getFieldValue(HTTPParser.UrlFields.UF_FRAGMENT, data);
+
+ } catch (java.io.UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+
+ if (!request_path.equals(parsed_path)) {
+ throw new RuntimeException(name+": invalid path: "+parsed_path+" should be: "+request_path);
+ }
+ if (!query_string.equals(parsed_query)) {
+ throw new RuntimeException(name+": invalid query: "+parsed_query+" should be: "+query_string);
+ }
+ if (!request_url.equals(parsed_url)) {
+ throw new RuntimeException(">"+name+"<: invalid url: >"+parsed_url+"< should be: >"+request_url+"<");
+ }
+ if (!fragment.equals(parsed_frag)) {
+ throw new RuntimeException(name+": invalid fragement: "+parsed_frag+" should be: "+fragment);
+ }
+ if (null != currHValue || null != currHField) {
+ if (null == currHField || null == currHValue) {
+ throw new RuntimeException("shouldn't happen");
+ }
+ }
+ if (null != currHField) {
+ //p(currHField);
+ //p(">"+currHValue+"<");
+ parsed_header.put(currHField, currHValue);
+ currHField = null;
+ currHValue = null;
+ }
+
+
+ return 0;
+ }
+ };
+ // s.on_headers_complete = new HTTPCallback() {
+ // public int cb (HTTPParser p) {
+ // p("Complete:"+name);
+ // return 0;
+ // }
+ // };
+
+ s.on_body = new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len){
+ int l = pbody == null ? len : len + pbody.length;
+ int off = pbody == null ? 0 : pbody.length;
+ byte [] nbody = new byte[l];
+
+ if (null != pbody) {
+ System.arraycopy(pbody, 0, nbody, 0, pbody.length);
+ }
+
+ int saved = b.position();
+ b.position(pos);
+ b.get(nbody, off, len);
+ b.position(saved);
+ pbody = nbody;
+ return 0;
+ }
+ };
+
+ s.on_message_complete = new HTTPCallback() {
+ public int cb(HTTPParser p) {
+ message_complete_called = true;
+ num_called += 1;
+ if ( p.http_minor != http_minor
+ || p.http_major != http_major
+ || p.status_code != status_code ) {
+
+ throw new RuntimeException("major/minor/status_code mismatch");
+ }
+
+ //check headers
+
+ if (header.keySet().size() != parsed_header.keySet().size()) {
+ p(parsed_header);
+ throw new RuntimeException(name+": different amount of headers");
+ }
+ for (String key : header.keySet()) {
+ String pvalue = parsed_header.get(key);
+ if (!header.get(key).equals(pvalue)) {
+ throw new RuntimeException(name+" : different values for :"+key+" is >"+pvalue+"< should: >"+header.get(key)+"<");
+ }
+ }
+ //check body
+ if (null == pbody && (null == body || body.length == 0 || body.length == 1)) {
+ s.success = true;
+ return 0;
+ }
+ if (null == pbody) {
+ throw new RuntimeException(name+": no body, should be: "+new String(body));
+ }
+ if (pbody.length != body.length) {
+ p(pbody.length);
+ p(body.length);
+ p(new String(pbody));
+ p(new String(body));
+ throw new RuntimeException(name+": incorrect body length");
+ }
+ for (int i = 0 ; i!= body.length; ++i) {
+ if (pbody[i] != body[i]) {
+ throw new RuntimeException("different body");
+ }
+ }
+ s.success = true;
+ return 0;
+ }
+ };
+ return s;
+ } // settings
+ static void p(Object o) {
+ System.out.println(o);
+ }
+
+ static class TestSettings extends ParserSettings {
+ public boolean success;
+ Map<String, ByteList> map;
+ TestSettings () {
+ map = new HashMap<String, ByteList>();
+ map.put("path", new ByteList());
+ map.put("query_string", new ByteList());
+ map.put("url", new ByteList());
+ map.put("fragment", new ByteList());
+ }
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java
new file mode 100644
index 0000000..0e74021
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java
@@ -0,0 +1,51 @@
+package http_parser.lolevel;
+
+import http_parser.HTTPParserUrl;
+import static http_parser.lolevel.Util.*;
+
+public class ParseUrl {
+ public static void test(int i) {
+ HTTPParserUrl u = new HTTPParserUrl();
+ HTTPParser p = new HTTPParser();
+ Url test = Url.URL_TESTS[i];
+// System.out.println(":: " + test.name);
+ int rv = p.parse_url(Util.buffer(test.url),test.is_connect,u);
+ UnitTest.check_equals(rv, test.rv);
+ if(test.rv == 0){
+ UnitTest.check_equals(u, test.u);
+ }
+
+ }
+ public static void test() {
+ p(ParseUrl.class);
+
+ for (int i = 0; i < Url.URL_TESTS.length; i++) {
+ test(i);
+ }
+ }
+
+ static void usage() {
+ p("usage: [jre] http_parser.lolevel.ParseUrl [i]");
+ p(" i : optional test case id");
+ p("---------------------------------------------");
+ p("Test Cases:");
+ for (int i =0; i!= Url.URL_TESTS.length; ++i) {
+ p(" "+i+": "+Url.URL_TESTS[i].name);
+ }
+ }
+
+ public static void main (String [] args) {
+ if (0 == args.length) {
+ test();
+ } else {
+ try {
+ int i = Integer.parseInt(args[0]);
+ test(i);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ usage();
+ }
+
+ }
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java
new file mode 100644
index 0000000..4367bbb
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java
@@ -0,0 +1,69 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.util.*;
+
+import static http_parser.lolevel.Util.*;
+import http_parser.*;
+
+import primitive.collection.ByteList;
+
+public class Requests {
+
+ static void test_simple(String req, boolean should_pass) {
+ HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST);
+ ByteBuffer buf = buffer(req);
+ boolean passed = false;
+ int read = 0;
+ try {
+ parser.execute(Util.SETTINGS_NULL, buf);
+ passed = (read == req.length());
+ read = parser.execute(Util.SETTINGS_NULL, Util.empty());
+ passed &= (0 == read);
+ } catch (Throwable t) {
+ passed = false;
+ }
+ check(passed == should_pass);
+ }
+ static void simple_tests() {
+ test_simple("hello world", false);
+ test_simple("GET / HTP/1.1\r\n\r\n", false);
+
+ test_simple("ASDF / HTTP/1.1\r\n\r\n", false);
+ test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", false);
+ test_simple("GETA / HTTP/1.1\r\n\r\n", false);
+ }
+
+ public static void test () {
+ p(Requests.class);
+ simple_tests();
+
+ List<Message> all = TestLoaderNG.load("tests.dumped");
+ List<Message> requests = new LinkedList<Message>();
+ for (Message m : all) {
+ if (ParserType.HTTP_REQUEST == m.type) {
+ requests.add(m);
+ }
+ }
+ for (Message m : requests) {
+ test_message(m);
+ }
+
+ for (int i = 0; i!= requests.size(); ++i) {
+ if (!requests.get(i).should_keep_alive) continue;
+ for (int j = 0; j!=requests.size(); ++j) {
+ if (!requests.get(j).should_keep_alive) continue;
+ for (int k = 0; k!= requests.size(); ++k) {
+ test_multiple3(requests.get(i), requests.get(j), requests.get(k));
+ }
+ }
+ }
+
+ // postpone test_scan
+
+ }
+
+
+
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java
new file mode 100644
index 0000000..1cb71dc
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java
@@ -0,0 +1,52 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.util.*;
+
+import static http_parser.lolevel.Util.*;
+import http_parser.*;
+
+import primitive.collection.ByteList;
+
+public class Responses {
+
+
+
+ public static void test () {
+ p(Responses.class);
+ List<Message> all = TestLoaderNG.load("tests.dumped");
+ List<Message> responses = new LinkedList<Message>();
+ for (Message m : all) {
+ if (ParserType.HTTP_RESPONSE == m.type) {
+ responses.add(m);
+ }
+ }
+ for (Message m : responses) {
+ test_message(m);
+ }
+
+ for (int i = 0; i!= responses.size(); ++i) {
+ if (!responses.get(i).should_keep_alive) continue;
+ for (int j = 0; j!=responses.size(); ++j) {
+ if (!responses.get(j).should_keep_alive) continue;
+ for (int k = 0; k!= responses.size(); ++k) {
+ test_multiple3(responses.get(i), responses.get(j), responses.get(k));
+ }
+ }
+ }
+
+ // not sure what test_message_count_body does that test_message doesn't...
+ // Message m = find(responses, "404 no headers no body");
+ // test_message_count_body(m);
+ // m = find(responses, "200 trailing space on chunked body");
+ // test_message_count_body(m);
+
+ // TODO test very large chunked response
+
+ // test_scan is more or less the same as test_permutations, will implement later...
+ }
+
+
+
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java
new file mode 100644
index 0000000..6c35898
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java
@@ -0,0 +1,16 @@
+package http_parser.lolevel;
+
+
+public class Test {
+ public static void main (String [] args) {
+ UnitTest.test();
+ TestHeaderOverflowError.test();
+ TestNoOverflowLongBody.test();
+ Responses.test();
+ ParseUrl.test();
+ Requests.test();
+ Upgrade.test();
+ WrongContentLength.test();
+ }
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java
new file mode 100644
index 0000000..ee47903
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java
@@ -0,0 +1,48 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+
+import static http_parser.lolevel.Util.*;
+
+public class TestHeaderOverflowError {
+
+ public static void test (http_parser.ParserType type) {
+ HTTPParser parser = new HTTPParser(type);
+ ByteBuffer buf = getBytes(type);
+
+ int numbytes = buf.limit();
+
+ parser.execute(Util.SETTINGS_NULL, buf);
+
+ check(numbytes == buf.position());
+
+ buf = buffer("header-key: header-value\r\n");
+ numbytes = buf.limit();
+ for (int i = 0; i!= 1000; ++i) {
+ parser.execute(Util.SETTINGS_NULL, buf);
+ check(numbytes == buf.position());
+
+ buf.rewind();
+
+ }
+ }
+
+ static ByteBuffer getBytes (http_parser.ParserType type) {
+ if (http_parser.ParserType.HTTP_BOTH == type) {
+ throw new RuntimeException("only HTTP_REQUEST and HTTP_RESPONSE");
+ }
+
+ if (http_parser.ParserType.HTTP_REQUEST == type) {
+ return buffer("GET / HTTP/1.1\r\n");
+ }
+ return buffer("HTTP/1.0 200 OK\r\n");
+ }
+
+ public static void test () {
+ p(TestHeaderOverflowError.class);
+ test(http_parser.ParserType.HTTP_REQUEST);
+ test(http_parser.ParserType.HTTP_RESPONSE);
+ }
+
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java
new file mode 100644
index 0000000..329485d
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java
@@ -0,0 +1,212 @@
+package http_parser.lolevel;
+// name : 200 trailing space on chunked body
+// raw : "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n"
+// type : HTTP_RESPONSE
+// method: HTTP_DELETE
+// status code :200
+// request_path:
+// request_url :
+// fragment :
+// query_string:
+// body :"This is the data in the first chunk\r\nand this is the second one\r\n"
+// body_size :65
+// header_0 :{ "Content-Type": "text/plain"}
+// header_1 :{ "Transfer-Encoding": "chunked"}
+// should_keep_alive :1
+// upgrade :0
+// http_major :1
+// http_minor :1
+
+
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.Reader;
+import java.io.Reader;
+import java.io.IOException;
+
+import java.util.*;
+import java.util.regex.*;
+
+import java.nio.ByteBuffer;
+
+import http_parser.HTTPMethod;
+import http_parser.ParserType;
+
+public class TestLoaderNG {
+ String fn;
+ public TestLoaderNG(String filename) {
+ this.fn = filename;
+ }
+ static void p(Object o) {
+ System.out.println(o);
+ }
+ public static List<Message> load (String fn) {
+ List<Message> list = null;
+ try {
+ BufferedReader buf = new BufferedReader(new FileReader(fn));
+ list = load(buf);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ return list;
+
+ }
+ public static Message parse (String message) {
+ List<Message> list = load(new BufferedReader(new StringReader(message)));
+ if (null == list || 0 == list.size() ) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ public static List<Message> load (BufferedReader buf) {
+ List<Message> list = new LinkedList<Message>();
+ String line = null;
+ Message curr = new Message();
+ Pattern pattern = Pattern.compile("(\\S+)\\s*:(.*)");
+ try {
+ while (null != (line = buf.readLine()) ){
+ if ("".equals(line.trim())) {
+ list.add (curr);
+ curr = new Message();
+ continue;
+ }
+ Matcher m = pattern.matcher(line);
+ if (m.matches()) {
+ // you can not be fucking serious!?
+ // this has got to be the most retarded regex
+ // interface in the history of the world ...
+ // (though I'm sure there's worse c++ regexp libs...)
+ MatchResult r = m.toMatchResult();
+ String key = r.group(1).trim();
+ String value = r.group(2).trim();
+ if ("name".equals(key)) {curr.name = value;}
+ else if ("raw".equals(key)) {curr.raw = toByteArray(value);} //!
+ else if ("type".equals(key)) {curr.type = ParserType.parse(value);}
+ else if ("method".equals(key)) {curr.method = HTTPMethod.parse(value);}
+ else if ("status_code".equals(key)) {curr.status_code = Integer.parseInt(value);}
+ else if ("request_path".equals(key)) {curr.request_path = value;}
+ else if ("request_url".equals(key)) {curr.request_url = value;}
+
+ else if ("fragment".equals(key)) {curr.fragment = value;}
+ else if ("query_string".equals(key)) {curr.query_string = value;}
+ else if ("body".equals(key)) {curr.body = toByteArray(value);} //!
+ else if ("body_size".equals(key)) {curr.body_size = Integer.parseInt(value);}
+ else if (key.startsWith("header")) {
+ String [] h = getHeader(value);
+ curr.header.put(h[0], h[1]);
+ }
+ else if ("should_keep_alive".equals(key))
+ {curr.should_keep_alive = (1 == Integer.parseInt(value));}
+ else if ("upgrade".equals(key)) { curr.upgrade = toByteArray(value);}
+ else if ("http_major".equals(key)) {curr.http_major = Integer.parseInt(value);}
+ else if ("http_minor".equals(key)) {curr.http_minor = Integer.parseInt(value);}
+ } else {
+ p("WTF?"+line);
+ }
+
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ return list;
+ }
+
+ static String [] getHeader(String value) {
+ // { "Host": "0.0.0.0=5000"}
+ Pattern p = Pattern.compile("\\{ ?\"([^\"]*)\": ?\"(.*)\"}");
+ Matcher m = p.matcher(value);
+ if (!m.matches()) {
+ p(value);
+ throw new RuntimeException("something wrong");
+ }
+ String [] result = new String[2];
+ MatchResult r = m.toMatchResult();
+ result[0] = r.group(1).trim();
+ result[1] = r.group(2); //.trim();
+ return result;
+ }
+
+ static final byte BSLASH = 0x5c;
+ static final byte QUOT = 0x22;
+ static final byte CR = 0x0d;
+ static final byte LF = 0x0a;
+ static final byte n = 0x6e;
+ static final byte r = 0x72;
+
+ static final Byte[] JAVA_GENERICS_ROCK_HARD = new Byte[0];
+
+
+ static byte [] toByteArray (String quotedString) {
+ ArrayList<Byte> bytes = new ArrayList<Byte>();
+ String s = quotedString.substring(1, quotedString.length()-1);
+ byte [] byts = s.getBytes(java.nio.charset.Charset.forName("UTF8"));
+ boolean escaped = false;
+ for (byte b : byts) {
+ switch (b) {
+ case BSLASH:
+ escaped = true;
+ break;
+ case n:
+ if (escaped) {
+ bytes.add(LF);
+ escaped = false;
+ } else {
+ bytes.add(b);
+ }
+ break;
+ case r:
+ if (escaped) {
+ escaped = false;
+ bytes.add(CR);
+ } else {
+ bytes.add(b);
+ }
+ break;
+ case QUOT:
+ escaped = false;
+ bytes.add(QUOT);
+ break;
+ default:
+ bytes.add(b);
+ }
+
+ }
+
+ byts = new byte[bytes.size()];
+ int i = 0;
+ for (Byte b : bytes) {
+ byts[i++]=b;
+ }
+ return byts;
+ }
+
+ public static void main(String [] args) throws Throwable {
+ //TestLoaderNG l = new TestLoaderNG(args[0]);
+ List<Message> ts = load(args[0]);
+ for (Message t : ts) {
+// for (int i =0; i!= t.raw.length; ++i) {
+// p(i+":"+t.raw[i]);
+// }
+// try {
+ t.execute_permutations();
+// } catch (Throwable th) {
+// p("failed: "+t.name);
+// }
+ t.execute();
+ // System.exit(0);
+ }
+ }
+
+ class Header {
+ String field;
+ String value;
+ }
+ enum LastHeader {
+ NONE
+ ,FIELD
+ ,VALUE
+ }
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java
new file mode 100644
index 0000000..13d8ea0
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java
@@ -0,0 +1,62 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+
+import static http_parser.lolevel.Util.*;
+
+public class TestNoOverflowLongBody {
+
+ public static void test (http_parser.ParserType type, int len) {
+ HTTPParser parser = new HTTPParser(type);
+ ByteBuffer buf = getBytes(type, len);
+
+ int buflen = buf.limit();
+
+ parser.execute(Util.SETTINGS_NULL, buf);
+
+ check(buflen == buf.position());
+
+ buf = buffer("a");
+ buflen = buf.limit();
+
+ for (int i = 0; i!= len; ++i) {
+ parser.execute(Util.SETTINGS_NULL, buf);
+ check(buflen == buf.position());
+ buf.rewind();
+ }
+
+ buf = getBytes(type, len);
+ buflen = buf.limit();
+
+ parser.execute(Util.SETTINGS_NULL, buf);
+
+ check(buflen == buf.position());
+
+ }
+
+ static ByteBuffer getBytes (http_parser.ParserType type, int length) {
+ if (http_parser.ParserType.HTTP_BOTH == type) {
+ throw new RuntimeException("only HTTP_REQUEST and HTTP_RESPONSE");
+ }
+
+ String template = "%s\r\nConnection: Keep-Alive\r\nContent-Length: %d\r\n\r\n";
+ String str = null;
+ if (http_parser.ParserType.HTTP_REQUEST == type) {
+ str = String.format(template, "GET / HTTP/1.1", length);
+ } else {
+ str = String.format(template, "HTTP/1.0 200 OK", length);
+ }
+ return buffer(str);
+ }
+
+ public static void test () {
+ p(TestNoOverflowLongBody.class);
+ test(http_parser.ParserType.HTTP_REQUEST, 1000);
+ test(http_parser.ParserType.HTTP_REQUEST, 100000);
+ test(http_parser.ParserType.HTTP_RESPONSE, 1000);
+ test(http_parser.ParserType.HTTP_RESPONSE, 100000);
+ }
+
+
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java
new file mode 100644
index 0000000..4159980
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java
@@ -0,0 +1,117 @@
+package http_parser.lolevel;
+
+import java.nio.ByteBuffer;
+import http_parser.HTTPException;
+import http_parser.Util;
+
+public class UnitTest {
+
+ static void p(Object o) {System.out.println(o);}
+
+ public static void testErrorFormat() {
+ String bla = "This has an error in position 10 (the n in 'an')";
+ ByteBuffer buf = ByteBuffer.wrap(bla.getBytes());
+ buf.position(10);
+
+ String mes =
+"This has an error in position 10 (the n in 'an')\n" +
+"..........^";
+
+ check_equals(mes, Util.error ("test error", buf, 0));
+
+
+ bla = "123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J";
+ buf = ByteBuffer.wrap(bla.getBytes());
+ buf.position(50);
+ mes =
+"56789B123456789C123456789D123456789E123456789F123456789G123456789H123456\n"+
+"....................................^";
+ check_equals(mes, Util.error("test trim right and left", buf, 0));
+
+
+ buf.position(5);
+ mes =
+"123456789A123456789B123456789C123456789D123456789E123456789F123456789G12\n"+
+".....^";
+ check_equals(mes, Util.error("test trim right", buf, 0));
+
+
+ int limit = buf.limit();
+ buf.limit(10);
+ mes =
+"123456789A\n"+
+".....^";
+ check_equals(mes, Util.error("all before, not enough after", buf, 0));
+
+
+
+ buf.limit(limit);
+ buf.position(90);
+ mes =
+"9C123456789D123456789E123456789F123456789G123456789H123456789I123456789J\n"+
+"..............................................................^";
+ check_equals(mes, Util.error("test trim left", buf, 10));
+ }
+
+
+ // Test that the error callbacks are properly called.
+ public static void testErrorCallback () {
+ String nothttp = "THis is certainly not valid HTTP";
+ ByteBuffer buf = ByteBuffer.wrap(nothttp.getBytes());
+
+ ParserSettings s = new ParserSettings();
+ s.on_error = new HTTPErrorCallback() {
+ public void cb (HTTPParser p, String mes, ByteBuffer buf, int pos) {
+ throw new HTTPException(mes);
+ }
+ }; // err callback
+
+
+ HTTPParser p = new HTTPParser();
+ try {
+ p.execute(s, buf);
+ } catch (HTTPException e) {
+ check_equals("Invalid HTTP method", e.getMessage());
+ }
+
+ buf = ByteBuffer.wrap("GET / HTTP 1.10000".getBytes());
+ p = new HTTPParser();
+ try {
+ p.execute(s, buf);
+ } catch (HTTPException e) {
+ check_equals("ridiculous http minor", e.getMessage());
+ }
+
+ // if no error handler is defined, behave just like the above...
+ ParserSettings s0 = new ParserSettings();
+
+ buf = ByteBuffer.wrap("THis is certainly not valid HTTP".getBytes());
+ p = new HTTPParser();
+ try {
+ p.execute(s0, buf);
+ } catch (HTTPException e) {
+ check_equals("Invalid HTTP method", e.getMessage());
+ }
+
+ buf = ByteBuffer.wrap("GET / HTTP 1.10000".getBytes());
+ p = new HTTPParser();
+ try {
+ p.execute(s0, buf);
+ } catch (HTTPException e) {
+ check_equals("ridiculous http minor", e.getMessage());
+ }
+ }
+
+ static void check_equals(Object supposed2be, Object is) {
+ if (!supposed2be.equals(is)) {
+ throw new RuntimeException(is + " is supposed to be "+supposed2be);
+ }
+ }
+
+
+ public static void test () {
+ p(UnitTest.class);
+ testErrorFormat();
+ testErrorCallback();
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java
new file mode 100644
index 0000000..9af3d4a
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java
@@ -0,0 +1,27 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.util.*;
+
+import http_parser.ParserType;
+
+import static http_parser.lolevel.Util.*;
+
+public class Upgrade {
+ static final String upgrade = "GET /demo HTTP/1.1\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Upgrade: WebSocket\r\n\r\n" +
+ "third key data";
+ static void test () {
+ p(Upgrade.class);
+ HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST);
+ ByteBuffer buf = buffer(upgrade);
+
+ int read = parser.execute(Util.SETTINGS_NULL, buf);
+ check (63 == read);
+ String s = str(buf);
+ check ("third key data".equals(str(buf)));
+
+ }
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java
new file mode 100644
index 0000000..35469d1
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java
@@ -0,0 +1,127 @@
+package http_parser.lolevel;
+
+import http_parser.FieldData;
+import http_parser.HTTPParserUrl;
+
+import static http_parser.HTTPParserUrl.*;
+import static http_parser.lolevel.HTTPParser.*;
+
+/**
+ */
+public class Url {
+
+ public static Url[] URL_TESTS = new Url[]{
+ new Url("proxy request", "http://hostname/", false,
+ new HTTPParserUrl(
+ (1 << UrlFields.UF_SCHEMA.getIndex()) | (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PATH.getIndex()),
+ 0,
+ new FieldData[]{
+ new FieldData(0,4),
+ new FieldData(7,8),
+ new FieldData(0,0),
+ new FieldData(15,1),
+ new FieldData(0,0),
+ new FieldData(0,0)
+ }),
+ 0),
+ new Url("CONNECT request", "hostname:443", true,
+ new HTTPParserUrl(
+ (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PORT.getIndex()),
+ 443,
+ new FieldData[]{
+ new FieldData(0,0),
+ new FieldData(0,8),
+ new FieldData(9,3),
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0)
+ }),
+ 0),
+ new Url("proxy ipv6 request", "http://[1:2::3:4]/", false,
+ new HTTPParserUrl(
+ (1 << UrlFields.UF_SCHEMA.getIndex()) | (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PATH.getIndex()),
+ 0,
+ new FieldData[]{
+ new FieldData(0,4),
+ new FieldData(8,8),
+ new FieldData(0,0),
+ new FieldData(17,1),
+ new FieldData(0,0),
+ new FieldData(0,0)
+ }),
+ 0),
+ new Url("CONNECT ipv6 address", "[1:2::3:4]:443", true,
+ new HTTPParserUrl(
+ (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PORT.getIndex()),
+ 443,
+ new FieldData[]{
+ new FieldData(0,0),
+ new FieldData(1,8),
+ new FieldData(11,3),
+ new FieldData(0,0),
+ new FieldData(0,0),
+ new FieldData(0,0)
+ }),
+ 0),
+ new Url("extra ? in query string",
+ "http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css",
+ false,
+ new HTTPParserUrl(
+ (1 << UrlFields.UF_SCHEMA.getIndex()) |
+ (1 << UrlFields.UF_HOST.getIndex()) |
+ (1 << UrlFields.UF_PATH.getIndex()) |
+ (1 << UrlFields.UF_QUERY.getIndex()),
+ 0,
+ new FieldData[]{
+ new FieldData(0,4),
+ new FieldData(7,10),
+ new FieldData(0,0),
+ new FieldData(17,12),
+ new FieldData(30,187),
+ new FieldData(0,0)
+ }),
+ 0),
+ new Url("proxy empty host",
+ "http://:443/",
+ false,
+ null,
+ 1),
+ new Url("proxy empty port",
+ "http://hostname:/",
+ false,
+ null,
+ 1),
+ new Url("CONNECT empty host",
+ ":443",
+ true,
+ null,
+ 1),
+ new Url("CONNECT empty port",
+ "hostname:",
+ true,
+ null,
+ 1),
+ new Url("CONNECT with extra bits",
+ "hostname:443/",
+ true,
+ null,
+ 1),
+
+ };
+
+ String name;
+ String url;
+ boolean is_connect;
+ HTTPParserUrl u;
+ int rv;
+
+ public Url(String name, String url, boolean is_connect, HTTPParserUrl u, int rv) {
+ this.name = name;
+ this.url = url;
+ this.is_connect = is_connect;
+ this.u = u;
+ this.rv = rv;
+ }
+
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java
new file mode 100644
index 0000000..c73d9e6
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java
@@ -0,0 +1,236 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.util.*;
+
+import primitive.collection.ByteList;
+
+import http_parser.*;
+
+public class Util {
+
+ static final ParserSettings SETTINGS_NULL = new ParserSettings();
+
+ static String str (ByteBuffer b, int pos, int len) {
+ byte [] by = new byte[len];
+ int saved = b.position();
+ b.position(pos);
+ b.get(by);
+ b.position(saved);
+ return new String(by);
+ }
+ static String str (ByteBuffer b) {
+ int len = b.limit() - b.position();
+ byte [] by = new byte[len];
+ int saved = b.position();
+ b.get(by);
+ b.position(saved);
+ return new String(by);
+ }
+
+ static ByteBuffer buffer(String str) {
+ return ByteBuffer.wrap(str.getBytes());
+ }
+
+ static ByteBuffer empty() {
+ return ByteBuffer.wrap(new byte[0]);
+ }
+
+ static void check(boolean betterBtrue) {
+ if (!betterBtrue) {
+ throw new RuntimeException("!");
+ }
+ }
+ static void check (int should, int is) {
+ if (should != is) {
+ throw new RuntimeException("should be: "+should+" is:"+is);
+ }
+ }
+
+ static void test_message(Message mes) {
+ int raw_len = mes.raw.length;
+ for (int msg1len = 0; msg1len != raw_len; ++msg1len) {
+ mes.reset();
+ ByteBuffer msg1 = ByteBuffer.wrap(mes.raw, 0, msg1len);
+ ByteBuffer msg2 = ByteBuffer.wrap(mes.raw, msg1len, mes.raw.length - msg1len);
+
+ HTTPParser parser = new HTTPParser(mes.type);
+ ParserSettings settings = mes.settings();
+
+ int read = 0;
+ if (msg1len !=0) {
+ read = parser.execute(settings, msg1);
+ if (mes.upgrade() && parser.upgrade) {
+ // Messages have a settings() that checks itself...
+ check(1 == mes.num_called);
+ continue;
+ }
+ check(read == msg1len);
+ }
+
+ read = parser.execute(settings, msg2);
+ if (mes.upgrade() && parser.upgrade) {
+ check(1 == mes.num_called);
+ continue;
+ }
+
+ check( mes.raw.length - msg1len, read);
+
+ ByteBuffer empty = Util.empty();
+ read = parser.execute(settings, empty);
+
+ if (mes.upgrade() && parser.upgrade) {
+ check(1 == mes.num_called);
+ continue;
+ }
+ check(empty.position() == empty.limit());
+ check(0 == read);
+ check(1 == mes.num_called);
+
+ }
+ }
+
+ static void test_multiple3(Message r1, Message r2, Message r3) {
+ int message_count = 1;
+ if (!r1.upgrade()) {
+ message_count++;
+ if (!r2.upgrade()) {
+ message_count++;
+ }
+ }
+ boolean has_upgrade = (message_count < 3 || r3.upgrade());
+
+ ByteList blist = new ByteList();
+ blist.addAll(r1.raw);
+ blist.addAll(r2.raw);
+ blist.addAll(r3.raw);
+
+ byte [] raw = blist.toArray();
+ ByteBuffer buf = ByteBuffer.wrap(raw);
+
+ Util.Settings settings = Util.settings();
+ HTTPParser parser = new HTTPParser(r1.type);
+
+ int read = parser.execute(settings, buf);
+ if (has_upgrade && parser.upgrade) {
+ raw = upgrade_message_fix(raw, read, r1,r2,r3);
+ check(settings.numCalled == message_count);
+ return;
+ }
+
+ check(read == raw.length);
+
+ buf = Util.empty();
+ read = parser.execute(settings, buf);
+ if (has_upgrade && parser.upgrade) {
+ check(settings.numCalled == message_count);
+ return;
+ }
+
+ check(0 == read);
+ check(settings.numCalled == message_count);
+ }
+
+ /* Given a sequence of bytes and the number of these that we were able to
+ * parse, verify that upgrade bodies are correct.
+ */
+ static byte [] upgrade_message_fix(byte[] body, int nread, Message... msgs) {
+ int off = 0;
+ for (Message m : msgs) {
+ off += m.raw.length;
+ if (m.upgrade()) {
+ off -= m.upgrade.length;
+ // Original C:
+ // Check the portion of the response after its specified upgrade
+ // if (!check_str_eq(m, "upgrade", body + off, body + nread)) {
+ // abort();
+ // }
+ // to me, this seems to be equivalent to comparing off and nread ...
+ check (off, nread);
+
+ // Original C:
+ // Fix up the response so that message_eq() will verify the beginning
+ // of the upgrade */
+ //
+ // *(body + nread + strlen(m->upgrade)) = '\0';
+ // This only shortens body so the strlen check passes.
+ return new byte[off];
+
+ }
+ }
+ return null;
+ }
+//upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
+// va_list ap;
+// size_t i;
+// size_t off = 0;
+//
+// va_start(ap, nmsgs);
+//
+// for (i = 0; i < nmsgs; i++) {
+// struct message *m = va_arg(ap, struct message *);
+//
+// off += strlen(m->raw);
+//
+// if (m->upgrade) {
+// off -= strlen(m->upgrade);
+//
+// /* Check the portion of the response after its specified upgrade */
+// if (!check_str_eq(m, "upgrade", body + off, body + nread)) {
+// abort();
+// }
+//
+// /* Fix up the response so that message_eq() will verify the beginning
+// * of the upgrade */
+// *(body + nread + strlen(m->upgrade)) = '\0';
+// messages[num_messages -1 ].upgrade = body + nread;
+//
+// va_end(ap);
+// return;
+// }
+// }
+//
+// va_end(ap);
+// printf("\n\n*** Error: expected a message with upgrade ***\n");
+//
+// abort();
+//}
+ static void p (Object o) {
+ System.out.println(o);
+ }
+
+ static Settings settings() {
+ return new Settings();
+ }
+ static Message find(List<Message> list, String name) {
+ for (Message m : list) {
+ if (name.equals(m.name)) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ static class Settings extends ParserSettings {
+ public int numCalled;
+ public int bodyCount;
+ Settings() {
+ this.on_message_complete = new HTTPCallback() {
+ public int cb (HTTPParser parser) {
+ numCalled++;
+ return 0;
+ }
+ };
+ this.on_body = new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len) {
+ bodyCount += len;
+ return 0;
+ }
+ };
+ }
+
+ int numCalled () {
+ return this.numCalled;
+ }
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java
new file mode 100644
index 0000000..fc8f081
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java
@@ -0,0 +1,59 @@
+package http_parser.lolevel;
+
+import java.nio.*;
+import java.util.*;
+
+import http_parser.ParserType;
+
+import static http_parser.lolevel.Util.*;
+
+public class WrongContentLength {
+ static final String contentLength = "GET / HTTP/1.0\r\n" +
+ "Content-Length: 5\r\n" +
+ "\r\n" +
+ "hello" +
+ "hello_again";
+ static void test () {
+ p(WrongContentLength.class);
+ HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST);
+ ByteBuffer buf = buffer(contentLength);
+
+ Settings settings = new Settings();
+
+ int read = parser.execute(settings, buf);
+ check (settings.msg_cmplt_called);
+ check ("invalid method".equals(settings.err));
+
+ }
+ public static void main(String [] args) {
+ test();
+ }
+
+ static class Settings extends ParserSettings {
+ public int bodyCount;
+ public boolean msg_cmplt_called;
+ public String err;
+ Settings () {
+ this.on_message_complete = new HTTPCallback () {
+ public int cb (HTTPParser p) {
+ check (5 == bodyCount);
+ msg_cmplt_called = true;
+ return 0;
+ }
+ };
+ this.on_body = new HTTPDataCallback() {
+ public int cb (HTTPParser p, ByteBuffer b, int pos, int len) {
+ bodyCount += len;
+ check ("hello".equals(str(b, pos, len)));
+ return 0;
+ }
+ };
+ this.on_error = new HTTPErrorCallback() {
+ public void cb (HTTPParser p, String mes, ByteBuffer b, int i) {
+ err = mes;
+ }
+ };
+ }
+ }
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/test.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/test.c
new file mode 100644
index 0000000..3840747
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/test.c
@@ -0,0 +1,3425 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h> /* rand */
+#include <string.h>
+#include <stdarg.h>
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+
+#define MAX_HEADERS 13
+#define MAX_ELEMENT_SIZE 2048
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+static http_parser *parser;
+
+struct message {
+ const char *name; // for debugging purposes
+ const char *raw;
+ enum http_parser_type type;
+ enum http_method method;
+ int status_code;
+ char request_path[MAX_ELEMENT_SIZE];
+ char request_url[MAX_ELEMENT_SIZE];
+ char fragment[MAX_ELEMENT_SIZE];
+ char query_string[MAX_ELEMENT_SIZE];
+ char body[MAX_ELEMENT_SIZE];
+ size_t body_size;
+ const char *host;
+ const char *userinfo;
+ uint16_t port;
+ int num_headers;
+ enum { NONE=0, FIELD, VALUE } last_header_element;
+ char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
+ int should_keep_alive;
+
+ const char *upgrade; // upgraded body
+
+ unsigned short http_major;
+ unsigned short http_minor;
+
+ int message_begin_cb_called;
+ int headers_complete_cb_called;
+ int message_complete_cb_called;
+ int message_complete_on_eof;
+ int body_is_final;
+};
+
+static int currently_parsing_eof;
+
+static struct message messages[5];
+static int num_messages;
+static http_parser_settings *current_pause_parser;
+
+/* * R E Q U E S T S * */
+const struct message requests[] =
+#define CURL_GET 0
+{ {.name= "curl get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test HTTP/1.1\r\n"
+ "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
+ "Host: 0.0.0.0=5000\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 3
+ ,.headers=
+ { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
+ , { "Host", "0.0.0.0=5000" }
+ , { "Accept", "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define FIREFOX_GET 1
+, {.name= "firefox get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /favicon.ico HTTP/1.1\r\n"
+ "Host: 0.0.0.0=5000\r\n"
+ "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+ "Accept-Language: en-us,en;q=0.5\r\n"
+ "Accept-Encoding: gzip,deflate\r\n"
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
+ "Keep-Alive: 300\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/favicon.ico"
+ ,.request_url= "/favicon.ico"
+ ,.num_headers= 8
+ ,.headers=
+ { { "Host", "0.0.0.0=5000" }
+ , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
+ , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
+ , { "Accept-Language", "en-us,en;q=0.5" }
+ , { "Accept-Encoding", "gzip,deflate" }
+ , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
+ , { "Keep-Alive", "300" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= ""
+ }
+
+#define DUMBFUCK 2
+, {.name= "dumbfuck"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
+ "aaaaaaaaaaaaa:++++++++++\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/dumbfuck"
+ ,.request_url= "/dumbfuck"
+ ,.num_headers= 1
+ ,.headers=
+ { { "aaaaaaaaaaaaa", "++++++++++" }
+ }
+ ,.body= ""
+ }
+
+#define FRAGMENT_IN_URI 3
+, {.name= "fragment in url"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "page=1"
+ ,.fragment= "posts-17408"
+ ,.request_path= "/forums/1/topics/2375"
+ /* XXX request url does include fragment? */
+ ,.request_url= "/forums/1/topics/2375?page=1#posts-17408"
+ ,.num_headers= 0
+ ,.body= ""
+ }
+
+#define GET_NO_HEADERS_NO_BODY 4
+, {.name= "get no headers no body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_no_headers_no_body/world"
+ ,.request_url= "/get_no_headers_no_body/world"
+ ,.num_headers= 0
+ ,.body= ""
+ }
+
+#define GET_ONE_HEADER_NO_BODY 5
+, {.name= "get one header no body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_one_header_no_body"
+ ,.request_url= "/get_one_header_no_body"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Accept" , "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define GET_FUNKY_CONTENT_LENGTH 6
+, {.name= "get funky content length body hello"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
+ "conTENT-Length: 5\r\n"
+ "\r\n"
+ "HELLO"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_funky_content_length_body_hello"
+ ,.request_url= "/get_funky_content_length_body_hello"
+ ,.num_headers= 1
+ ,.headers=
+ { { "conTENT-Length" , "5" }
+ }
+ ,.body= "HELLO"
+ }
+
+#define POST_IDENTITY_BODY_WORLD 7
+, {.name= "post identity body world"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "Transfer-Encoding: identity\r\n"
+ "Content-Length: 5\r\n"
+ "\r\n"
+ "World"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= "q=search"
+ ,.fragment= "hey"
+ ,.request_path= "/post_identity_body_world"
+ ,.request_url= "/post_identity_body_world?q=search#hey"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Accept", "*/*" }
+ , { "Transfer-Encoding", "identity" }
+ , { "Content-Length", "5" }
+ }
+ ,.body= "World"
+ }
+
+#define POST_CHUNKED_ALL_YOUR_BASE 8
+, {.name= "post - chunked body: all your base are belong to us"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "1e\r\nall your base are belong to us\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/post_chunked_all_your_base"
+ ,.request_url= "/post_chunked_all_your_base"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding" , "chunked" }
+ }
+ ,.body= "all your base are belong to us"
+ }
+
+#define TWO_CHUNKS_MULT_ZERO_END 9
+, {.name= "two chunks ; triple zero ending"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5\r\nhello\r\n"
+ "6\r\n world\r\n"
+ "000\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/two_chunks_mult_zero_end"
+ ,.request_url= "/two_chunks_mult_zero_end"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "hello world"
+ }
+
+#define CHUNKED_W_TRAILING_HEADERS 10
+, {.name= "chunked with trailing headers. blech."
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5\r\nhello\r\n"
+ "6\r\n world\r\n"
+ "0\r\n"
+ "Vary: *\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/chunked_w_trailing_headers"
+ ,.request_url= "/chunked_w_trailing_headers"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ , { "Vary", "*" }
+ , { "Content-Type", "text/plain" }
+ }
+ ,.body= "hello world"
+ }
+
+#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
+, {.name= "with bullshit after the length"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
+ "6; blahblah; blah\r\n world\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/chunked_w_bullshit_after_length"
+ ,.request_url= "/chunked_w_bullshit_after_length"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "hello world"
+ }
+
+#define WITH_QUOTES 12
+, {.name= "with quotes"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "foo=\"bar\""
+ ,.fragment= ""
+ ,.request_path= "/with_\"stupid\"_quotes"
+ ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define APACHEBENCH_GET 13
+/* The server receiving this request SHOULD NOT wait for EOF
+ * to know that content-length == 0.
+ * How to represent this in a unit test? message_complete_on_eof
+ * Compare with NO_CONTENT_LENGTH_RESPONSE.
+ */
+, {.name = "apachebench get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test HTTP/1.0\r\n"
+ "Host: 0.0.0.0:5000\r\n"
+ "User-Agent: ApacheBench/2.3\r\n"
+ "Accept: */*\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 3
+ ,.headers= { { "Host", "0.0.0.0:5000" }
+ , { "User-Agent", "ApacheBench/2.3" }
+ , { "Accept", "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define QUERY_URL_WITH_QUESTION_MARK_GET 14
+/* Some clients include '?' characters in query strings.
+ */
+, {.name = "query url with question mark"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "foo=bar?baz"
+ ,.fragment= ""
+ ,.request_path= "/test.cgi"
+ ,.request_url= "/test.cgi?foo=bar?baz"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define PREFIX_NEWLINE_GET 15
+/* Some clients, especially after a POST in a keep-alive connection,
+ * will send an extra CRLF before the next request
+ */
+, {.name = "newline prefix get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define UPGRADE_REQUEST 16
+, {.name = "upgrade request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 7
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Host", "example.com" }
+ , { "Connection", "Upgrade" }
+ , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" }
+ , { "Sec-WebSocket-Protocol", "sample" }
+ , { "Upgrade", "WebSocket" }
+ , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" }
+ , { "Origin", "http://example.com" }
+ }
+ ,.body= ""
+ }
+
+#define CONNECT_REQUEST 17
+, {.name = "connect request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ "some data\r\n"
+ "and yet even more data"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "0-home0.netscape.com:443"
+ ,.num_headers= 2
+ ,.upgrade="some data\r\nand yet even more data"
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+
+#define REPORT_REQ 18
+, {.name= "report request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "REPORT /test HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_REPORT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define NO_HTTP_VERSION 19
+, {.name= "request with no http version"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 0
+ ,.http_minor= 9
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define MSEARCH_REQ 20
+, {.name= "m-search request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: \"ssdp:all\"\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_MSEARCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "*"
+ ,.request_url= "*"
+ ,.num_headers= 3
+ ,.headers= { { "HOST", "239.255.255.250:1900" }
+ , { "MAN", "\"ssdp:discover\"" }
+ , { "ST", "\"ssdp:all\"" }
+ }
+ ,.body= ""
+ }
+
+#define LINE_FOLDING_IN_HEADER 21
+, {.name= "line folding in header value"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET / HTTP/1.1\r\n"
+ "Line1: abc\r\n"
+ "\tdef\r\n"
+ " ghi\r\n"
+ "\t\tjkl\r\n"
+ " mno \r\n"
+ "\t \tqrs\r\n"
+ "Line2: \t line2\t\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 2
+ ,.headers= { { "Line1", "abcdefghijklmno qrs" }
+ , { "Line2", "line2\t" }
+ }
+ ,.body= ""
+ }
+
+
+#define QUERY_TERMINATED_HOST 22
+, {.name= "host terminated by a query string"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "hail=all"
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org?hail=all"
+ ,.host= "hypnotoad.org"
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define QUERY_TERMINATED_HOSTPORT 23
+, {.name= "host:port terminated by a query string"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "hail=all"
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org:1234?hail=all"
+ ,.host= "hypnotoad.org"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define SPACE_TERMINATED_HOSTPORT 24
+, {.name= "host:port terminated by a space"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org:1234"
+ ,.host= "hypnotoad.org"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define PATCH_REQ 25
+, {.name = "PATCH request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "PATCH /file.txt HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/example\r\n"
+ "If-Match: \"e0023aa4e\"\r\n"
+ "Content-Length: 10\r\n"
+ "\r\n"
+ "cccccccccc"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_PATCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/file.txt"
+ ,.request_url= "/file.txt"
+ ,.num_headers= 4
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/example" }
+ , { "If-Match", "\"e0023aa4e\"" }
+ , { "Content-Length", "10" }
+ }
+ ,.body= "cccccccccc"
+ }
+
+#define CONNECT_CAPS_REQUEST 26
+, {.name = "connect caps request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "HOME0.NETSCAPE.COM:443"
+ ,.num_headers= 2
+ ,.upgrade=""
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+
+#if !HTTP_PARSER_STRICT
+#define UTF8_PATH_REQ 27
+, {.name= "utf-8 path request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n"
+ "Host: github.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "q=1"
+ ,.fragment= "narf"
+ ,.request_path= "/δ¶/δt/pope"
+ ,.request_url= "/δ¶/δt/pope?q=1#narf"
+ ,.num_headers= 1
+ ,.headers= { {"Host", "github.com" }
+ }
+ ,.body= ""
+ }
+
+#define HOSTNAME_UNDERSCORE 28
+, {.name = "hostname underscore"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "home_0.netscape.com:443"
+ ,.num_headers= 2
+ ,.upgrade=""
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+#endif /* !HTTP_PARSER_STRICT */
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29
+, {.name = "eat CRLF between requests, no \"Connection: close\" header"
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 4\r\n"
+ "\r\n"
+ "q=42\r\n" /* note the trailing CRLF */
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 3
+ ,.upgrade= 0
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/x-www-form-urlencoded" }
+ , { "Content-Length", "4" }
+ }
+ ,.body= "q=42"
+ }
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30
+, {.name = "eat CRLF between requests even if \"Connection: close\" is set"
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 4\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "q=42\r\n" /* note the trailing CRLF */
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 4
+ ,.upgrade= 0
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/x-www-form-urlencoded" }
+ , { "Content-Length", "4" }
+ , { "Connection", "close" }
+ }
+ ,.body= "q=42"
+ }
+
+#define PURGE_REQ 31
+, {.name = "PURGE request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "PURGE /file.txt HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_PURGE
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/file.txt"
+ ,.request_url= "/file.txt"
+ ,.num_headers= 1
+ ,.headers= { { "Host", "www.example.com" } }
+ ,.body= ""
+ }
+
+#define SEARCH_REQ 32
+, {.name = "SEARCH request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "SEARCH / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_SEARCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 1
+ ,.headers= { { "Host", "www.example.com" } }
+ ,.body= ""
+ }
+
+#define PROXY_WITH_BASIC_AUTH 33
+, {.name= "host:port and basic_auth"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.fragment= ""
+ ,.request_path= "/toto"
+ ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto"
+ ,.host= "hypnotoad.org"
+ ,.userinfo= "a%12:b!&*$"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+
+, {.name= NULL } /* sentinel */
+};
+
+/* * R E S P O N S E S * */
+const struct message responses[] =
+#define GOOGLE_301 0
+{ {.name= "google 301"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: http://www.google.com/\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
+ "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
+ "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */
+ "Cache-Control: public, max-age=2592000\r\n"
+ "Server: gws\r\n"
+ "Content-Length: 219 \r\n"
+ "\r\n"
+ "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+ "<H1>301 Moved</H1>\n"
+ "The document has moved\n"
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+ "</BODY></HTML>\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 301
+ ,.num_headers= 8
+ ,.headers=
+ { { "Location", "http://www.google.com/" }
+ , { "Content-Type", "text/html; charset=UTF-8" }
+ , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
+ , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
+ , { "X-$PrototypeBI-Version", "1.6.0.3" }
+ , { "Cache-Control", "public, max-age=2592000" }
+ , { "Server", "gws" }
+ , { "Content-Length", "219 " }
+ }
+ ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+ "<H1>301 Moved</H1>\n"
+ "The document has moved\n"
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+ "</BODY></HTML>\r\n"
+ }
+
+#define NO_CONTENT_LENGTH_RESPONSE 1
+/* The client should wait for the server's EOF. That is, when content-length
+ * is not specified, and "Connection: close", the end of body is specified
+ * by the EOF.
+ * Compare with APACHEBENCH_GET
+ */
+, {.name= "no content-length response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
+ "Server: Apache\r\n"
+ "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+ " <SOAP-ENV:Body>\n"
+ " <SOAP-ENV:Fault>\n"
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
+ " <faultstring>Client Error</faultstring>\n"
+ " </SOAP-ENV:Fault>\n"
+ " </SOAP-ENV:Body>\n"
+ "</SOAP-ENV:Envelope>"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 5
+ ,.headers=
+ { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
+ , { "Server", "Apache" }
+ , { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
+ , { "Content-Type", "text/xml; charset=utf-8" }
+ , { "Connection", "close" }
+ }
+ ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+ " <SOAP-ENV:Body>\n"
+ " <SOAP-ENV:Fault>\n"
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
+ " <faultstring>Client Error</faultstring>\n"
+ " </SOAP-ENV:Fault>\n"
+ " </SOAP-ENV:Body>\n"
+ "</SOAP-ENV:Envelope>"
+ }
+
+#define NO_HEADERS_NO_BODY_404 2
+, {.name= "404 no headers no body"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 404
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_REASON_PHRASE 3
+, {.name= "301 no response phrase"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 301\r\n\r\n"
+ ,.should_keep_alive = FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 301
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define TRAILING_SPACE_ON_CHUNKED_BODY 4
+, {.name="200 trailing space on chunked body"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "25 \r\n"
+ "This is the data in the first chunk\r\n"
+ "\r\n"
+ "1C\r\n"
+ "and this is the second one\r\n"
+ "\r\n"
+ "0 \r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 2
+ ,.headers=
+ { {"Content-Type", "text/plain" }
+ , {"Transfer-Encoding", "chunked" }
+ }
+ ,.body_size = 37+28
+ ,.body =
+ "This is the data in the first chunk\r\n"
+ "and this is the second one\r\n"
+
+ }
+
+#define NO_CARRIAGE_RET 5
+, {.name="no carriage ret"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\n"
+ "Content-Type: text/html; charset=utf-8\n"
+ "Connection: close\n"
+ "\n"
+ "these headers are from http://news.ycombinator.com/"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 2
+ ,.headers=
+ { {"Content-Type", "text/html; charset=utf-8" }
+ , {"Connection", "close" }
+ }
+ ,.body= "these headers are from http://news.ycombinator.com/"
+ }
+
+#define PROXY_CONNECTION 6
+, {.name="proxy connection"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Content-Length: 11\r\n"
+ "Proxy-Connection: close\r\n"
+ "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n"
+ "\r\n"
+ "hello world"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 4
+ ,.headers=
+ { {"Content-Type", "text/html; charset=UTF-8" }
+ , {"Content-Length", "11" }
+ , {"Proxy-Connection", "close" }
+ , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"}
+ }
+ ,.body= "hello world"
+ }
+
+#define UNDERSTORE_HEADER_KEY 7
+ // shown by
+ // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;"
+, {.name="underscore header key"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Server: DCLK-AdSvr\r\n"
+ "Content-Type: text/xml\r\n"
+ "Content-Length: 0\r\n"
+ "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 4
+ ,.headers=
+ { {"Server", "DCLK-AdSvr" }
+ , {"Content-Type", "text/xml" }
+ , {"Content-Length", "0" }
+ , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" }
+ }
+ ,.body= ""
+ }
+
+#define BONJOUR_MADAME_FR 8
+/* The client should not merge two headers fields when the first one doesn't
+ * have a value.
+ */
+, {.name= "bonjourmadame.fr"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 301 Moved Permanently\r\n"
+ "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n"
+ "Server: Apache/2.2.3 (Red Hat)\r\n"
+ "Cache-Control: public\r\n"
+ "Pragma: \r\n"
+ "Location: http://www.bonjourmadame.fr/\r\n"
+ "Vary: Accept-Encoding\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 301
+ ,.num_headers= 9
+ ,.headers=
+ { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" }
+ , { "Server", "Apache/2.2.3 (Red Hat)" }
+ , { "Cache-Control", "public" }
+ , { "Pragma", "" }
+ , { "Location", "http://www.bonjourmadame.fr/" }
+ , { "Vary", "Accept-Encoding" }
+ , { "Content-Length", "0" }
+ , { "Content-Type", "text/html; charset=UTF-8" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= ""
+ }
+
+#define RES_FIELD_UNDERSCORE 9
+/* Should handle spaces in header fields */
+, {.name= "field underscore"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n"
+ "Server: Apache\r\n"
+ "Cache-Control: no-cache, must-revalidate\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n"
+ "Vary: Accept-Encoding\r\n"
+ "_eep-Alive: timeout=45\r\n" /* semantic value ignored */
+ "_onnection: Keep-Alive\r\n" /* semantic value ignored */
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Type: text/html\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "0\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 11
+ ,.headers=
+ { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" }
+ , { "Server", "Apache" }
+ , { "Cache-Control", "no-cache, must-revalidate" }
+ , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" }
+ , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" }
+ , { "Vary", "Accept-Encoding" }
+ , { "_eep-Alive", "timeout=45" }
+ , { "_onnection", "Keep-Alive" }
+ , { "Transfer-Encoding", "chunked" }
+ , { "Content-Type", "text/html" }
+ , { "Connection", "close" }
+ }
+ ,.body= ""
+ }
+
+#define NON_ASCII_IN_STATUS_LINE 10
+/* Should handle non-ASCII in status line */
+, {.name= "non-ASCII in status line"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n"
+ "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n"
+ "Content-Length: 0\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 500
+ ,.num_headers= 3
+ ,.headers=
+ { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" }
+ , { "Content-Length", "0" }
+ , { "Connection", "close" }
+ }
+ ,.body= ""
+ }
+
+#define HTTP_VERSION_0_9 11
+/* Should handle HTTP/0.9 */
+, {.name= "http version 0.9"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/0.9 200 OK\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 0
+ ,.http_minor= 9
+ ,.status_code= 200
+ ,.num_headers= 0
+ ,.headers=
+ {}
+ ,.body= ""
+ }
+
+#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12
+/* The client should wait for the server's EOF. That is, when neither
+ * content-length nor transfer-encoding is specified, the end of body
+ * is specified by the EOF.
+ */
+, {.name= "neither content-length nor transfer-encoding response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "hello world"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 1
+ ,.headers=
+ { { "Content-Type", "text/plain" }
+ }
+ ,.body= "hello world"
+ }
+
+#define NO_BODY_HTTP10_KA_200 13
+, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 200 OK\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 200
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "keep-alive" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP10_KA_204 14
+, {.name= "HTTP/1.0 with keep-alive and a 204 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 204 No content\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 204
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "keep-alive" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_200 15
+, {.name= "HTTP/1.1 with an EOF-terminated 200 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 0
+ ,.headers={}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_204 16
+, {.name= "HTTP/1.1 with a 204 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 204 No content\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 204
+ ,.num_headers= 0
+ ,.headers={}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_NOKA_204 17
+, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 204 No content\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 204
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "close" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_CHUNKED_200 18
+, {.name= "HTTP/1.1 with chunked endocing and a 200 response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#if !HTTP_PARSER_STRICT
+#define SPACE_IN_FIELD_RES 19
+/* Should handle spaces in header fields */
+, {.name= "field space"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Server: Microsoft-IIS/6.0\r\n"
+ "X-Powered-By: ASP.NET\r\n"
+ "en-US Content-Type: text/xml\r\n" /* this is the problem */
+ "Content-Type: text/xml\r\n"
+ "Content-Length: 16\r\n"
+ "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ "<xml>hello</xml>" /* fake body */
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.num_headers= 7
+ ,.headers=
+ { { "Server", "Microsoft-IIS/6.0" }
+ , { "X-Powered-By", "ASP.NET" }
+ , { "en-US Content-Type", "text/xml" }
+ , { "Content-Type", "text/xml" }
+ , { "Content-Length", "16" }
+ , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= "<xml>hello</xml>"
+ }
+#endif /* !HTTP_PARSER_STRICT */
+
+, {.name= NULL } /* sentinel */
+};
+
+/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
+ * define it ourselves.
+ */
+size_t
+strnlen(const char *s, size_t maxlen)
+{
+ const char *p;
+
+ p = memchr(s, '\0', maxlen);
+ if (p == NULL)
+ return maxlen;
+
+ return p - s;
+}
+
+size_t
+strlncat(char *dst, size_t len, const char *src, size_t n)
+{
+ size_t slen;
+ size_t dlen;
+ size_t rlen;
+ size_t ncpy;
+
+ slen = strnlen(src, n);
+ dlen = strnlen(dst, len);
+
+ if (dlen < len) {
+ rlen = len - dlen;
+ ncpy = slen < rlen ? slen : (rlen - 1);
+ memcpy(dst + dlen, src, ncpy);
+ dst[dlen + ncpy] = '\0';
+ }
+
+ assert(len > slen + dlen);
+ return slen + dlen;
+}
+
+size_t
+strlcat(char *dst, const char *src, size_t len)
+{
+ return strlncat(dst, len, src, (size_t) -1);
+}
+
+size_t
+strlncpy(char *dst, size_t len, const char *src, size_t n)
+{
+ size_t slen;
+ size_t ncpy;
+
+ slen = strnlen(src, n);
+
+ if (len > 0) {
+ ncpy = slen < len ? slen : (len - 1);
+ memcpy(dst, src, ncpy);
+ dst[ncpy] = '\0';
+ }
+
+ assert(len > slen);
+ return slen;
+}
+
+size_t
+strlcpy(char *dst, const char *src, size_t len)
+{
+ return strlncpy(dst, len, src, (size_t) -1);
+}
+
+int
+request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ strlncat(messages[num_messages].request_url,
+ sizeof(messages[num_messages].request_url),
+ buf,
+ len);
+ return 0;
+}
+
+int
+status_complete_cb (http_parser *p) {
+ assert(p == parser);
+ p->data++;
+ return 0;
+}
+
+int
+header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ struct message *m = &messages[num_messages];
+
+ if (m->last_header_element != FIELD)
+ m->num_headers++;
+
+ strlncat(m->headers[m->num_headers-1][0],
+ sizeof(m->headers[m->num_headers-1][0]),
+ buf,
+ len);
+
+ m->last_header_element = FIELD;
+
+ return 0;
+}
+
+int
+header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ struct message *m = &messages[num_messages];
+
+ strlncat(m->headers[m->num_headers-1][1],
+ sizeof(m->headers[m->num_headers-1][1]),
+ buf,
+ len);
+
+ m->last_header_element = VALUE;
+
+ return 0;
+}
+
+void
+check_body_is_final (const http_parser *p)
+{
+ if (messages[num_messages].body_is_final) {
+ fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+ "on last on_body callback call "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+ messages[num_messages].body_is_final = http_body_is_final(p);
+}
+
+int
+body_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ strlncat(messages[num_messages].body,
+ sizeof(messages[num_messages].body),
+ buf,
+ len);
+ messages[num_messages].body_size += len;
+ check_body_is_final(p);
+ // printf("body_cb: '%s'\n", requests[num_messages].body);
+ return 0;
+}
+
+int
+count_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ assert(buf);
+ messages[num_messages].body_size += len;
+ check_body_is_final(p);
+ return 0;
+}
+
+int
+message_begin_cb (http_parser *p)
+{
+ assert(p == parser);
+ messages[num_messages].message_begin_cb_called = TRUE;
+ return 0;
+}
+
+int
+headers_complete_cb (http_parser *p)
+{
+ assert(p == parser);
+ messages[num_messages].method = parser->method;
+ messages[num_messages].status_code = parser->status_code;
+ messages[num_messages].http_major = parser->http_major;
+ messages[num_messages].http_minor = parser->http_minor;
+ messages[num_messages].headers_complete_cb_called = TRUE;
+ messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+ return 0;
+}
+
+int
+message_complete_cb (http_parser *p)
+{
+ assert(p == parser);
+ if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
+ {
+ fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
+ "value in both on_message_complete and on_headers_complete "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+
+ if (messages[num_messages].body_size &&
+ http_body_is_final(p) &&
+ !messages[num_messages].body_is_final)
+ {
+ fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+ "on last on_body callback call "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+
+ messages[num_messages].message_complete_cb_called = TRUE;
+
+ messages[num_messages].message_complete_on_eof = currently_parsing_eof;
+
+ num_messages++;
+ return 0;
+}
+
+/* These dontcall_* callbacks exist so that we can verify that when we're
+ * paused, no additional callbacks are invoked */
+int
+dontcall_message_begin_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_headers_complete_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_headers_complete() called on paused "
+ "parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_message_complete_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_message_complete() called on paused "
+ "parser ***\n\n");
+ abort();
+}
+
+static http_parser_settings settings_dontcall =
+ {.on_message_begin = dontcall_message_begin_cb
+ ,.on_header_field = dontcall_header_field_cb
+ ,.on_header_value = dontcall_header_value_cb
+ ,.on_url = dontcall_request_url_cb
+ ,.on_body = dontcall_body_cb
+ ,.on_headers_complete = dontcall_headers_complete_cb
+ ,.on_message_complete = dontcall_message_complete_cb
+ };
+
+/* These pause_* callbacks always pause the parser and just invoke the regular
+ * callback that tracks content. Before returning, we overwrite the parser
+ * settings to point to the _dontcall variety so that we can verify that
+ * the pause actually did, you know, pause. */
+int
+pause_message_begin_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return message_begin_cb(p);
+}
+
+int
+pause_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return header_field_cb(p, buf, len);
+}
+
+int
+pause_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return header_value_cb(p, buf, len);
+}
+
+int
+pause_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return request_url_cb(p, buf, len);
+}
+
+int
+pause_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return body_cb(p, buf, len);
+}
+
+int
+pause_headers_complete_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return headers_complete_cb(p);
+}
+
+int
+pause_message_complete_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return message_complete_cb(p);
+}
+
+static http_parser_settings settings_pause =
+ {.on_message_begin = pause_message_begin_cb
+ ,.on_header_field = pause_header_field_cb
+ ,.on_header_value = pause_header_value_cb
+ ,.on_url = pause_request_url_cb
+ ,.on_body = pause_body_cb
+ ,.on_headers_complete = pause_headers_complete_cb
+ ,.on_message_complete = pause_message_complete_cb
+ };
+
+static http_parser_settings settings =
+ {.on_message_begin = message_begin_cb
+ ,.on_header_field = header_field_cb
+ ,.on_header_value = header_value_cb
+ ,.on_url = request_url_cb
+ ,.on_body = body_cb
+ ,.on_headers_complete = headers_complete_cb
+ ,.on_message_complete = message_complete_cb
+ };
+
+static http_parser_settings settings_count_body =
+ {.on_message_begin = message_begin_cb
+ ,.on_header_field = header_field_cb
+ ,.on_header_value = header_value_cb
+ ,.on_url = request_url_cb
+ ,.on_body = count_body_cb
+ ,.on_headers_complete = headers_complete_cb
+ ,.on_message_complete = message_complete_cb
+ };
+
+static http_parser_settings settings_null =
+ {.on_message_begin = 0
+ ,.on_header_field = 0
+ ,.on_header_value = 0
+ ,.on_url = 0
+ ,.on_body = 0
+ ,.on_headers_complete = 0
+ ,.on_message_complete = 0
+ };
+
+void
+parser_init (enum http_parser_type type)
+{
+ num_messages = 0;
+
+ assert(parser == NULL);
+
+ parser = malloc(sizeof(http_parser));
+
+ http_parser_init(parser, type);
+
+ memset(&messages, 0, sizeof messages);
+
+}
+
+void
+parser_free ()
+{
+ assert(parser);
+ free(parser);
+ parser = NULL;
+}
+
+size_t parse (const char *buf, size_t len)
+{
+ size_t nparsed;
+ currently_parsing_eof = (len == 0);
+ nparsed = http_parser_execute(parser, &settings, buf, len);
+ return nparsed;
+}
+
+size_t parse_count_body (const char *buf, size_t len)
+{
+ size_t nparsed;
+ currently_parsing_eof = (len == 0);
+ nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
+ return nparsed;
+}
+
+size_t parse_pause (const char *buf, size_t len)
+{
+ size_t nparsed;
+ http_parser_settings s = settings_pause;
+
+ currently_parsing_eof = (len == 0);
+ current_pause_parser = &s;
+ nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
+ return nparsed;
+}
+
+static inline int
+check_str_eq (const struct message *m,
+ const char *prop,
+ const char *expected,
+ const char *found) {
+ if ((expected == NULL) != (found == NULL)) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected %s\n", (expected == NULL) ? "NULL" : expected);
+ printf(" found %s\n", (found == NULL) ? "NULL" : found);
+ return 0;
+ }
+ if (expected != NULL && 0 != strcmp(expected, found)) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected '%s'\n", expected);
+ printf(" found '%s'\n", found);
+ return 0;
+ }
+ return 1;
+}
+
+static inline int
+check_num_eq (const struct message *m,
+ const char *prop,
+ int expected,
+ int found) {
+ if (expected != found) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected %d\n", expected);
+ printf(" found %d\n", found);
+ return 0;
+ }
+ return 1;
+}
+
+#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
+ if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
+ if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \
+do { \
+ char ubuf[256]; \
+ \
+ if ((u)->field_set & (1 << (fn))) { \
+ memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \
+ (u)->field_data[(fn)].len); \
+ ubuf[(u)->field_data[(fn)].len] = '\0'; \
+ } else { \
+ ubuf[0] = '\0'; \
+ } \
+ \
+ check_str_eq(expected, #prop, expected->prop, ubuf); \
+} while(0)
+
+int
+message_eq (int index, const struct message *expected)
+{
+ int i;
+ struct message *m = &messages[index];
+
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
+
+ if (expected->type == HTTP_REQUEST) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, method);
+ } else {
+ MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
+ }
+
+ MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
+ MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
+
+ assert(m->message_begin_cb_called);
+ assert(m->headers_complete_cb_called);
+ assert(m->message_complete_cb_called);
+
+
+ MESSAGE_CHECK_STR_EQ(expected, m, request_url);
+
+ /* Check URL components; we can't do this w/ CONNECT since it doesn't
+ * send us a well-formed URL.
+ */
+ if (*m->request_url && m->method != HTTP_CONNECT) {
+ struct http_parser_url u;
+
+ if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) {
+ fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n",
+ m->request_url);
+ abort();
+ }
+
+ if (expected->host) {
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST);
+ }
+
+ if (expected->userinfo) {
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO);
+ }
+
+ m->port = (u.field_set & (1 << UF_PORT)) ?
+ u.port : 0;
+
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY);
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT);
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH);
+ MESSAGE_CHECK_NUM_EQ(expected, m, port);
+ }
+
+ if (expected->body_size) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
+ } else {
+ MESSAGE_CHECK_STR_EQ(expected, m, body);
+ }
+
+ MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
+
+ int r;
+ for (i = 0; i < m->num_headers; i++) {
+ r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
+ if (!r) return 0;
+ r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
+ if (!r) return 0;
+ }
+
+ MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
+
+ return 1;
+}
+
+/* Given a sequence of varargs messages, return the number of them that the
+ * parser should successfully parse, taking into account that upgraded
+ * messages prevent all subsequent messages from being parsed.
+ */
+size_t
+count_parsed_messages(const size_t nmsgs, ...) {
+ size_t i;
+ va_list ap;
+
+ va_start(ap, nmsgs);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct message *m = va_arg(ap, struct message *);
+
+ if (m->upgrade) {
+ va_end(ap);
+ return i + 1;
+ }
+ }
+
+ va_end(ap);
+ return nmsgs;
+}
+
+/* Given a sequence of bytes and the number of these that we were able to
+ * parse, verify that upgrade bodies are correct.
+ */
+void
+upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
+ va_list ap;
+ size_t i;
+ size_t off = 0;
+
+ va_start(ap, nmsgs);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct message *m = va_arg(ap, struct message *);
+
+ off += strlen(m->raw);
+
+ if (m->upgrade) {
+ off -= strlen(m->upgrade);
+
+ /* Check the portion of the response after its specified upgrade */
+ if (!check_str_eq(m, "upgrade", body + off, body + nread)) {
+ abort();
+ }
+
+ /* Fix up the response so that message_eq() will verify the beginning
+ * of the upgrade */
+ *(body + nread + strlen(m->upgrade)) = '\0';
+ messages[num_messages -1 ].upgrade = body + nread;
+
+ va_end(ap);
+ return;
+ }
+ }
+
+ va_end(ap);
+ printf("\n\n*** Error: expected a message with upgrade ***\n");
+
+ abort();
+}
+
+static void
+print_error (const char *raw, size_t error_location)
+{
+ fprintf(stderr, "\n*** %s ***\n\n",
+ http_errno_description(HTTP_PARSER_ERRNO(parser)));
+
+ int this_line = 0, char_len = 0;
+ size_t i, j, len = strlen(raw), error_location_line = 0;
+ for (i = 0; i < len; i++) {
+ if (i == error_location) this_line = 1;
+ switch (raw[i]) {
+ case '\r':
+ char_len = 2;
+ fprintf(stderr, "\\r");
+ break;
+
+ case '\n':
+ char_len = 2;
+ fprintf(stderr, "\\n\n");
+
+ if (this_line) goto print;
+
+ error_location_line = 0;
+ continue;
+
+ default:
+ char_len = 1;
+ fputc(raw[i], stderr);
+ break;
+ }
+ if (!this_line) error_location_line += char_len;
+ }
+
+ fprintf(stderr, "[eof]\n");
+
+ print:
+ for (j = 0; j < error_location_line; j++) {
+ fputc(' ', stderr);
+ }
+ fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location);
+}
+
+void
+test_preserve_data (void)
+{
+ char my_data[] = "application-specific data";
+ http_parser parser;
+ parser.data = my_data;
+ http_parser_init(&parser, HTTP_REQUEST);
+ if (parser.data != my_data) {
+ printf("\n*** parser.data not preserved accross http_parser_init ***\n\n");
+ abort();
+ }
+}
+
+struct url_test {
+ const char *name;
+ const char *url;
+ int is_connect;
+ struct http_parser_url u;
+ int rv;
+};
+
+const struct url_test url_tests[] =
+{ {.name="proxy request"
+ ,.url="http://hostname/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 15, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy request with port"
+ ,.url="http://hostname:444/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+ ,.port=444
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 16, 3 } /* UF_PORT */
+ ,{ 19, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT request"
+ ,.url="hostname:443"
+ ,.is_connect=1
+ ,.u=
+ {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+ ,.port=443
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 8 } /* UF_HOST */
+ ,{ 9, 3 } /* UF_PORT */
+ ,{ 0, 0 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT request but not connect"
+ ,.url="hostname:443"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy ipv6 request"
+ ,.url="http://[1:2::3:4]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 17, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy ipv6 request with port"
+ ,.url="http://[1:2::3:4]:67/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+ ,.port=67
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 18, 2 } /* UF_PORT */
+ ,{ 20, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT ipv6 address"
+ ,.url="[1:2::3:4]:443"
+ ,.is_connect=1
+ ,.u=
+ {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+ ,.port=443
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 1, 8 } /* UF_HOST */
+ ,{ 11, 3 } /* UF_PORT */
+ ,{ 0, 0 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="ipv4 in ipv6 address"
+ ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 37 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 46, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="extra ? in query string"
+ ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,"
+ "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,"
+ "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 10 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 17, 12 } /* UF_PATH */
+ ,{ 30,187 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="space URL encoded"
+ ,.url="/toto.html?toto=a%20b"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_PATH) | (1<<UF_QUERY)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 10 } /* UF_PATH */
+ ,{ 11, 10 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+
+, {.name="URL fragment"
+ ,.url="/toto.html#titi"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 10 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 11, 4 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL fragment"
+ ,.url="http://www.webmasterworld.com/r.cgi?f=21&d=8405&url="
+ "http://www.example.com/index.html?foo=bar&hello=world#midpage"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\
+ (1<<UF_FRAGMENT)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 22 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 29, 6 } /* UF_PATH */
+ ,{ 36, 69 } /* UF_QUERY */
+ ,{106, 7 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL from node js url parser doc"
+ ,.url="http://host.com:8080/p/a/t/h?query=string#hash"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+ (1<<UF_QUERY) | (1<<UF_FRAGMENT)
+ ,.port=8080
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 16, 4 } /* UF_PORT */
+ ,{ 20, 8 } /* UF_PATH */
+ ,{ 29, 12 } /* UF_QUERY */
+ ,{ 42, 4 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL with basic auth from node js url parser doc"
+ ,.url="http://a:b@host.com:8080/p/a/t/h?query=string#hash"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+ (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO)
+ ,.port=8080
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 11, 8 } /* UF_HOST */
+ ,{ 20, 4 } /* UF_PORT */
+ ,{ 24, 8 } /* UF_PATH */
+ ,{ 33, 12 } /* UF_QUERY */
+ ,{ 46, 4 } /* UF_FRAGMENT */
+ ,{ 7, 3 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="double @"
+ ,.url="http://a:b@@hostname:443/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy empty host"
+ ,.url="http://:443/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy empty port"
+ ,.url="http://hostname:/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="CONNECT with basic auth"
+ ,.url="a:b@hostname:443"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT empty host"
+ ,.url=":443"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT empty port"
+ ,.url="hostname:"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT with extra bits"
+ ,.url="hostname:443/"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="space in URL"
+ ,.url="/foo bar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with space url encoded"
+ ,.url="http://a%20:b@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 14, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 22, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 6 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="carriage return in URL"
+ ,.url="/foo\rbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy double : in URL"
+ ,.url="http://hostname::443/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with double :"
+ ,.url="http://a::b@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 12, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 20, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 4 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="line feed in URL"
+ ,.url="/foo\nbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy empty basic auth"
+ ,.url="http://@hostname/fo"
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 16, 3 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+, {.name="proxy line feed in hostname"
+ ,.url="http://host\name/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy % in hostname"
+ ,.url="http://host%name/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy ; in hostname"
+ ,.url="http://host;ame/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with unreservedchars"
+ ,.url="http://a!;-_!=+$@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 17, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 25, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 9 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy only empty basic auth"
+ ,.url="http://@/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy only basic auth"
+ ,.url="http://toto@/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy emtpy hostname"
+ ,.url="http:///fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy = in URL"
+ ,.url="http://host=ame/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+#if HTTP_PARSER_STRICT
+
+, {.name="tab in URL"
+ ,.url="/foo\tbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="form feed in URL"
+ ,.url="/foo\fbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+#else /* !HTTP_PARSER_STRICT */
+
+, {.name="tab in URL"
+ ,.url="/foo\tbar/"
+ ,.u=
+ {.field_set=(1 << UF_PATH)
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 9 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="form feed in URL"
+ ,.url="/foo\fbar/"
+ ,.u=
+ {.field_set=(1 << UF_PATH)
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 9 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+#endif
+};
+
+void
+dump_url (const char *url, const struct http_parser_url *u)
+{
+ unsigned int i;
+
+ printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
+ for (i = 0; i < UF_MAX; i++) {
+ if ((u->field_set & (1 << i)) == 0) {
+ printf("\tfield_data[%u]: unset\n", i);
+ continue;
+ }
+
+ printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"",
+ i,
+ u->field_data[i].off,
+ u->field_data[i].len,
+ u->field_data[i].len,
+ url + u->field_data[i].off);
+ }
+}
+
+void
+test_parse_url (void)
+{
+ struct http_parser_url u;
+ const struct url_test *test;
+ unsigned int i;
+ int rv;
+
+ for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) {
+ test = &url_tests[i];
+ memset(&u, 0, sizeof(u));
+
+ rv = http_parser_parse_url(test->url,
+ strlen(test->url),
+ test->is_connect,
+ &u);
+
+ if (test->rv == 0) {
+ if (rv != 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+ "unexpected rv %d ***\n\n", test->url, test->name, rv);
+ abort();
+ }
+
+ if (memcmp(&u, &test->u, sizeof(u)) != 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n",
+ test->url, test->name);
+
+ printf("target http_parser_url:\n");
+ dump_url(test->url, &test->u);
+ printf("result http_parser_url:\n");
+ dump_url(test->url, &u);
+
+ abort();
+ }
+ } else {
+ /* test->rv != 0 */
+ if (rv == 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+ "unexpected rv %d ***\n\n", test->url, test->name, rv);
+ abort();
+ }
+ }
+ }
+}
+
+void
+test_method_str (void)
+{
+ assert(0 == strcmp("GET", http_method_str(HTTP_GET)));
+ assert(0 == strcmp("<unknown>", http_method_str(1337)));
+}
+
+void
+test_message (const struct message *message)
+{
+ size_t raw_len = strlen(message->raw);
+ size_t msg1len;
+ for (msg1len = 0; msg1len < raw_len; msg1len++) {
+ parser_init(message->type);
+
+ size_t read;
+ const char *msg1 = message->raw;
+ const char *msg2 = msg1 + msg1len;
+ size_t msg2len = raw_len - msg1len;
+
+ if (msg1len) {
+ read = parse(msg1, msg1len);
+
+ if (message->upgrade && parser->upgrade) {
+ messages[num_messages - 1].upgrade = msg1 + read;
+ goto test;
+ }
+
+ if (read != msg1len) {
+ print_error(msg1, read);
+ abort();
+ }
+ }
+
+
+ read = parse(msg2, msg2len);
+
+ if (message->upgrade && parser->upgrade) {
+ messages[num_messages - 1].upgrade = msg2 + read;
+ goto test;
+ }
+
+ if (read != msg2len) {
+ print_error(msg2, read);
+ abort();
+ }
+
+ read = parse(NULL, 0);
+
+ if (read != 0) {
+ print_error(message->raw, read);
+ abort();
+ }
+
+ test:
+
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+ abort();
+ }
+
+ if(!message_eq(0, message)) abort();
+
+ parser_free();
+ }
+}
+
+void
+test_message_count_body (const struct message *message)
+{
+ parser_init(message->type);
+
+ size_t read;
+ size_t l = strlen(message->raw);
+ size_t i, toread;
+ size_t chunk = 4024;
+
+ for (i = 0; i < l; i+= chunk) {
+ toread = MIN(l-i, chunk);
+ read = parse_count_body(message->raw + i, toread);
+ if (read != toread) {
+ print_error(message->raw, read);
+ abort();
+ }
+ }
+
+
+ read = parse_count_body(NULL, 0);
+ if (read != 0) {
+ print_error(message->raw, read);
+ abort();
+ }
+
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+ abort();
+ }
+
+ if(!message_eq(0, message)) abort();
+
+ parser_free();
+}
+
+void
+test_simple (const char *buf, enum http_errno err_expected)
+{
+ parser_init(HTTP_REQUEST);
+
+ size_t parsed;
+ int pass;
+ enum http_errno err;
+
+ parsed = parse(buf, strlen(buf));
+ pass = (parsed == strlen(buf));
+ err = HTTP_PARSER_ERRNO(parser);
+ parsed = parse(NULL, 0);
+ pass &= (parsed == 0);
+
+ parser_free();
+
+ /* In strict mode, allow us to pass with an unexpected HPE_STRICT as
+ * long as the caller isn't expecting success.
+ */
+#if HTTP_PARSER_STRICT
+ if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) {
+#else
+ if (err_expected != err) {
+#endif
+ fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n",
+ http_errno_name(err_expected), http_errno_name(err), buf);
+ abort();
+ }
+}
+
+void
+test_header_overflow_error (int req)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = "header-key: header-value\r\n";
+ size_t buflen = strlen(buf);
+
+ int i;
+ for (i = 0; i < 10000; i++) {
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ //fprintf(stderr, "error found on iter %d\n", i);
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW);
+ return;
+ }
+ }
+
+ fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
+ abort();
+}
+
+static void
+test_content_length_overflow (const char *buf, size_t buflen, int expect_ok)
+{
+ http_parser parser;
+ http_parser_init(&parser, HTTP_RESPONSE);
+ http_parser_execute(&parser, &settings_null, buf, buflen);
+
+ if (expect_ok)
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK);
+ else
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH);
+}
+
+void
+test_header_content_length_overflow_error (void)
+{
+#define X(size) \
+ "HTTP/1.1 200 OK\r\n" \
+ "Content-Length: " #size "\r\n" \
+ "\r\n"
+ const char a[] = X(18446744073709551614); /* 2^64-2 */
+ const char b[] = X(18446744073709551615); /* 2^64-1 */
+ const char c[] = X(18446744073709551616); /* 2^64 */
+#undef X
+ test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */
+ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_chunk_content_length_overflow_error (void)
+{
+#define X(size) \
+ "HTTP/1.1 200 OK\r\n" \
+ "Transfer-Encoding: chunked\r\n" \
+ "\r\n" \
+ #size "\r\n" \
+ "..."
+ const char a[] = X(FFFFFFFFFFFFFFFE); /* 2^64-2 */
+ const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */
+ const char c[] = X(10000000000000000); /* 2^64 */
+#undef X
+ test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */
+ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_no_overflow_long_body (int req, size_t length)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ size_t i;
+ char buf1[3000];
+ size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n",
+ req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length);
+ parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+ if (parsed != buf1len)
+ goto err;
+
+ for (i = 0; i < length; i++) {
+ char foo = 'a';
+ parsed = http_parser_execute(&parser, &settings_null, &foo, 1);
+ if (parsed != 1)
+ goto err;
+ }
+
+ parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+ if (parsed != buf1len) goto err;
+ return;
+
+ err:
+ fprintf(stderr,
+ "\n*** error in test_no_overflow_long_body %s of length %lu ***\n",
+ req ? "REQUEST" : "RESPONSE",
+ (unsigned long)length);
+ abort();
+}
+
+void
+test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+ int message_count = count_parsed_messages(3, r1, r2, r3);
+
+ char total[ strlen(r1->raw)
+ + strlen(r2->raw)
+ + strlen(r3->raw)
+ + 1
+ ];
+ total[0] = '\0';
+
+ strcat(total, r1->raw);
+ strcat(total, r2->raw);
+ strcat(total, r3->raw);
+
+ parser_init(r1->type);
+
+ size_t read;
+
+ read = parse(total, strlen(total));
+
+ if (parser->upgrade) {
+ upgrade_message_fix(total, read, 3, r1, r2, r3);
+ goto test;
+ }
+
+ if (read != strlen(total)) {
+ print_error(total, read);
+ abort();
+ }
+
+ read = parse(NULL, 0);
+
+ if (read != 0) {
+ print_error(total, read);
+ abort();
+ }
+
+test:
+
+ if (message_count != num_messages) {
+ fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages);
+ abort();
+ }
+
+ if (!message_eq(0, r1)) abort();
+ if (message_count > 1 && !message_eq(1, r2)) abort();
+ if (message_count > 2 && !message_eq(2, r3)) abort();
+
+ parser_free();
+}
+
+/* SCAN through every possible breaking to make sure the
+ * parser can handle getting the content in any chunks that
+ * might come from the socket
+ */
+void
+test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+ char total[80*1024] = "\0";
+ char buf1[80*1024] = "\0";
+ char buf2[80*1024] = "\0";
+ char buf3[80*1024] = "\0";
+
+ strcat(total, r1->raw);
+ strcat(total, r2->raw);
+ strcat(total, r3->raw);
+
+ size_t read;
+
+ int total_len = strlen(total);
+
+ int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;
+ int ops = 0 ;
+
+ size_t buf1_len, buf2_len, buf3_len;
+ int message_count = count_parsed_messages(3, r1, r2, r3);
+
+ int i,j,type_both;
+ for (type_both = 0; type_both < 2; type_both ++ ) {
+ for (j = 2; j < total_len; j ++ ) {
+ for (i = 1; i < j; i ++ ) {
+
+ if (ops % 1000 == 0) {
+ printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
+ fflush(stdout);
+ }
+ ops += 1;
+
+ parser_init(type_both ? HTTP_BOTH : r1->type);
+
+ buf1_len = i;
+ strlncpy(buf1, sizeof(buf1), total, buf1_len);
+ buf1[buf1_len] = 0;
+
+ buf2_len = j - i;
+ strlncpy(buf2, sizeof(buf1), total+i, buf2_len);
+ buf2[buf2_len] = 0;
+
+ buf3_len = total_len - j;
+ strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
+ buf3[buf3_len] = 0;
+
+ read = parse(buf1, buf1_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len) {
+ print_error(buf1, read);
+ goto error;
+ }
+
+ read += parse(buf2, buf2_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len + buf2_len) {
+ print_error(buf2, read);
+ goto error;
+ }
+
+ read += parse(buf3, buf3_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len + buf2_len + buf3_len) {
+ print_error(buf3, read);
+ goto error;
+ }
+
+ parse(NULL, 0);
+
+test:
+ if (parser->upgrade) {
+ upgrade_message_fix(total, read, 3, r1, r2, r3);
+ }
+
+ if (message_count != num_messages) {
+ fprintf(stderr, "\n\nParser didn't see %d messages only %d\n",
+ message_count, num_messages);
+ goto error;
+ }
+
+ if (!message_eq(0, r1)) {
+ fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
+ goto error;
+ }
+
+ if (message_count > 1 && !message_eq(1, r2)) {
+ fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
+ goto error;
+ }
+
+ if (message_count > 2 && !message_eq(2, r3)) {
+ fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
+ goto error;
+ }
+
+ parser_free();
+ }
+ }
+ }
+ puts("\b\b\b\b100%");
+ return;
+
+ error:
+ fprintf(stderr, "i=%d j=%d\n", i, j);
+ fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1);
+ fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2);
+ fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3);
+ abort();
+}
+
+// user required to free the result
+// string terminated by \0
+char *
+create_large_chunked_message (int body_size_in_kb, const char* headers)
+{
+ int i;
+ size_t wrote = 0;
+ size_t headers_len = strlen(headers);
+ size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6;
+ char * buf = malloc(bufsize);
+
+ memcpy(buf, headers, headers_len);
+ wrote += headers_len;
+
+ for (i = 0; i < body_size_in_kb; i++) {
+ // write 1kb chunk into the body.
+ memcpy(buf + wrote, "400\r\n", 5);
+ wrote += 5;
+ memset(buf + wrote, 'C', 1024);
+ wrote += 1024;
+ strcpy(buf + wrote, "\r\n");
+ wrote += 2;
+ }
+
+ memcpy(buf + wrote, "0\r\n\r\n", 6);
+ wrote += 6;
+ assert(wrote == bufsize);
+
+ return buf;
+}
+
+void
+test_status_complete (void)
+{
+ parser_init(HTTP_RESPONSE);
+ parser->data = 0;
+ http_parser_settings settings = settings_null;
+ settings.on_status_complete = status_complete_cb;
+
+ char *response = "don't mind me, just a simple response";
+ http_parser_execute(parser, &settings, response, strlen(response));
+ assert(parser->data == (void*)0); // the status_complete callback was never called
+ assert(parser->http_errno == HPE_INVALID_CONSTANT); // the errno for an invalid status line
+}
+
+/* Verify that we can pause parsing at any of the bytes in the
+ * message and still get the result that we're expecting. */
+void
+test_message_pause (const struct message *msg)
+{
+ char *buf = (char*) msg->raw;
+ size_t buflen = strlen(msg->raw);
+ size_t nread;
+
+ parser_init(msg->type);
+
+ do {
+ nread = parse_pause(buf, buflen);
+
+ // We can only set the upgrade buffer once we've gotten our message
+ // completion callback.
+ if (messages[0].message_complete_cb_called &&
+ msg->upgrade &&
+ parser->upgrade) {
+ messages[0].upgrade = buf + nread;
+ goto test;
+ }
+
+ if (nread < buflen) {
+
+ // Not much do to if we failed a strict-mode check
+ if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
+ parser_free();
+ return;
+ }
+
+ assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
+ }
+
+ buf += nread;
+ buflen -= nread;
+ http_parser_pause(parser, 0);
+ } while (buflen > 0);
+
+ nread = parse_pause(NULL, 0);
+ assert (nread == 0);
+
+test:
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
+ abort();
+ }
+
+ if(!message_eq(0, msg)) abort();
+
+ parser_free();
+}
+
+int
+main (void)
+{
+ parser = NULL;
+ int i, j, k;
+ int request_count;
+ int response_count;
+
+ printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
+
+ for (request_count = 0; requests[request_count].name; request_count++);
+ for (response_count = 0; responses[response_count].name; response_count++);
+
+ //// API
+ test_preserve_data();
+ test_parse_url();
+ test_method_str();
+
+ //// OVERFLOW CONDITIONS
+
+ test_header_overflow_error(HTTP_REQUEST);
+ test_no_overflow_long_body(HTTP_REQUEST, 1000);
+ test_no_overflow_long_body(HTTP_REQUEST, 100000);
+
+ test_header_overflow_error(HTTP_RESPONSE);
+ test_no_overflow_long_body(HTTP_RESPONSE, 1000);
+ test_no_overflow_long_body(HTTP_RESPONSE, 100000);
+
+ test_header_content_length_overflow_error();
+ test_chunk_content_length_overflow_error();
+
+ //// RESPONSES
+
+ for (i = 0; i < response_count; i++) {
+ test_message(&responses[i]);
+ }
+
+ for (i = 0; i < response_count; i++) {
+ test_message_pause(&responses[i]);
+ }
+
+ for (i = 0; i < response_count; i++) {
+ if (!responses[i].should_keep_alive) continue;
+ for (j = 0; j < response_count; j++) {
+ if (!responses[j].should_keep_alive) continue;
+ for (k = 0; k < response_count; k++) {
+ test_multiple3(&responses[i], &responses[j], &responses[k]);
+ }
+ }
+ }
+
+ test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
+ test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
+
+ // test very large chunked response
+ {
+ char * msg = create_large_chunked_message(31337,
+ "HTTP/1.0 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n");
+ struct message large_chunked =
+ {.name= "large chunked"
+ ,.type= HTTP_RESPONSE
+ ,.raw= msg
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 200
+ ,.num_headers= 2
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ , { "Content-Type", "text/plain" }
+ }
+ ,.body_size= 31337*1024
+ };
+ test_message_count_body(&large_chunked);
+ free(msg);
+ }
+
+
+
+ printf("response scan 1/2 ");
+ test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
+ , &responses[NO_BODY_HTTP10_KA_204]
+ , &responses[NO_REASON_PHRASE]
+ );
+
+ printf("response scan 2/2 ");
+ test_scan( &responses[BONJOUR_MADAME_FR]
+ , &responses[UNDERSTORE_HEADER_KEY]
+ , &responses[NO_CARRIAGE_RET]
+ );
+
+ puts("responses okay");
+
+
+ /// REQUESTS
+
+ test_simple("hello world", HPE_INVALID_METHOD);
+ test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
+
+
+ test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
+ test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
+ test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
+
+ // Well-formed but incomplete
+ test_simple("GET / HTTP/1.1\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 6\r\n"
+ "\r\n"
+ "fooba",
+ HPE_OK);
+
+ static const char *all_methods[] = {
+ "DELETE",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel
+ "OPTIONS",
+ "TRACE",
+ "COPY",
+ "LOCK",
+ "MKCOL",
+ "MOVE",
+ "PROPFIND",
+ "PROPPATCH",
+ "UNLOCK",
+ "REPORT",
+ "MKACTIVITY",
+ "CHECKOUT",
+ "MERGE",
+ "M-SEARCH",
+ "NOTIFY",
+ "SUBSCRIBE",
+ "UNSUBSCRIBE",
+ "PATCH",
+ 0 };
+ const char **this_method;
+ for (this_method = all_methods; *this_method; this_method++) {
+ char buf[200];
+ sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+ test_simple(buf, HPE_OK);
+ }
+
+ static const char *bad_methods[] = {
+ "C******",
+ "M****",
+ 0 };
+ for (this_method = bad_methods; *this_method; this_method++) {
+ char buf[200];
+ sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+ test_simple(buf, HPE_UNKNOWN);
+ }
+
+ const char *dumbfuck2 =
+ "GET / HTTP/1.1\r\n"
+ "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
+ "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
+ "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
+ "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
+ "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
+ "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
+ "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
+ "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
+ "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
+ "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
+ "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
+ "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
+ "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
+ "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
+ "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
+ "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
+ "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
+ "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
+ "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
+ "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
+ "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
+ "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
+ "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
+ "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
+ "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
+ "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
+ "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
+ "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
+ "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
+ "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
+ "\tRA==\r\n"
+ "\t-----END CERTIFICATE-----\r\n"
+ "\r\n";
+ test_simple(dumbfuck2, HPE_OK);
+
+#if 0
+ // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
+ // until EOF.
+ //
+ // no content-length
+ // error if there is a body without content length
+ const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ "HELLO";
+ test_simple(bad_get_no_headers_no_body, 0);
+#endif
+ /* TODO sending junk and large headers gets rejected */
+
+
+ /* check to make sure our predefined requests are okay */
+ for (i = 0; requests[i].name; i++) {
+ test_message(&requests[i]);
+ }
+
+ for (i = 0; i < request_count; i++) {
+ test_message_pause(&requests[i]);
+ }
+
+ for (i = 0; i < request_count; i++) {
+ if (!requests[i].should_keep_alive) continue;
+ for (j = 0; j < request_count; j++) {
+ if (!requests[j].should_keep_alive) continue;
+ for (k = 0; k < request_count; k++) {
+ test_multiple3(&requests[i], &requests[j], &requests[k]);
+ }
+ }
+ }
+
+ printf("request scan 1/4 ");
+ test_scan( &requests[GET_NO_HEADERS_NO_BODY]
+ , &requests[GET_ONE_HEADER_NO_BODY]
+ , &requests[GET_NO_HEADERS_NO_BODY]
+ );
+
+ printf("request scan 2/4 ");
+ test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
+ , &requests[POST_IDENTITY_BODY_WORLD]
+ , &requests[GET_FUNKY_CONTENT_LENGTH]
+ );
+
+ printf("request scan 3/4 ");
+ test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
+ , &requests[CHUNKED_W_TRAILING_HEADERS]
+ , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
+ );
+
+ printf("request scan 4/4 ");
+ test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]
+ , &requests[PREFIX_NEWLINE_GET ]
+ , &requests[CONNECT_REQUEST]
+ );
+
+ test_status_complete();
+
+ puts("requests okay");
+
+ return 0;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped
new file mode 100644
index 0000000..038bb52
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped
@@ -0,0 +1,845 @@
+name :curl get
+raw :"GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/test
+request_url :/test
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "User-Agent": "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1"}
+header_1 :{ "Host": "0.0.0.0=5000"}
+header_2 :{ "Accept": "*/*"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :firefox get
+raw :"GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/favicon.ico
+request_url :/favicon.ico
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Host": "0.0.0.0=5000"}
+header_1 :{ "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"}
+header_2 :{ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}
+header_3 :{ "Accept-Language": "en-us,en;q=0.5"}
+header_4 :{ "Accept-Encoding": "gzip,deflate"}
+header_5 :{ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7"}
+header_6 :{ "Keep-Alive": "300"}
+header_7 :{ "Connection": "keep-alive"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :dumbfuck
+raw :"GET /dumbfuck HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/dumbfuck
+request_url :/dumbfuck
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "aaaaaaaaaaaaa": "++++++++++"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :fragment in url
+raw :"GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/forums/1/topics/2375
+request_url :/forums/1/topics/2375?page=1#posts-17408
+fragment :posts-17408
+query_string:page=1
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :get no headers no body
+raw :"GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/get_no_headers_no_body/world
+request_url :/get_no_headers_no_body/world
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :get one header no body
+raw :"GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/get_one_header_no_body
+request_url :/get_one_header_no_body
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Accept": "*/*"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :get funky content length body hello
+raw :"GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/get_funky_content_length_body_hello
+request_url :/get_funky_content_length_body_hello
+fragment :
+query_string:
+body :"HELLO"
+body_size :0
+header_0 :{ "conTENT-Length": "5"}
+should_keep_alive :0
+http_major :1
+http_minor :0
+
+name :post identity body world
+raw :"POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/post_identity_body_world
+request_url :/post_identity_body_world?q=search#hey
+fragment :hey
+query_string:q=search
+body :"World"
+body_size :0
+header_0 :{ "Accept": "*/*"}
+header_1 :{ "Transfer-Encoding": "identity"}
+header_2 :{ "Content-Length": "5"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :post - chunked body: all your base are belong to us
+raw :"POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/post_chunked_all_your_base
+request_url :/post_chunked_all_your_base
+fragment :
+query_string:
+body :"all your base are belong to us"
+body_size :0
+header_0 :{ "Transfer-Encoding": "chunked"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :two chunks ; triple zero ending
+raw :"POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/two_chunks_mult_zero_end
+request_url :/two_chunks_mult_zero_end
+fragment :
+query_string:
+body :"hello world"
+body_size :0
+header_0 :{ "Transfer-Encoding": "chunked"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :chunked with trailing headers. blech.
+raw :"POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/chunked_w_trailing_headers
+request_url :/chunked_w_trailing_headers
+fragment :
+query_string:
+body :"hello world"
+body_size :0
+header_0 :{ "Transfer-Encoding": "chunked"}
+header_1 :{ "Vary": "*"}
+header_2 :{ "Content-Type": "text/plain"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :with bullshit after the length
+raw :"POST /chunked_w_bullshit_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n6; blahblah; blah\r\n world\r\n0\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/chunked_w_bullshit_after_length
+request_url :/chunked_w_bullshit_after_length
+fragment :
+query_string:
+body :"hello world"
+body_size :0
+header_0 :{ "Transfer-Encoding": "chunked"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :with quotes
+raw :"GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/with_"stupid"_quotes
+request_url :/with_"stupid"_quotes?foo="bar"
+fragment :
+query_string:foo="bar"
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :apachebench get
+raw :"GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/test
+request_url :/test
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Host": "0.0.0.0:5000"}
+header_1 :{ "User-Agent": "ApacheBench/2.3"}
+header_2 :{ "Accept": "*/*"}
+should_keep_alive :0
+http_major :1
+http_minor :0
+
+name :query url with question mark
+raw :"GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/test.cgi
+request_url :/test.cgi?foo=bar?baz
+fragment :
+query_string:foo=bar?baz
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :newline prefix get
+raw :"\r\nGET /test HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/test
+request_url :/test
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :upgrade request
+raw :"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/demo
+request_url :/demo
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Host": "example.com"}
+header_1 :{ "Connection": "Upgrade"}
+header_2 :{ "Sec-WebSocket-Key2": "12998 5 Y3 1 .P00"}
+header_3 :{ "Sec-WebSocket-Protocol": "sample"}
+header_4 :{ "Upgrade": "WebSocket"}
+header_5 :{ "Sec-WebSocket-Key1": "4 @1 46546xW%0l 1 5"}
+header_6 :{ "Origin": "http://example.com"}
+should_keep_alive :1
+upgrade :"Hot diggity dogg"
+http_major :1
+http_minor :1
+
+name :connect request
+raw :"CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\r\nand yet even more data"
+type :HTTP_REQUEST
+method: HTTP_CONNECT
+status_code :0
+request_path:
+request_url :0-home0.netscape.com:443
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "User-agent": "Mozilla/1.1N"}
+header_1 :{ "Proxy-authorization": "basic aGVsbG86d29ybGQ="}
+should_keep_alive :0
+upgrade :"some data\r\nand yet even more data"
+http_major :1
+http_minor :0
+
+name :report request
+raw :"REPORT /test HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_REPORT
+status_code :0
+request_path:/test
+request_url :/test
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :request with no http version
+raw :"GET /\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/
+request_url :/
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :0
+http_major :0
+http_minor :9
+
+name :m-search request
+raw :"M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_MSEARCH
+status_code :0
+request_path:*
+request_url :*
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "HOST": "239.255.255.250:1900"}
+header_1 :{ "MAN": ""ssdp:discover""}
+header_2 :{ "ST": ""ssdp:all""}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :line folding in header value
+raw :"GET / HTTP/1.1\r\nLine1: abc\r\n def\r\n ghi\r\n jkl\r\n mno \r\n qrs\r\nLine2: line2 \r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/
+request_url :/
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Line1": "abcdefghijklmno qrs"}
+header_1 :{ "Line2": "line2 "}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :host terminated by a query string
+raw :"GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:
+request_url :http://hypnotoad.org?hail=all
+fragment :
+query_string:hail=all
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :host:port terminated by a query string
+raw :"GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:
+request_url :http://hypnotoad.org:1234?hail=all
+fragment :
+query_string:hail=all
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :host:port terminated by a space
+raw :"GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:
+request_url :http://hypnotoad.org:1234
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :PATCH request
+raw :"PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc"
+type :HTTP_REQUEST
+method: UNKNOWN
+status_code :0
+request_path:/file.txt
+request_url :/file.txt
+fragment :
+query_string:
+body :"cccccccccc"
+body_size :0
+header_0 :{ "Host": "www.example.com"}
+header_1 :{ "Content-Type": "application/example"}
+header_2 :{ "If-Match": ""e0023aa4e""}
+header_3 :{ "Content-Length": "10"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :connect caps request
+raw :"CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_CONNECT
+status_code :0
+request_path:
+request_url :HOME0.NETSCAPE.COM:443
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "User-agent": "Mozilla/1.1N"}
+header_1 :{ "Proxy-authorization": "basic aGVsbG86d29ybGQ="}
+should_keep_alive :0
+upgrade :""
+http_major :1
+http_minor :0
+
+name :eat CRLF between requests, no "Connection: close" header
+raw :"POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\n\r\nq=42\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/
+request_url :/
+fragment :
+query_string:
+body :"q=42"
+body_size :0
+header_0 :{ "Host": "www.example.com"}
+header_1 :{ "Content-Type": "application/x-www-form-urlencoded"}
+header_2 :{ "Content-Length": "4"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :eat CRLF between requests even if "Connection: close" is set
+raw :"POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\nConnection: close\r\n\r\nq=42\r\n"
+type :HTTP_REQUEST
+method: HTTP_POST
+status_code :0
+request_path:/
+request_url :/
+fragment :
+query_string:
+body :"q=42"
+body_size :0
+header_0 :{ "Host": "www.example.com"}
+header_1 :{ "Content-Type": "application/x-www-form-urlencoded"}
+header_2 :{ "Content-Length": "4"}
+header_3 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :PURGE request
+raw :"PURGE /file.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
+type :HTTP_REQUEST
+method: UNKNOWN
+status_code :0
+request_path:/file.txt
+request_url :/file.txt
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Host": "www.example.com"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :google 301
+raw :"HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219 \r\n\r\n<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :301
+request_path:
+request_url :
+fragment :
+query_string:
+body :"<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n"
+body_size :0
+header_0 :{ "Location": "http://www.google.com/"}
+header_1 :{ "Content-Type": "text/html; charset=UTF-8"}
+header_2 :{ "Date": "Sun, 26 Apr 2009 11:11:49 GMT"}
+header_3 :{ "Expires": "Tue, 26 May 2009 11:11:49 GMT"}
+header_4 :{ "X-$PrototypeBI-Version": "1.6.0.3"}
+header_5 :{ "Cache-Control": "public, max-age=2592000"}
+header_6 :{ "Server": "gws"}
+header_7 :{ "Content-Length": "219 "}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :no content-length response
+raw :"HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <SOAP-ENV:Body>\n <SOAP-ENV:Fault>\n <faultcode>SOAP-ENV:Client</faultcode>\n <faultstring>Client Error</faultstring>\n </SOAP-ENV:Fault>\n </SOAP-ENV:Body>\n</SOAP-ENV:Envelope>"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <SOAP-ENV:Body>\n <SOAP-ENV:Fault>\n <faultcode>SOAP-ENV:Client</faultcode>\n <faultstring>Client Error</faultstring>\n </SOAP-ENV:Fault>\n </SOAP-ENV:Body>\n</SOAP-ENV:Envelope>"
+body_size :0
+header_0 :{ "Date": "Tue, 04 Aug 2009 07:59:32 GMT"}
+header_1 :{ "Server": "Apache"}
+header_2 :{ "X-Powered-By": "Servlet/2.5 JSP/2.1"}
+header_3 :{ "Content-Type": "text/xml; charset=utf-8"}
+header_4 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :404 no headers no body
+raw :"HTTP/1.1 404 Not Found\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :404
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :301 no response phrase
+raw :"HTTP/1.1 301\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :301
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :200 trailing space on chunked body
+raw :"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :"This is the data in the first chunk\r\nand this is the second one\r\n"
+body_size :65
+header_0 :{ "Content-Type": "text/plain"}
+header_1 :{ "Transfer-Encoding": "chunked"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :no carriage ret
+raw :"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :"these headers are from http://news.ycombinator.com/"
+body_size :0
+header_0 :{ "Content-Type": "text/html; charset=utf-8"}
+header_1 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :proxy connection
+raw :"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :"hello world"
+body_size :0
+header_0 :{ "Content-Type": "text/html; charset=UTF-8"}
+header_1 :{ "Content-Length": "11"}
+header_2 :{ "Proxy-Connection": "close"}
+header_3 :{ "Date": "Thu, 31 Dec 2009 20:55:48 +0000"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :underscore header key
+raw :"HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Server": "DCLK-AdSvr"}
+header_1 :{ "Content-Type": "text/xml"}
+header_2 :{ "Content-Length": "0"}
+header_3 :{ "DCLK_imp": "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :bonjourmadame.fr
+raw :"HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :301
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Date": "Thu, 03 Jun 2010 09:56:32 GMT"}
+header_1 :{ "Server": "Apache/2.2.3 (Red Hat)"}
+header_2 :{ "Cache-Control": "public"}
+header_3 :{ "Pragma": ""}
+header_4 :{ "Location": "http://www.bonjourmadame.fr/"}
+header_5 :{ "Vary": "Accept-Encoding"}
+header_6 :{ "Content-Length": "0"}
+header_7 :{ "Content-Type": "text/html; charset=UTF-8"}
+header_8 :{ "Connection": "keep-alive"}
+should_keep_alive :1
+http_major :1
+http_minor :0
+
+name :field underscore
+raw :"HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Date": "Tue, 28 Sep 2010 01:14:13 GMT"}
+header_1 :{ "Server": "Apache"}
+header_2 :{ "Cache-Control": "no-cache, must-revalidate"}
+header_3 :{ "Expires": "Mon, 26 Jul 1997 05:00:00 GMT"}
+header_4 :{ ".et-Cookie": "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com"}
+header_5 :{ "Vary": "Accept-Encoding"}
+header_6 :{ "_eep-Alive": "timeout=45"}
+header_7 :{ "_onnection": "Keep-Alive"}
+header_8 :{ "Transfer-Encoding": "chunked"}
+header_9 :{ "Content-Type": "text/html"}
+header_10 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :non-ASCII in status line
+raw :"HTTP/1.1 500 Oriëntatieprobleem\r\nDate: Fri, 5 Nov 2010 23:07:12 GMT+2\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :500
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Date": "Fri, 5 Nov 2010 23:07:12 GMT+2"}
+header_1 :{ "Content-Length": "0"}
+header_2 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :http version 0.9
+raw :"HTTP/0.9 200 OK\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :0
+http_major :0
+http_minor :9
+
+name :neither content-length nor transfer-encoding response
+raw :"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :"hello world"
+body_size :0
+header_0 :{ "Content-Type": "text/plain"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :HTTP/1.0 with keep-alive and EOF-terminated 200 status
+raw :"HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Connection": "keep-alive"}
+should_keep_alive :0
+http_major :1
+http_minor :0
+
+name :HTTP/1.0 with keep-alive and a 204 status
+raw :"HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :204
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Connection": "keep-alive"}
+should_keep_alive :1
+http_major :1
+http_minor :0
+
+name :HTTP/1.1 with an EOF-terminated 200 status
+raw :"HTTP/1.1 200 OK\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :HTTP/1.1 with a 204 status
+raw :"HTTP/1.1 204 No content\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :204
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+should_keep_alive :1
+http_major :1
+http_minor :1
+
+name :HTTP/1.1 with a 204 status and keep-alive disabled
+raw :"HTTP/1.1 204 No content\r\nConnection: close\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :204
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Connection": "close"}
+should_keep_alive :0
+http_major :1
+http_minor :1
+
+name :HTTP/1.1 with chunked endocing and a 200 response
+raw :"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
+type :HTTP_RESPONSE
+method: HTTP_DELETE
+status_code :200
+request_path:
+request_url :
+fragment :
+query_string:
+body :""
+body_size :0
+header_0 :{ "Transfer-Encoding": "chunked"}
+should_keep_alive :1
+http_major :1
+http_minor :1
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8
new file mode 100644
index 0000000..5266159
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8
@@ -0,0 +1,17 @@
+name :utf-8 path request
+raw :"GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n"
+type :HTTP_REQUEST
+method: HTTP_GET
+status_code :0
+request_path:/δ¶/δt/pope
+request_url :/δ¶/δt/pope?q=1#narf
+fragment :narf
+query_string:q=1
+body :""
+body_size :0
+header_0 :{ "Host": "github.com"}
+should_keep_alive :1
+upgrade :0
+http_major :1
+http_minor :1
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb
new file mode 100644
index 0000000..1604890
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb
@@ -0,0 +1,6 @@
+
+"A".upto("Z") {|c|
+ puts "public static final byte #{c} = 0x#{c[0].to_s(16)};"
+}
+
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb
new file mode 100644
index 0000000..84f9699
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb
@@ -0,0 +1,13 @@
+
+
+def printbytes str
+str.each_byte { |b|
+ print "0x#{b.to_s(16)}, "
+}
+end
+
+if $0 == __FILE__
+ printf "static final byte [] #{ARGV[0]} = {\n"
+ printbytes ARGV[0]
+ printf "\n};\n"
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb
new file mode 100644
index 0000000..13960cb
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb
@@ -0,0 +1,15 @@
+
+
+0.upto(255) { |i|
+ printf "\n" if i%16 == 0
+ printf " " if i%8 == 0
+ s = ("" << i)
+ if s =~ /[A-Z0-9\-_\/ ]/
+ print "0x#{i.to_s(16)},"
+ elsif s =~ /[a-z]/
+ print "0x#{s.upcase[0].to_s(16)},"
+ else
+ print "0x00,"
+ end
+
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb
new file mode 100644
index 0000000..683adb9
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb
@@ -0,0 +1,33 @@
+
+
+
+
+# name : 200 trailing space on chunked body
+# raw : "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n"
+# type : HTTP_RESPONSE
+# method: HTTP_DELETE
+# status code :200
+# request_path:
+# request_url :
+# fragment :
+# query_string:
+# body :"This is the data in the first chunk\r\nand this is the second one\r\n"
+# body_size :65
+# header_0 :{ "Content-Type": "text/plain"}
+# header_1 :{ "Transfer-Encoding": "chunked"}
+# should_keep_alive :1
+# upgrade :0
+# http_major :1
+# http_minor :1
+
+
+class ParserTest
+ attr_accessor :name
+ attr_accessor :raw
+ attr_accessor :type
+ attr_accessor :method
+ attr_accessor :status_code
+ attr_accessor :request_path
+ attr_accessor :method
+end
+
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/AUTHORS b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/AUTHORS
new file mode 100644
index 0000000..5323b68
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/AUTHORS
@@ -0,0 +1,68 @@
+# Authors ordered by first contribution.
+Ryan Dahl <ry@tinyclouds.org>
+Jeremy Hinegardner <jeremy@hinegardner.org>
+Sergey Shepelev <temotor@gmail.com>
+Joe Damato <ice799@gmail.com>
+tomika <tomika_nospam@freemail.hu>
+Phoenix Sol <phoenix@burninglabs.com>
+Cliff Frey <cliff@meraki.com>
+Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
+Santiago Gala <sgala@apache.org>
+Tim Becker <tim.becker@syngenio.de>
+Jeff Terrace <jterrace@gmail.com>
+Ben Noordhuis <info@bnoordhuis.nl>
+Nathan Rajlich <nathan@tootallnate.net>
+Mark Nottingham <mnot@mnot.net>
+Aman Gupta <aman@tmm1.net>
+Tim Becker <tim.becker@kuriositaet.de>
+Sean Cunningham <sean.cunningham@mandiant.com>
+Peter Griess <pg@std.in>
+Salman Haq <salman.haq@asti-usa.com>
+Cliff Frey <clifffrey@gmail.com>
+Jon Kolb <jon@b0g.us>
+Fouad Mardini <f.mardini@gmail.com>
+Paul Querna <pquerna@apache.org>
+Felix Geisendörfer <felix@debuggable.com>
+koichik <koichik@improvement.jp>
+Andre Caron <andre.l.caron@gmail.com>
+Ivo Raisr <ivosh@ivosh.net>
+James McLaughlin <jamie@lacewing-project.org>
+David Gwynne <loki@animata.net>
+Thomas LE ROUX <thomas@november-eleven.fr>
+Randy Rizun <rrizun@ortivawireless.com>
+Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
+Simon Zimmermann <simonz05@gmail.com>
+Erik Dubbelboer <erik@dubbelboer.com>
+Martell Malone <martellmalone@gmail.com>
+Bertrand Paquet <bpaquet@octo.com>
+BogDan Vatra <bogdan@kde.org>
+Peter Faiman <peter@thepicard.org>
+Corey Richardson <corey@octayn.net>
+Tóth Tamás <tomika_nospam@freemail.hu>
+Cam Swords <cam.swords@gmail.com>
+Chris Dickinson <christopher.s.dickinson@gmail.com>
+Uli Köhler <ukoehler@btronik.de>
+Charlie Somerville <charlie@charliesomerville.com>
+Patrik Stutz <patrik.stutz@gmail.com>
+Fedor Indutny <fedor.indutny@gmail.com>
+runner <runner.mei@gmail.com>
+Alexis Campailla <alexis@janeasystems.com>
+David Wragg <david@wragg.org>
+Vinnie Falco <vinnie.falco@gmail.com>
+Alex Butum <alexbutum@linux.com>
+Rex Feng <rexfeng@gmail.com>
+Alex Kocharin <alex@kocharin.ru>
+Mark Koopman <markmontymark@yahoo.com>
+Helge Heß <me@helgehess.eu>
+Alexis La Goutte <alexis.lagoutte@gmail.com>
+George Miroshnykov <george.miroshnykov@gmail.com>
+Maciej Małecki <me@mmalecki.com>
+Marc O'Morain <github.com@marcomorain.com>
+Jeff Pinner <jpinner@twitter.com>
+Timothy J Fontaine <tjfontaine@gmail.com>
+Akagi201 <akagi201@gmail.com>
+Romain Giraud <giraud.romain@gmail.com>
+Jay Satiro <raysatiro@yahoo.com>
+Arne Steen <Arne.Steen@gmx.de>
+Kjell Schubert <kjell.schubert@gmail.com>
+Olivier Mengué <dolmen@cpan.org>
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT
new file mode 100644
index 0000000..1ec0ab4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright Joyent, Inc. and other Node contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/README.md b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/README.md
new file mode 100644
index 0000000..b265d71
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/README.md
@@ -0,0 +1,246 @@
+HTTP Parser
+===========
+
+[![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](https://travis-ci.org/nodejs/http-parser)
+
+This is a parser for HTTP messages written in C. It parses both requests and
+responses. The parser is designed to be used in performance HTTP
+applications. It does not make any syscalls nor allocations, it does not
+buffer data, it can be interrupted at anytime. Depending on your
+architecture, it only requires about 40 bytes of data per message
+stream (in a web server that is per connection).
+
+Features:
+
+ * No dependencies
+ * Handles persistent streams (keep-alive).
+ * Decodes chunked encoding.
+ * Upgrade support
+ * Defends against buffer overflow attacks.
+
+The parser extracts the following information from HTTP messages:
+
+ * Header fields and values
+ * Content-Length
+ * Request method
+ * Response status code
+ * Transfer-Encoding
+ * HTTP version
+ * Request URL
+ * Message body
+
+
+Usage
+-----
+
+One `http_parser` object is used per TCP connection. Initialize the struct
+using `http_parser_init()` and set the callbacks. That might look something
+like this for a request parser:
+```c
+http_parser_settings settings;
+settings.on_url = my_url_callback;
+settings.on_header_field = my_header_field_callback;
+/* ... */
+
+http_parser *parser = malloc(sizeof(http_parser));
+http_parser_init(parser, HTTP_REQUEST);
+parser->data = my_socket;
+```
+
+When data is received on the socket execute the parser and check for errors.
+
+```c
+size_t len = 80*1024, nparsed;
+char buf[len];
+ssize_t recved;
+
+recved = recv(fd, buf, len, 0);
+
+if (recved < 0) {
+ /* Handle error. */
+}
+
+/* Start up / continue the parser.
+ * Note we pass recved==0 to signal that EOF has been received.
+ */
+nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+if (parser->upgrade) {
+ /* handle new protocol */
+} else if (nparsed != recved) {
+ /* Handle error. Usually just close the connection. */
+}
+```
+
+`http_parser` needs to know where the end of the stream is. For example, sometimes
+servers send responses without Content-Length and expect the client to
+consume input (for the body) until EOF. To tell `http_parser` about EOF, give
+`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors
+can still be encountered during an EOF, so one must still be prepared
+to receive them.
+
+Scalar valued message information such as `status_code`, `method`, and the
+HTTP version are stored in the parser structure. This data is only
+temporally stored in `http_parser` and gets reset on each new message. If
+this information is needed later, copy it out of the structure during the
+`headers_complete` callback.
+
+The parser decodes the transfer-encoding for both requests and responses
+transparently. That is, a chunked encoding is decoded before being sent to
+the on_body callback.
+
+
+The Special Problem of Upgrade
+------------------------------
+
+`http_parser` supports upgrading the connection to a different protocol. An
+increasingly common example of this is the WebSocket protocol which sends
+a request like
+
+ GET /demo HTTP/1.1
+ Upgrade: WebSocket
+ Connection: Upgrade
+ Host: example.com
+ Origin: http://example.com
+ WebSocket-Protocol: sample
+
+followed by non-HTTP data.
+
+(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the
+WebSocket protocol.)
+
+To support this, the parser will treat this as a normal HTTP message without a
+body, issuing both on_headers_complete and on_message_complete callbacks. However
+http_parser_execute() will stop parsing at the end of the headers and return.
+
+The user is expected to check if `parser->upgrade` has been set to 1 after
+`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
+offset by the return value of `http_parser_execute()`.
+
+
+Callbacks
+---------
+
+During the `http_parser_execute()` call, the callbacks set in
+`http_parser_settings` will be executed. The parser maintains state and
+never looks behind, so buffering the data is not necessary. If you need to
+save certain data for later usage, you can do that from the callbacks.
+
+There are two types of callbacks:
+
+* notification `typedef int (*http_cb) (http_parser*);`
+ Callbacks: on_message_begin, on_headers_complete, on_message_complete.
+* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
+ Callbacks: (requests only) on_url,
+ (common) on_header_field, on_header_value, on_body;
+
+Callbacks must return 0 on success. Returning a non-zero value indicates
+error to the parser, making it exit immediately.
+
+For cases where it is necessary to pass local information to/from a callback,
+the `http_parser` object's `data` field can be used.
+An example of such a case is when using threads to handle a socket connection,
+parse a request, and then give a response over that socket. By instantiation
+of a thread-local struct containing relevant data (e.g. accepted socket,
+allocated memory for callbacks to write into, etc), a parser's callbacks are
+able to communicate data between the scope of the thread and the scope of the
+callback in a threadsafe manner. This allows `http_parser` to be used in
+multi-threaded contexts.
+
+Example:
+```c
+ typedef struct {
+ socket_t sock;
+ void* buffer;
+ int buf_len;
+ } custom_data_t;
+
+
+int my_url_callback(http_parser* parser, const char *at, size_t length) {
+ /* access to thread local custom_data_t struct.
+ Use this access save parsed data for later use into thread local
+ buffer, or communicate over socket
+ */
+ parser->data;
+ ...
+ return 0;
+}
+
+...
+
+void http_parser_thread(socket_t sock) {
+ int nparsed = 0;
+ /* allocate memory for user data */
+ custom_data_t *my_data = malloc(sizeof(custom_data_t));
+
+ /* some information for use by callbacks.
+ * achieves thread -> callback information flow */
+ my_data->sock = sock;
+
+ /* instantiate a thread-local parser */
+ http_parser *parser = malloc(sizeof(http_parser));
+ http_parser_init(parser, HTTP_REQUEST); /* initialise parser */
+ /* this custom data reference is accessible through the reference to the
+ parser supplied to callback functions */
+ parser->data = my_data;
+
+ http_parser_settings settings; /* set up callbacks */
+ settings.on_url = my_url_callback;
+
+ /* execute parser */
+ nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+ ...
+ /* parsed information copied from callback.
+ can now perform action on data copied into thread-local memory from callbacks.
+ achieves callback -> thread information flow */
+ my_data->buffer;
+ ...
+}
+
+```
+
+In case you parse HTTP message in chunks (i.e. `read()` request line
+from socket, parse, read half headers, parse, etc) your data callbacks
+may be called more than once. `http_parser` guarantees that data pointer is only
+valid for the lifetime of callback. You can also `read()` into a heap allocated
+buffer to avoid copying memory around if this fits your application.
+
+Reading headers may be a tricky task if you read/parse headers partially.
+Basically, you need to remember whether last header callback was field or value
+and apply the following logic:
+
+ (on_header_field and on_header_value shortened to on_h_*)
+ ------------------------ ------------ --------------------------------------------
+ | State (prev. callback) | Callback | Description/action |
+ ------------------------ ------------ --------------------------------------------
+ | nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
+ | | | into it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_field | New header started. |
+ | | | Copy current name,value buffers to headers |
+ | | | list and allocate new buffer for new name |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_field | Previous name continues. Reallocate name |
+ | | | buffer and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | field | on_h_value | Value for current header started. Allocate |
+ | | | new buffer and copy callback data to it |
+ ------------------------ ------------ --------------------------------------------
+ | value | on_h_value | Value continues. Reallocate value buffer |
+ | | | and append callback data to it |
+ ------------------------ ------------ --------------------------------------------
+
+
+Parsing URLs
+------------
+
+A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
+Users of this library may wish to use it to parse URLs constructed from
+consecutive `on_url` callbacks.
+
+See examples of reading in headers:
+
+* [partial example](http://gist.github.com/155877) in C
+* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
+* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/bench.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/bench.c
new file mode 100644
index 0000000..678f555
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/bench.c
@@ -0,0 +1,128 @@
+/* Copyright Fedor Indutny. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+/* 8 gb */
+static const int64_t kBytes = 8LL << 30;
+
+static const char data[] =
+ "POST /joyent/http-parser HTTP/1.1\r\n"
+ "Host: github.com\r\n"
+ "DNT: 1\r\n"
+ "Accept-Encoding: gzip, deflate, sdch\r\n"
+ "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n"
+ "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/39.0.2171.65 Safari/537.36\r\n"
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
+ "image/webp,*/*;q=0.8\r\n"
+ "Referer: https://github.com/joyent/http-parser\r\n"
+ "Connection: keep-alive\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n";
+static const size_t data_len = sizeof(data) - 1;
+
+static int on_info(http_parser* p) {
+ return 0;
+}
+
+
+static int on_data(http_parser* p, const char *at, size_t length) {
+ return 0;
+}
+
+static http_parser_settings settings = {
+ .on_message_begin = on_info,
+ .on_headers_complete = on_info,
+ .on_message_complete = on_info,
+ .on_header_field = on_data,
+ .on_header_value = on_data,
+ .on_url = on_data,
+ .on_status = on_data,
+ .on_body = on_data
+};
+
+int bench(int iter_count, int silent) {
+ struct http_parser parser;
+ int i;
+ int err;
+ struct timeval start;
+ struct timeval end;
+
+ if (!silent) {
+ err = gettimeofday(&start, NULL);
+ assert(err == 0);
+ }
+
+ fprintf(stderr, "req_len=%d\n", (int) data_len);
+ for (i = 0; i < iter_count; i++) {
+ size_t parsed;
+ http_parser_init(&parser, HTTP_REQUEST);
+
+ parsed = http_parser_execute(&parser, &settings, data, data_len);
+ assert(parsed == data_len);
+ }
+
+ if (!silent) {
+ double elapsed;
+ double bw;
+ double total;
+
+ err = gettimeofday(&end, NULL);
+ assert(err == 0);
+
+ fprintf(stdout, "Benchmark result:\n");
+
+ elapsed = (double) (end.tv_sec - start.tv_sec) +
+ (end.tv_usec - start.tv_usec) * 1e-6f;
+
+ total = (double) iter_count * data_len;
+ bw = (double) total / elapsed;
+
+ fprintf(stdout, "%.2f mb | %.2f mb/s | %.2f req/sec | %.2f s\n",
+ (double) total / (1024 * 1024),
+ bw / (1024 * 1024),
+ (double) iter_count / elapsed,
+ elapsed);
+
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ int64_t iterations;
+
+ iterations = kBytes / (int64_t) data_len;
+ if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
+ for (;;)
+ bench(iterations, 1);
+ return 0;
+ } else {
+ return bench(iterations, 0);
+ }
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c
new file mode 100644
index 0000000..3daa7f4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c
@@ -0,0 +1,157 @@
+/* Copyright Joyent, Inc. and other Node contributors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* Dump what the parser finds to stdout as it happen */
+
+#include "http_parser.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int on_message_begin(http_parser* _) {
+ (void)_;
+ printf("\n***MESSAGE BEGIN***\n\n");
+ return 0;
+}
+
+int on_headers_complete(http_parser* _) {
+ (void)_;
+ printf("\n***HEADERS COMPLETE***\n\n");
+ return 0;
+}
+
+int on_message_complete(http_parser* _) {
+ (void)_;
+ printf("\n***MESSAGE COMPLETE***\n\n");
+ return 0;
+}
+
+int on_url(http_parser* _, const char* at, size_t length) {
+ (void)_;
+ printf("Url: %.*s\n", (int)length, at);
+ return 0;
+}
+
+int on_header_field(http_parser* _, const char* at, size_t length) {
+ (void)_;
+ printf("Header field: %.*s\n", (int)length, at);
+ return 0;
+}
+
+int on_header_value(http_parser* _, const char* at, size_t length) {
+ (void)_;
+ printf("Header value: %.*s\n", (int)length, at);
+ return 0;
+}
+
+int on_body(http_parser* _, const char* at, size_t length) {
+ (void)_;
+ printf("Body: %.*s\n", (int)length, at);
+ return 0;
+}
+
+void usage(const char* name) {
+ fprintf(stderr,
+ "Usage: %s $type $filename\n"
+ " type: -x, where x is one of {r,b,q}\n"
+ " parses file as a Response, reQuest, or Both\n",
+ name);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char* argv[]) {
+ enum http_parser_type file_type;
+
+ if (argc != 3) {
+ usage(argv[0]);
+ }
+
+ char* type = argv[1];
+ if (type[0] != '-') {
+ usage(argv[0]);
+ }
+
+ switch (type[1]) {
+ /* in the case of "-", type[1] will be NUL */
+ case 'r':
+ file_type = HTTP_RESPONSE;
+ break;
+ case 'q':
+ file_type = HTTP_REQUEST;
+ break;
+ case 'b':
+ file_type = HTTP_BOTH;
+ break;
+ default:
+ usage(argv[0]);
+ }
+
+ char* filename = argv[2];
+ FILE* file = fopen(filename, "r");
+ if (file == NULL) {
+ perror("fopen");
+ goto fail;
+ }
+
+ fseek(file, 0, SEEK_END);
+ long file_length = ftell(file);
+ if (file_length == -1) {
+ perror("ftell");
+ goto fail;
+ }
+ fseek(file, 0, SEEK_SET);
+
+ char* data = malloc(file_length);
+ if (fread(data, 1, file_length, file) != (size_t)file_length) {
+ fprintf(stderr, "couldn't read entire file\n");
+ free(data);
+ goto fail;
+ }
+
+ http_parser_settings settings;
+ memset(&settings, 0, sizeof(settings));
+ settings.on_message_begin = on_message_begin;
+ settings.on_url = on_url;
+ settings.on_header_field = on_header_field;
+ settings.on_header_value = on_header_value;
+ settings.on_headers_complete = on_headers_complete;
+ settings.on_body = on_body;
+ settings.on_message_complete = on_message_complete;
+
+ http_parser parser;
+ http_parser_init(&parser, file_type);
+ size_t nparsed = http_parser_execute(&parser, &settings, data, file_length);
+ free(data);
+
+ if (nparsed != (size_t)file_length) {
+ fprintf(stderr,
+ "Error: %s (%s)\n",
+ http_errno_description(HTTP_PARSER_ERRNO(&parser)),
+ http_errno_name(HTTP_PARSER_ERRNO(&parser)));
+ goto fail;
+ }
+
+ return EXIT_SUCCESS;
+
+fail:
+ fclose(file);
+ return EXIT_FAILURE;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c
new file mode 100644
index 0000000..f235bed
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c
@@ -0,0 +1,47 @@
+#include "http_parser.h"
+#include <stdio.h>
+#include <string.h>
+
+void
+dump_url (const char *url, const struct http_parser_url *u)
+{
+ unsigned int i;
+
+ printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
+ for (i = 0; i < UF_MAX; i++) {
+ if ((u->field_set & (1 << i)) == 0) {
+ printf("\tfield_data[%u]: unset\n", i);
+ continue;
+ }
+
+ printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n",
+ i,
+ u->field_data[i].off,
+ u->field_data[i].len,
+ u->field_data[i].len,
+ url + u->field_data[i].off);
+ }
+}
+
+int main(int argc, char ** argv) {
+ struct http_parser_url u;
+ int len, connect, result;
+
+ if (argc != 3) {
+ printf("Syntax : %s connect|get url\n", argv[0]);
+ return 1;
+ }
+ len = strlen(argv[2]);
+ connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
+ printf("Parsing %s, connect %d\n", argv[2], connect);
+
+ http_parser_url_init(&u);
+ result = http_parser_parse_url(argv[2], len, connect, &u);
+ if (result != 0) {
+ printf("Parse error : %d\n", result);
+ return result;
+ }
+ printf("Parse ok, result : \n");
+ dump_url(argv[2], &u);
+ return 0;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.c
new file mode 100644
index 0000000..f9991c3
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.c
@@ -0,0 +1,2440 @@
+/* Copyright Joyent, Inc. and other Node contributors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
+
+#define SET_ERRNO(e) \
+do { \
+ parser->http_errno = (e); \
+} while(0)
+
+#define CURRENT_STATE() p_state
+#define UPDATE_STATE(V) p_state = (enum state) (V);
+#define RETURN(V) \
+do { \
+ parser->state = CURRENT_STATE(); \
+ return (V); \
+} while (0);
+#define REEXECUTE() \
+ goto reexecute; \
+
+
+#ifdef __GNUC__
+# define LIKELY(X) __builtin_expect(!!(X), 1)
+# define UNLIKELY(X) __builtin_expect(!!(X), 0)
+#else
+# define LIKELY(X) (X)
+# define UNLIKELY(X) (X)
+#endif
+
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (LIKELY(settings->on_##FOR)) { \
+ parser->state = CURRENT_STATE(); \
+ if (UNLIKELY(0 != settings->on_##FOR(parser))) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ UPDATE_STATE(parser->state); \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \
+ return (ER); \
+ } \
+ } \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (FOR##_mark) { \
+ if (LIKELY(settings->on_##FOR)) { \
+ parser->state = CURRENT_STATE(); \
+ if (UNLIKELY(0 != \
+ settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ UPDATE_STATE(parser->state); \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \
+ return (ER); \
+ } \
+ } \
+ FOR##_mark = NULL; \
+ } \
+} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR) \
+do { \
+ if (!FOR##_mark) { \
+ FOR##_mark = p; \
+ } \
+} while (0)
+
+/* Don't allow the total size of the HTTP headers (including the status
+ * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect
+ * embedders against denial-of-service attacks where the attacker feeds
+ * us a never-ending header that the embedder keeps buffering.
+ *
+ * This check is arguably the responsibility of embedders but we're doing
+ * it on the embedder's behalf because most won't bother and this way we
+ * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger
+ * than any reasonable request or response so this should never affect
+ * day-to-day operation.
+ */
+#define COUNT_HEADER_SIZE(V) \
+do { \
+ parser->nread += (V); \
+ if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \
+ SET_ERRNO(HPE_HEADER_OVERFLOW); \
+ goto error; \
+ } \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+ {
+#define XX(num, name, string) #string,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, '!', 0, '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', 0,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'x', 'y', 'z', 0, 0, 0, '^', '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };
+
+
+static const int8_t unhex[256] =
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
+
+#undef T
+
+enum state
+ { s_dead = 1 /* important that this is > 0 */
+
+ , s_start_req_or_res
+ , s_res_or_resp_H
+ , s_start_res
+ , s_res_H
+ , s_res_HT
+ , s_res_HTT
+ , s_res_HTTP
+ , s_res_http_major
+ , s_res_http_dot
+ , s_res_http_minor
+ , s_res_http_end
+ , s_res_first_status_code
+ , s_res_status_code
+ , s_res_status_start
+ , s_res_status
+ , s_res_line_almost_done
+
+ , s_start_req
+
+ , s_req_method
+ , s_req_spaces_before_url
+ , s_req_schema
+ , s_req_schema_slash
+ , s_req_schema_slash_slash
+ , s_req_server_start
+ , s_req_server
+ , s_req_server_with_at
+ , s_req_path
+ , s_req_query_string_start
+ , s_req_query_string
+ , s_req_fragment_start
+ , s_req_fragment
+ , s_req_http_start
+ , s_req_http_H
+ , s_req_http_HT
+ , s_req_http_HTT
+ , s_req_http_HTTP
+ , s_req_http_major
+ , s_req_http_dot
+ , s_req_http_minor
+ , s_req_http_end
+ , s_req_line_almost_done
+
+ , s_header_field_start
+ , s_header_field
+ , s_header_value_discard_ws
+ , s_header_value_discard_ws_almost_done
+ , s_header_value_discard_lws
+ , s_header_value_start
+ , s_header_value
+ , s_header_value_lws
+
+ , s_header_almost_done
+
+ , s_chunk_size_start
+ , s_chunk_size
+ , s_chunk_parameters
+ , s_chunk_size_almost_done
+
+ , s_headers_almost_done
+ , s_headers_done
+
+ /* Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+
+ , s_chunk_data
+ , s_chunk_data_almost_done
+ , s_chunk_data_done
+
+ , s_body_identity
+ , s_body_identity_eof
+
+ , s_message_done
+ };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+ { h_general = 0
+ , h_C
+ , h_CO
+ , h_CON
+
+ , h_matching_connection
+ , h_matching_proxy_connection
+ , h_matching_content_length
+ , h_matching_transfer_encoding
+ , h_matching_upgrade
+
+ , h_connection
+ , h_content_length
+ , h_content_length_num
+ , h_content_length_ws
+ , h_transfer_encoding
+ , h_upgrade
+
+ , h_matching_transfer_encoding_chunked
+ , h_matching_connection_token_start
+ , h_matching_connection_keep_alive
+ , h_matching_connection_close
+ , h_matching_connection_upgrade
+ , h_matching_connection_token
+
+ , h_transfer_encoding_chunked
+ , h_connection_keep_alive
+ , h_connection_close
+ , h_connection_upgrade
+ };
+
+enum http_host_state
+ {
+ s_http_host_dead = 1
+ , s_http_userinfo_start
+ , s_http_userinfo
+ , s_http_host_start
+ , s_http_host_v6_start
+ , s_http_host
+ , s_http_host_v6
+ , s_http_host_v6_end
+ , s_http_host_v6_zone_start
+ , s_http_host_v6_zone
+ , s_http_host_port_start
+ , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode */
+#define CR '\r'
+#define LF '\n'
+#define LOWER(c) (unsigned char)(c | 0x20)
+#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+ (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
+
+#define STRICT_TOKEN(c) (tokens[(unsigned char)c])
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c) (tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) \
+ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c) \
+ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+/**
+ * Verify that a char is a valid visible (printable) US-ASCII
+ * character or %x80-FF
+ **/
+#define IS_HEADER_CHAR(ch) \
+ (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond) \
+do { \
+ if (cond) { \
+ SET_ERRNO(HPE_STRICT); \
+ goto error; \
+ } \
+} while (0)
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+/* Map errno values to strings for human-readable output */
+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
+static struct {
+ const char *name;
+ const char *description;
+} http_strerror_tab[] = {
+ HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
+};
+#undef HTTP_STRERROR_GEN
+
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return s_dead;
+ }
+
+#if HTTP_PARSER_STRICT
+ if (ch == '\t' || ch == '\f') {
+ return s_dead;
+ }
+#endif
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return s_req_path;
+ }
+
+ if (IS_ALPHA(ch)) {
+ return s_req_schema;
+ }
+
+ break;
+
+ case s_req_schema:
+ if (IS_ALPHA(ch)) {
+ return s;
+ }
+
+ if (ch == ':') {
+ return s_req_schema_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return s_req_schema_slash_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return s_req_server_start;
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return s_dead;
+ }
+
+ /* FALLTHROUGH */
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return s_req_path;
+ }
+
+ if (ch == '?') {
+ return s_req_query_string_start;
+ }
+
+ if (ch == '@') {
+ return s_req_server_with_at;
+ }
+
+ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return s_req_server;
+ }
+
+ break;
+
+ case s_req_path:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_query_string_start;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_query_string;
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return s_req_query_string;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_fragment_start:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_fragment;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_fragment;
+
+ case '#':
+ return s;
+ }
+
+ break;
+
+ case s_req_fragment:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return s;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return s_dead;
+}
+
+size_t http_parser_execute (http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len)
+{
+ char c, ch;
+ int8_t unhex_val;
+ const char *p = data;
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *url_mark = 0;
+ const char *body_mark = 0;
+ const char *status_mark = 0;
+ enum state p_state = (enum state) parser->state;
+ const unsigned int lenient = parser->lenient_http_headers;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return 0;
+ }
+
+ if (len == 0) {
+ switch (CURRENT_STATE()) {
+ case s_body_identity_eof:
+ /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+ * we got paused.
+ */
+ CALLBACK_NOTIFY_NOADVANCE(message_complete);
+ return 0;
+
+ case s_dead:
+ case s_start_req_or_res:
+ case s_start_res:
+ case s_start_req:
+ return 0;
+
+ default:
+ SET_ERRNO(HPE_INVALID_EOF_STATE);
+ return 1;
+ }
+ }
+
+
+ if (CURRENT_STATE() == s_header_field)
+ header_field_mark = data;
+ if (CURRENT_STATE() == s_header_value)
+ header_value_mark = data;
+ switch (CURRENT_STATE()) {
+ case s_req_path:
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ url_mark = data;
+ break;
+ case s_res_status:
+ status_mark = data;
+ break;
+ default:
+ break;
+ }
+
+ for (p=data; p != data + len; p++) {
+ ch = *p;
+
+ if (PARSING_HEADER(CURRENT_STATE()))
+ COUNT_HEADER_SIZE(1);
+
+reexecute:
+ switch (CURRENT_STATE()) {
+
+ case s_dead:
+ /* this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ if (LIKELY(ch == CR || ch == LF))
+ break;
+
+ SET_ERRNO(HPE_CLOSED_CONNECTION);
+ goto error;
+
+ case s_start_req_or_res:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (ch == 'H') {
+ UPDATE_STATE(s_res_or_resp_H);
+
+ CALLBACK_NOTIFY(message_begin);
+ } else {
+ parser->type = HTTP_REQUEST;
+ UPDATE_STATE(s_start_req);
+ REEXECUTE();
+ }
+
+ break;
+ }
+
+ case s_res_or_resp_H:
+ if (ch == 'T') {
+ parser->type = HTTP_RESPONSE;
+ UPDATE_STATE(s_res_HT);
+ } else {
+ if (UNLIKELY(ch != 'E')) {
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ parser->type = HTTP_REQUEST;
+ parser->method = HTTP_HEAD;
+ parser->index = 2;
+ UPDATE_STATE(s_req_method);
+ }
+ break;
+
+ case s_start_res:
+ {
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ switch (ch) {
+ case 'H':
+ UPDATE_STATE(s_res_H);
+ break;
+
+ case CR:
+ case LF:
+ break;
+
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ CALLBACK_NOTIFY(message_begin);
+ break;
+ }
+
+ case s_res_H:
+ STRICT_CHECK(ch != 'T');
+ UPDATE_STATE(s_res_HT);
+ break;
+
+ case s_res_HT:
+ STRICT_CHECK(ch != 'T');
+ UPDATE_STATE(s_res_HTT);
+ break;
+
+ case s_res_HTT:
+ STRICT_CHECK(ch != 'P');
+ UPDATE_STATE(s_res_HTTP);
+ break;
+
+ case s_res_HTTP:
+ STRICT_CHECK(ch != '/');
+ UPDATE_STATE(s_res_http_major);
+ break;
+
+ case s_res_http_major:
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ UPDATE_STATE(s_res_http_dot);
+ break;
+
+ case s_res_http_dot:
+ {
+ if (UNLIKELY(ch != '.')) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ UPDATE_STATE(s_res_http_minor);
+ break;
+ }
+
+ case s_res_http_minor:
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ UPDATE_STATE(s_res_http_end);
+ break;
+
+ case s_res_http_end:
+ {
+ if (UNLIKELY(ch != ' ')) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ UPDATE_STATE(s_res_first_status_code);
+ break;
+ }
+
+ case s_res_first_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ if (ch == ' ') {
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ parser->status_code = ch - '0';
+ UPDATE_STATE(s_res_status_code);
+ break;
+ }
+
+ case s_res_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ switch (ch) {
+ case ' ':
+ UPDATE_STATE(s_res_status_start);
+ break;
+ case CR:
+ case LF:
+ UPDATE_STATE(s_res_status_start);
+ REEXECUTE();
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ break;
+ }
+
+ parser->status_code *= 10;
+ parser->status_code += ch - '0';
+
+ if (UNLIKELY(parser->status_code > 999)) {
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_res_status_start:
+ {
+ MARK(status);
+ UPDATE_STATE(s_res_status);
+ parser->index = 0;
+
+ if (ch == CR || ch == LF)
+ REEXECUTE();
+
+ break;
+ }
+
+ case s_res_status:
+ if (ch == CR) {
+ UPDATE_STATE(s_res_line_almost_done);
+ CALLBACK_DATA(status);
+ break;
+ }
+
+ if (ch == LF) {
+ UPDATE_STATE(s_header_field_start);
+ CALLBACK_DATA(status);
+ break;
+ }
+
+ break;
+
+ case s_res_line_almost_done:
+ STRICT_CHECK(ch != LF);
+ UPDATE_STATE(s_header_field_start);
+ break;
+
+ case s_start_req:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (UNLIKELY(!IS_ALPHA(ch))) {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ parser->method = (enum http_method) 0;
+ parser->index = 1;
+ switch (ch) {
+ case 'A': parser->method = HTTP_ACL; break;
+ case 'B': parser->method = HTTP_BIND; break;
+ case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+ case 'D': parser->method = HTTP_DELETE; break;
+ case 'G': parser->method = HTTP_GET; break;
+ case 'H': parser->method = HTTP_HEAD; break;
+ case 'L': parser->method = HTTP_LOCK; /* or LINK */ break;
+ case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
+ case 'N': parser->method = HTTP_NOTIFY; break;
+ case 'O': parser->method = HTTP_OPTIONS; break;
+ case 'P': parser->method = HTTP_POST;
+ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+ break;
+ case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
+ case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break;
+ case 'T': parser->method = HTTP_TRACE; break;
+ case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
+ default:
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+ UPDATE_STATE(s_req_method);
+
+ CALLBACK_NOTIFY(message_begin);
+
+ break;
+ }
+
+ case s_req_method:
+ {
+ const char *matcher;
+ if (UNLIKELY(ch == '\0')) {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[parser->index] == '\0') {
+ UPDATE_STATE(s_req_spaces_before_url);
+ } else if (ch == matcher[parser->index]) {
+ ; /* nada */
+ } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
+
+ switch (parser->method << 16 | parser->index << 8 | ch) {
+#define XX(meth, pos, ch, new_meth) \
+ case (HTTP_##meth << 16 | pos << 8 | ch): \
+ parser->method = HTTP_##new_meth; break;
+
+ XX(POST, 1, 'U', PUT)
+ XX(POST, 1, 'A', PATCH)
+ XX(POST, 1, 'R', PROPFIND)
+ XX(PUT, 2, 'R', PURGE)
+ XX(CONNECT, 1, 'H', CHECKOUT)
+ XX(CONNECT, 2, 'P', COPY)
+ XX(MKCOL, 1, 'O', MOVE)
+ XX(MKCOL, 1, 'E', MERGE)
+ XX(MKCOL, 1, '-', MSEARCH)
+ XX(MKCOL, 2, 'A', MKACTIVITY)
+ XX(MKCOL, 3, 'A', MKCALENDAR)
+ XX(SUBSCRIBE, 1, 'E', SEARCH)
+ XX(SUBSCRIBE, 1, 'O', SOURCE)
+ XX(REPORT, 2, 'B', REBIND)
+ XX(PROPFIND, 4, 'P', PROPPATCH)
+ XX(LOCK, 1, 'I', LINK)
+ XX(UNLOCK, 2, 'S', UNSUBSCRIBE)
+ XX(UNLOCK, 2, 'B', UNBIND)
+ XX(UNLOCK, 3, 'I', UNLINK)
+#undef XX
+ default:
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+ } else {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ ++parser->index;
+ break;
+ }
+
+ case s_req_spaces_before_url:
+ {
+ if (ch == ' ') break;
+
+ MARK(url);
+ if (parser->method == HTTP_CONNECT) {
+ UPDATE_STATE(s_req_server_start);
+ }
+
+ UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+ if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ {
+ switch (ch) {
+ /* No whitespace allowed here */
+ case ' ':
+ case CR:
+ case LF:
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ default:
+ UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+ if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+
+ break;
+ }
+
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_path:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ {
+ switch (ch) {
+ case ' ':
+ UPDATE_STATE(s_req_http_start);
+ CALLBACK_DATA(url);
+ break;
+ case CR:
+ case LF:
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ UPDATE_STATE((ch == CR) ?
+ s_req_line_almost_done :
+ s_header_field_start);
+ CALLBACK_DATA(url);
+ break;
+ default:
+ UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+ if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+ break;
+ }
+
+ case s_req_http_start:
+ switch (ch) {
+ case 'H':
+ UPDATE_STATE(s_req_http_H);
+ break;
+ case ' ':
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+ break;
+
+ case s_req_http_H:
+ STRICT_CHECK(ch != 'T');
+ UPDATE_STATE(s_req_http_HT);
+ break;
+
+ case s_req_http_HT:
+ STRICT_CHECK(ch != 'T');
+ UPDATE_STATE(s_req_http_HTT);
+ break;
+
+ case s_req_http_HTT:
+ STRICT_CHECK(ch != 'P');
+ UPDATE_STATE(s_req_http_HTTP);
+ break;
+
+ case s_req_http_HTTP:
+ STRICT_CHECK(ch != '/');
+ UPDATE_STATE(s_req_http_major);
+ break;
+
+ case s_req_http_major:
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ UPDATE_STATE(s_req_http_dot);
+ break;
+
+ case s_req_http_dot:
+ {
+ if (UNLIKELY(ch != '.')) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ UPDATE_STATE(s_req_http_minor);
+ break;
+ }
+
+ case s_req_http_minor:
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ UPDATE_STATE(s_req_http_end);
+ break;
+
+ case s_req_http_end:
+ {
+ if (ch == CR) {
+ UPDATE_STATE(s_req_line_almost_done);
+ break;
+ }
+
+ if (ch == LF) {
+ UPDATE_STATE(s_header_field_start);
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ break;
+ }
+
+ /* end of request line */
+ case s_req_line_almost_done:
+ {
+ if (UNLIKELY(ch != LF)) {
+ SET_ERRNO(HPE_LF_EXPECTED);
+ goto error;
+ }
+
+ UPDATE_STATE(s_header_field_start);
+ break;
+ }
+
+ case s_header_field_start:
+ {
+ if (ch == CR) {
+ UPDATE_STATE(s_headers_almost_done);
+ break;
+ }
+
+ if (ch == LF) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ UPDATE_STATE(s_headers_almost_done);
+ REEXECUTE();
+ }
+
+ c = TOKEN(ch);
+
+ if (UNLIKELY(!c)) {
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ MARK(header_field);
+
+ parser->index = 0;
+ UPDATE_STATE(s_header_field);
+
+ switch (c) {
+ case 'c':
+ parser->header_state = h_C;
+ break;
+
+ case 'p':
+ parser->header_state = h_matching_proxy_connection;
+ break;
+
+ case 't':
+ parser->header_state = h_matching_transfer_encoding;
+ break;
+
+ case 'u':
+ parser->header_state = h_matching_upgrade;
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_field:
+ {
+ const char* start = p;
+ for (; p != data + len; p++) {
+ ch = *p;
+ c = TOKEN(ch);
+
+ if (!c)
+ break;
+
+ switch (parser->header_state) {
+ case h_general:
+ break;
+
+ case h_C:
+ parser->index++;
+ parser->header_state = (c == 'o' ? h_CO : h_general);
+ break;
+
+ case h_CO:
+ parser->index++;
+ parser->header_state = (c == 'n' ? h_CON : h_general);
+ break;
+
+ case h_CON:
+ parser->index++;
+ switch (c) {
+ case 'n':
+ parser->header_state = h_matching_connection;
+ break;
+ case 't':
+ parser->header_state = h_matching_content_length;
+ break;
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case h_matching_connection:
+ parser->index++;
+ if (parser->index > sizeof(CONNECTION)-1
+ || c != CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case h_matching_proxy_connection:
+ parser->index++;
+ if (parser->index > sizeof(PROXY_CONNECTION)-1
+ || c != PROXY_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* content-length */
+
+ case h_matching_content_length:
+ parser->index++;
+ if (parser->index > sizeof(CONTENT_LENGTH)-1
+ || c != CONTENT_LENGTH[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+ parser->header_state = h_content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case h_matching_transfer_encoding:
+ parser->index++;
+ if (parser->index > sizeof(TRANSFER_ENCODING)-1
+ || c != TRANSFER_ENCODING[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+ parser->header_state = h_transfer_encoding;
+ }
+ break;
+
+ /* upgrade */
+
+ case h_matching_upgrade:
+ parser->index++;
+ if (parser->index > sizeof(UPGRADE)-1
+ || c != UPGRADE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(UPGRADE)-2) {
+ parser->header_state = h_upgrade;
+ }
+ break;
+
+ case h_connection:
+ case h_content_length:
+ case h_transfer_encoding:
+ case h_upgrade:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ assert(0 && "Unknown header_state");
+ break;
+ }
+ }
+
+ COUNT_HEADER_SIZE(p - start);
+
+ if (p == data + len) {
+ --p;
+ break;
+ }
+
+ if (ch == ':') {
+ UPDATE_STATE(s_header_value_discard_ws);
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ case s_header_value_discard_ws:
+ if (ch == ' ' || ch == '\t') break;
+
+ if (ch == CR) {
+ UPDATE_STATE(s_header_value_discard_ws_almost_done);
+ break;
+ }
+
+ if (ch == LF) {
+ UPDATE_STATE(s_header_value_discard_lws);
+ break;
+ }
+
+ /* FALLTHROUGH */
+
+ case s_header_value_start:
+ {
+ MARK(header_value);
+
+ UPDATE_STATE(s_header_value);
+ parser->index = 0;
+
+ c = LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_upgrade:
+ parser->flags |= F_UPGRADE;
+ parser->header_state = h_general;
+ break;
+
+ case h_transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ parser->header_state = h_matching_transfer_encoding_chunked;
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ case h_content_length:
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ if (parser->flags & F_CONTENTLENGTH) {
+ SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->flags |= F_CONTENTLENGTH;
+ parser->content_length = ch - '0';
+ parser->header_state = h_content_length_num;
+ break;
+
+ case h_connection:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ parser->header_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ parser->header_state = h_matching_connection_close;
+ } else if (c == 'u') {
+ parser->header_state = h_matching_connection_upgrade;
+ } else {
+ parser->header_state = h_matching_connection_token;
+ }
+ break;
+
+ /* Multi-value `Connection` header */
+ case h_matching_connection_token_start:
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_value:
+ {
+ const char* start = p;
+ enum header_states h_state = (enum header_states) parser->header_state;
+ for (; p != data + len; p++) {
+ ch = *p;
+ if (ch == CR) {
+ UPDATE_STATE(s_header_almost_done);
+ parser->header_state = h_state;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == LF) {
+ UPDATE_STATE(s_header_almost_done);
+ COUNT_HEADER_SIZE(p - start);
+ parser->header_state = h_state;
+ CALLBACK_DATA_NOADVANCE(header_value);
+ REEXECUTE();
+ }
+
+ if (!lenient && !IS_HEADER_CHAR(ch)) {
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ c = LOWER(ch);
+
+ switch (h_state) {
+ case h_general:
+ {
+ const char* p_cr;
+ const char* p_lf;
+ size_t limit = data + len - p;
+
+ limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
+
+ p_cr = (const char*) memchr(p, CR, limit);
+ p_lf = (const char*) memchr(p, LF, limit);
+ if (p_cr != NULL) {
+ if (p_lf != NULL && p_cr >= p_lf)
+ p = p_lf;
+ else
+ p = p_cr;
+ } else if (UNLIKELY(p_lf != NULL)) {
+ p = p_lf;
+ } else {
+ p = data + len;
+ }
+ --p;
+
+ break;
+ }
+
+ case h_connection:
+ case h_transfer_encoding:
+ assert(0 && "Shouldn't get here.");
+ break;
+
+ case h_content_length:
+ if (ch == ' ') break;
+ h_state = h_content_length_num;
+ /* FALLTHROUGH */
+
+ case h_content_length_num:
+ {
+ uint64_t t;
+
+ if (ch == ' ') {
+ h_state = h_content_length_ws;
+ break;
+ }
+
+ if (UNLIKELY(!IS_NUM(ch))) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 10;
+ t += ch - '0';
+
+ /* Overflow? Test against a conservative limit for simplicity. */
+ if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case h_content_length_ws:
+ if (ch == ' ') break;
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+
+ /* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_chunked:
+ parser->index++;
+ if (parser->index > sizeof(CHUNKED)-1
+ || c != CHUNKED[parser->index]) {
+ h_state = h_general;
+ } else if (parser->index == sizeof(CHUNKED)-2) {
+ h_state = h_transfer_encoding_chunked;
+ }
+ break;
+
+ case h_matching_connection_token_start:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ h_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ h_state = h_matching_connection_close;
+ } else if (c == 'u') {
+ h_state = h_matching_connection_upgrade;
+ } else if (STRICT_TOKEN(c)) {
+ h_state = h_matching_connection_token;
+ } else if (c == ' ' || c == '\t') {
+ /* Skip lws */
+ } else {
+ h_state = h_general;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case h_matching_connection_keep_alive:
+ parser->index++;
+ if (parser->index > sizeof(KEEP_ALIVE)-1
+ || c != KEEP_ALIVE[parser->index]) {
+ h_state = h_matching_connection_token;
+ } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+ h_state = h_connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case h_matching_connection_close:
+ parser->index++;
+ if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+ h_state = h_matching_connection_token;
+ } else if (parser->index == sizeof(CLOSE)-2) {
+ h_state = h_connection_close;
+ }
+ break;
+
+ /* looking for 'Connection: upgrade' */
+ case h_matching_connection_upgrade:
+ parser->index++;
+ if (parser->index > sizeof(UPGRADE) - 1 ||
+ c != UPGRADE[parser->index]) {
+ h_state = h_matching_connection_token;
+ } else if (parser->index == sizeof(UPGRADE)-2) {
+ h_state = h_connection_upgrade;
+ }
+ break;
+
+ case h_matching_connection_token:
+ if (ch == ',') {
+ h_state = h_matching_connection_token_start;
+ parser->index = 0;
+ }
+ break;
+
+ case h_transfer_encoding_chunked:
+ if (ch != ' ') h_state = h_general;
+ break;
+
+ case h_connection_keep_alive:
+ case h_connection_close:
+ case h_connection_upgrade:
+ if (ch == ',') {
+ if (h_state == h_connection_keep_alive) {
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ } else if (h_state == h_connection_close) {
+ parser->flags |= F_CONNECTION_CLOSE;
+ } else if (h_state == h_connection_upgrade) {
+ parser->flags |= F_CONNECTION_UPGRADE;
+ }
+ h_state = h_matching_connection_token_start;
+ parser->index = 0;
+ } else if (ch != ' ') {
+ h_state = h_matching_connection_token;
+ }
+ break;
+
+ default:
+ UPDATE_STATE(s_header_value);
+ h_state = h_general;
+ break;
+ }
+ }
+ parser->header_state = h_state;
+
+ COUNT_HEADER_SIZE(p - start);
+
+ if (p == data + len)
+ --p;
+ break;
+ }
+
+ case s_header_almost_done:
+ {
+ if (UNLIKELY(ch != LF)) {
+ SET_ERRNO(HPE_LF_EXPECTED);
+ goto error;
+ }
+
+ UPDATE_STATE(s_header_value_lws);
+ break;
+ }
+
+ case s_header_value_lws:
+ {
+ if (ch == ' ' || ch == '\t') {
+ UPDATE_STATE(s_header_value_start);
+ REEXECUTE();
+ }
+
+ /* finished the header */
+ switch (parser->header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ case h_connection_upgrade:
+ parser->flags |= F_CONNECTION_UPGRADE;
+ break;
+ default:
+ break;
+ }
+
+ UPDATE_STATE(s_header_field_start);
+ REEXECUTE();
+ }
+
+ case s_header_value_discard_ws_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+ UPDATE_STATE(s_header_value_discard_lws);
+ break;
+ }
+
+ case s_header_value_discard_lws:
+ {
+ if (ch == ' ' || ch == '\t') {
+ UPDATE_STATE(s_header_value_discard_ws);
+ break;
+ } else {
+ switch (parser->header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_connection_upgrade:
+ parser->flags |= F_CONNECTION_UPGRADE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+
+ /* header value was empty */
+ MARK(header_value);
+ UPDATE_STATE(s_header_field_start);
+ CALLBACK_DATA_NOADVANCE(header_value);
+ REEXECUTE();
+ }
+ }
+
+ case s_headers_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ if (parser->flags & F_TRAILING) {
+ /* End of a chunked request */
+ UPDATE_STATE(s_message_done);
+ CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
+ REEXECUTE();
+ }
+
+ /* Cannot use chunked encoding and a content-length header together
+ per the HTTP specification. */
+ if ((parser->flags & F_CHUNKED) &&
+ (parser->flags & F_CONTENTLENGTH)) {
+ SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
+
+ UPDATE_STATE(s_headers_done);
+
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ if ((parser->flags & F_UPGRADE) &&
+ (parser->flags & F_CONNECTION_UPGRADE)) {
+ /* For responses, "Upgrade: foo" and "Connection: upgrade" are
+ * mandatory only when it is a 101 Switching Protocols response,
+ * otherwise it is purely informational, to announce support.
+ */
+ parser->upgrade =
+ (parser->type == HTTP_REQUEST || parser->status_code == 101);
+ } else {
+ parser->upgrade = (parser->method == HTTP_CONNECT);
+ }
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ *
+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
+ */
+ if (settings->on_headers_complete) {
+ switch (settings->on_headers_complete(parser)) {
+ case 0:
+ break;
+
+ case 2:
+ parser->upgrade = 1;
+
+ /* FALLTHROUGH */
+ case 1:
+ parser->flags |= F_SKIPBODY;
+ break;
+
+ default:
+ SET_ERRNO(HPE_CB_headers_complete);
+ RETURN(p - data); /* Error */
+ }
+ }
+
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ RETURN(p - data);
+ }
+
+ REEXECUTE();
+ }
+
+ case s_headers_done:
+ {
+ int hasBody;
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ hasBody = parser->flags & F_CHUNKED ||
+ (parser->content_length > 0 && parser->content_length != ULLONG_MAX);
+ if (parser->upgrade && (parser->method == HTTP_CONNECT ||
+ (parser->flags & F_SKIPBODY) || !hasBody)) {
+ /* Exit, the rest of the message is in a different protocol. */
+ UPDATE_STATE(NEW_MESSAGE());
+ CALLBACK_NOTIFY(message_complete);
+ RETURN((p - data) + 1);
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ UPDATE_STATE(NEW_MESSAGE());
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header */
+ UPDATE_STATE(s_chunk_size_start);
+ } else {
+ if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ UPDATE_STATE(NEW_MESSAGE());
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->content_length != ULLONG_MAX) {
+ /* Content-Length header given and non-zero */
+ UPDATE_STATE(s_body_identity);
+ } else {
+ if (!http_message_needs_eof(parser)) {
+ /* Assume content-length 0 - read the next */
+ UPDATE_STATE(NEW_MESSAGE());
+ CALLBACK_NOTIFY(message_complete);
+ } else {
+ /* Read body until EOF */
+ UPDATE_STATE(s_body_identity_eof);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case s_body_identity:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* The difference between advancing content_length and p is because
+ * the latter will automaticaly advance on the next loop iteration.
+ * Further, if content_length ends up at 0, we want to see the last
+ * byte again for our message complete callback.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ UPDATE_STATE(s_message_done);
+
+ /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+ *
+ * The alternative to doing this is to wait for the next byte to
+ * trigger the data callback, just as in every other case. The
+ * problem with this is that this makes it difficult for the test
+ * harness to distinguish between complete-on-EOF and
+ * complete-on-length. It's not clear that this distinction is
+ * important for applications, but let's keep it for now.
+ */
+ CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+ REEXECUTE();
+ }
+
+ break;
+ }
+
+ /* read until EOF */
+ case s_body_identity_eof:
+ MARK(body);
+ p = data + len - 1;
+
+ break;
+
+ case s_message_done:
+ UPDATE_STATE(NEW_MESSAGE());
+ CALLBACK_NOTIFY(message_complete);
+ if (parser->upgrade) {
+ /* Exit, the rest of the message is in a different protocol. */
+ RETURN((p - data) + 1);
+ }
+ break;
+
+ case s_chunk_size_start:
+ {
+ assert(parser->nread == 1);
+ assert(parser->flags & F_CHUNKED);
+
+ unhex_val = unhex[(unsigned char)ch];
+ if (UNLIKELY(unhex_val == -1)) {
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ parser->content_length = unhex_val;
+ UPDATE_STATE(s_chunk_size);
+ break;
+ }
+
+ case s_chunk_size:
+ {
+ uint64_t t;
+
+ assert(parser->flags & F_CHUNKED);
+
+ if (ch == CR) {
+ UPDATE_STATE(s_chunk_size_almost_done);
+ break;
+ }
+
+ unhex_val = unhex[(unsigned char)ch];
+
+ if (unhex_val == -1) {
+ if (ch == ';' || ch == ' ') {
+ UPDATE_STATE(s_chunk_parameters);
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 16;
+ t += unhex_val;
+
+ /* Overflow? Test against a conservative limit for simplicity. */
+ if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case s_chunk_parameters:
+ {
+ assert(parser->flags & F_CHUNKED);
+ /* just ignore this shit. TODO check for overflow */
+ if (ch == CR) {
+ UPDATE_STATE(s_chunk_size_almost_done);
+ break;
+ }
+ break;
+ }
+
+ case s_chunk_size_almost_done:
+ {
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ if (parser->content_length == 0) {
+ parser->flags |= F_TRAILING;
+ UPDATE_STATE(s_header_field_start);
+ } else {
+ UPDATE_STATE(s_chunk_data);
+ }
+ CALLBACK_NOTIFY(chunk_header);
+ break;
+ }
+
+ case s_chunk_data:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* See the explanation in s_body_identity for why the content
+ * length and data pointers are managed this way.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ UPDATE_STATE(s_chunk_data_almost_done);
+ }
+
+ break;
+ }
+
+ case s_chunk_data_almost_done:
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length == 0);
+ STRICT_CHECK(ch != CR);
+ UPDATE_STATE(s_chunk_data_done);
+ CALLBACK_DATA(body);
+ break;
+
+ case s_chunk_data_done:
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+ parser->nread = 0;
+ UPDATE_STATE(s_chunk_size_start);
+ CALLBACK_NOTIFY(chunk_complete);
+ break;
+
+ default:
+ assert(0 && "unhandled state");
+ SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
+ goto error;
+ }
+ }
+
+ /* Run callbacks for any marks that we have leftover after we ran our of
+ * bytes. There should be at most one of these set, so it's OK to invoke
+ * them in series (unset marks will not result in callbacks).
+ *
+ * We use the NOADVANCE() variety of callbacks here because 'p' has already
+ * overflowed 'data' and this allows us to correct for the off-by-one that
+ * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+ * value that's in-bounds).
+ */
+
+ assert(((header_field_mark ? 1 : 0) +
+ (header_value_mark ? 1 : 0) +
+ (url_mark ? 1 : 0) +
+ (body_mark ? 1 : 0) +
+ (status_mark ? 1 : 0)) <= 1);
+
+ CALLBACK_DATA_NOADVANCE(header_field);
+ CALLBACK_DATA_NOADVANCE(header_value);
+ CALLBACK_DATA_NOADVANCE(url);
+ CALLBACK_DATA_NOADVANCE(body);
+ CALLBACK_DATA_NOADVANCE(status);
+
+ RETURN(len);
+
+error:
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
+ SET_ERRNO(HPE_UNKNOWN);
+ }
+
+ RETURN(p - data);
+}
+
+
+/* Does the parser need to see an EOF to find the end of the message? */
+int
+http_message_needs_eof (const http_parser *parser)
+{
+ if (parser->type == HTTP_REQUEST) {
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 || /* Not Modified */
+ parser->flags & F_SKIPBODY) { /* response to a HEAD request */
+ return 0;
+ }
+
+ if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
+{
+ if (parser->http_major > 0 && parser->http_minor > 0) {
+ /* HTTP/1.1 */
+ if (parser->flags & F_CONNECTION_CLOSE) {
+ return 0;
+ }
+ } else {
+ /* HTTP/1.0 or earlier */
+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+ return 0;
+ }
+ }
+
+ return !http_message_needs_eof(parser);
+}
+
+
+const char *
+http_method_str (enum http_method m)
+{
+ return ELEM_AT(method_strings, m, "<unknown>");
+}
+
+
+void
+http_parser_init (http_parser *parser, enum http_parser_type t)
+{
+ void *data = parser->data; /* preserve application data */
+ memset(parser, 0, sizeof(*parser));
+ parser->data = data;
+ parser->type = t;
+ parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+ parser->http_errno = HPE_OK;
+}
+
+void
+http_parser_settings_init(http_parser_settings *settings)
+{
+ memset(settings, 0, sizeof(*settings));
+}
+
+const char *
+http_errno_name(enum http_errno err) {
+ assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
+ return http_strerror_tab[err].name;
+}
+
+const char *
+http_errno_description(enum http_errno err) {
+ assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
+ return http_strerror_tab[err].description;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+ switch(s) {
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ if (ch == '@') {
+ return s_http_host_start;
+ }
+
+ if (IS_USERINFO_CHAR(ch)) {
+ return s_http_userinfo;
+ }
+ break;
+
+ case s_http_host_start:
+ if (ch == '[') {
+ return s_http_host_v6_start;
+ }
+
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ break;
+
+ case s_http_host:
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_end:
+ if (ch == ':') {
+ return s_http_host_port_start;
+ }
+
+ break;
+
+ case s_http_host_v6:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_start:
+ if (IS_HEX(ch) || ch == ':' || ch == '.') {
+ return s_http_host_v6;
+ }
+
+ if (s == s_http_host_v6 && ch == '%') {
+ return s_http_host_v6_zone_start;
+ }
+ break;
+
+ case s_http_host_v6_zone:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_zone_start:
+ /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
+ if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
+ ch == '~') {
+ return s_http_host_v6_zone;
+ }
+ break;
+
+ case s_http_host_port:
+ case s_http_host_port_start:
+ if (IS_NUM(ch)) {
+ return s_http_host_port;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+ enum http_host_state s;
+
+ const char *p;
+ size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+ assert(u->field_set & (1 << UF_HOST));
+
+ u->field_data[UF_HOST].len = 0;
+
+ s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+ for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+ enum http_host_state new_s = http_parse_host_char(s, *p);
+
+ if (new_s == s_http_host_dead) {
+ return 1;
+ }
+
+ switch(new_s) {
+ case s_http_host:
+ if (s != s_http_host) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6:
+ if (s != s_http_host_v6) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6_zone_start:
+ case s_http_host_v6_zone:
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ u->field_data[UF_PORT].off = p - buf;
+ u->field_data[UF_PORT].len = 0;
+ u->field_set |= (1 << UF_PORT);
+ }
+ u->field_data[UF_PORT].len++;
+ break;
+
+ case s_http_userinfo:
+ if (s != s_http_userinfo) {
+ u->field_data[UF_USERINFO].off = p - buf ;
+ u->field_data[UF_USERINFO].len = 0;
+ u->field_set |= (1 << UF_USERINFO);
+ }
+ u->field_data[UF_USERINFO].len++;
+ break;
+
+ default:
+ break;
+ }
+ s = new_s;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (s) {
+ case s_http_host_start:
+ case s_http_host_v6_start:
+ case s_http_host_v6:
+ case s_http_host_v6_zone_start:
+ case s_http_host_v6_zone:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void
+http_parser_url_init(struct http_parser_url *u) {
+ memset(u, 0, sizeof(*u));
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+ struct http_parser_url *u)
+{
+ enum state s;
+ const char *p;
+ enum http_parser_url_fields uf, old_uf;
+ int found_at = 0;
+
+ u->port = u->field_set = 0;
+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+ old_uf = UF_MAX;
+
+ for (p = buf; p < buf + buflen; p++) {
+ s = parse_url_char(s, *p);
+
+ /* Figure out the next field that we're operating on */
+ switch (s) {
+ case s_dead:
+ return 1;
+
+ /* Skip delimeters */
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_query_string_start:
+ case s_req_fragment_start:
+ continue;
+
+ case s_req_schema:
+ uf = UF_SCHEMA;
+ break;
+
+ case s_req_server_with_at:
+ found_at = 1;
+
+ /* FALLTHROUGH */
+ case s_req_server:
+ uf = UF_HOST;
+ break;
+
+ case s_req_path:
+ uf = UF_PATH;
+ break;
+
+ case s_req_query_string:
+ uf = UF_QUERY;
+ break;
+
+ case s_req_fragment:
+ uf = UF_FRAGMENT;
+ break;
+
+ default:
+ assert(!"Unexpected state");
+ return 1;
+ }
+
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ u->field_data[uf].len++;
+ continue;
+ }
+
+ u->field_data[uf].off = p - buf;
+ u->field_data[uf].len = 1;
+
+ u->field_set |= (1 << uf);
+ old_uf = uf;
+ }
+
+ /* host must be present if there is a schema */
+ /* parsing http:///toto will fail */
+ if ((u->field_set & (1 << UF_SCHEMA)) &&
+ (u->field_set & (1 << UF_HOST)) == 0) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_HOST)) {
+ if (http_parse_host(buf, u, found_at) != 0) {
+ return 1;
+ }
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_PORT)) {
+ uint16_t off;
+ uint16_t len;
+ const char* p;
+ const char* end;
+ unsigned long v;
+
+ off = u->field_data[UF_PORT].off;
+ len = u->field_data[UF_PORT].len;
+ end = buf + off + len;
+
+ /* NOTE: The characters are already validated and are in the [0-9] range */
+ assert(off + len <= buflen && "Port number overflow");
+ v = 0;
+ for (p = buf + off; p < end; p++) {
+ v *= 10;
+ v += *p - '0';
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+ }
+
+ u->port = (uint16_t) v;
+ }
+
+ return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+ /* Users should only be pausing/unpausing a parser that is not in an error
+ * state. In non-debug builds, there's not much that we can do about this
+ * other than ignore it.
+ */
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+ HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+ } else {
+ assert(0 && "Attempting to pause parser in error state");
+ }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+ return parser->state == s_message_done;
+}
+
+unsigned long
+http_parser_version(void) {
+ return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
+ HTTP_PARSER_VERSION_MINOR * 0x00100 |
+ HTTP_PARSER_VERSION_PATCH * 0x00001;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp
new file mode 100644
index 0000000..ef34eca
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp
@@ -0,0 +1,111 @@
+# This file is used with the GYP meta build system.
+# http://code.google.com/p/gyp/
+# To build try this:
+# svn co http://gyp.googlecode.com/svn/trunk gyp
+# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp
+# ./out/Debug/test
+{
+ 'target_defaults': {
+ 'default_configuration': 'Debug',
+ 'configurations': {
+ # TODO: hoist these out and put them somewhere common, because
+ # RuntimeLibrary MUST MATCH across the entire project
+ 'Debug': {
+ 'defines': [ 'DEBUG', '_DEBUG' ],
+ 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 1, # static debug
+ },
+ },
+ },
+ 'Release': {
+ 'defines': [ 'NDEBUG' ],
+ 'cflags': [ '-Wall', '-Wextra', '-O3' ],
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': 0, # static release
+ },
+ },
+ }
+ },
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ },
+ 'VCLibrarianTool': {
+ },
+ 'VCLinkerTool': {
+ 'GenerateDebugInformation': 'true',
+ },
+ },
+ 'conditions': [
+ ['OS == "win"', {
+ 'defines': [
+ 'WIN32'
+ ],
+ }]
+ ],
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'http_parser',
+ 'type': 'static_library',
+ 'include_dirs': [ '.' ],
+ 'direct_dependent_settings': {
+ 'defines': [ 'HTTP_PARSER_STRICT=0' ],
+ 'include_dirs': [ '.' ],
+ },
+ 'defines': [ 'HTTP_PARSER_STRICT=0' ],
+ 'sources': [ './http_parser.c', ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # Compile as C++. http_parser.c is actually C99, but C++ is
+ # close enough in this case.
+ 'CompileAs': 2,
+ },
+ },
+ }]
+ ],
+ },
+
+ {
+ 'target_name': 'http_parser_strict',
+ 'type': 'static_library',
+ 'include_dirs': [ '.' ],
+ 'direct_dependent_settings': {
+ 'defines': [ 'HTTP_PARSER_STRICT=1' ],
+ 'include_dirs': [ '.' ],
+ },
+ 'defines': [ 'HTTP_PARSER_STRICT=1' ],
+ 'sources': [ './http_parser.c', ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # Compile as C++. http_parser.c is actually C99, but C++ is
+ # close enough in this case.
+ 'CompileAs': 2,
+ },
+ },
+ }]
+ ],
+ },
+
+ {
+ 'target_name': 'test-nonstrict',
+ 'type': 'executable',
+ 'dependencies': [ 'http_parser' ],
+ 'sources': [ 'test.c' ]
+ },
+
+ {
+ 'target_name': 'test-strict',
+ 'type': 'executable',
+ 'dependencies': [ 'http_parser_strict' ],
+ 'sources': [ 'test.c' ]
+ }
+ ]
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.h b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.h
new file mode 100644
index 0000000..a0de71e
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/http_parser.h
@@ -0,0 +1,433 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Also update SONAME in the Makefile whenever you change these. */
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 8
+#define HTTP_PARSER_VERSION_PATCH 1
+
+#include <stddef.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
+#include <BaseTsd.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#endif
+
+/* Maximium header size allowed. If the macro is not defined
+ * before including this header then the default is used. To
+ * change the maximum header size, define the macro in the build
+ * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
+ * the effective limit on the size of the header, define the macro
+ * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
+ */
+#ifndef HTTP_MAX_HEADER_SIZE
+# define HTTP_MAX_HEADER_SIZE (80*1024)
+#endif
+
+typedef struct http_parser http_parser;
+typedef struct http_parser_settings http_parser_settings;
+
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * Returning `2` from on_headers_complete will tell parser that it should not
+ * expect neither a body nor any futher responses on this connection. This is
+ * useful for handling responses to a CONNECT request which may not contain
+ * `Upgrade` or `Connection: upgrade` headers.
+ *
+ * http_data_cb does not return data chunks. It will be called arbitrarily
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+typedef int (*http_cb) (http_parser*);
+
+
+/* Status Codes */
+#define HTTP_STATUS_MAP(XX) \
+ XX(100, CONTINUE, Continue) \
+ XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
+ XX(102, PROCESSING, Processing) \
+ XX(200, OK, OK) \
+ XX(201, CREATED, Created) \
+ XX(202, ACCEPTED, Accepted) \
+ XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
+ XX(204, NO_CONTENT, No Content) \
+ XX(205, RESET_CONTENT, Reset Content) \
+ XX(206, PARTIAL_CONTENT, Partial Content) \
+ XX(207, MULTI_STATUS, Multi-Status) \
+ XX(208, ALREADY_REPORTED, Already Reported) \
+ XX(226, IM_USED, IM Used) \
+ XX(300, MULTIPLE_CHOICES, Multiple Choices) \
+ XX(301, MOVED_PERMANENTLY, Moved Permanently) \
+ XX(302, FOUND, Found) \
+ XX(303, SEE_OTHER, See Other) \
+ XX(304, NOT_MODIFIED, Not Modified) \
+ XX(305, USE_PROXY, Use Proxy) \
+ XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
+ XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
+ XX(400, BAD_REQUEST, Bad Request) \
+ XX(401, UNAUTHORIZED, Unauthorized) \
+ XX(402, PAYMENT_REQUIRED, Payment Required) \
+ XX(403, FORBIDDEN, Forbidden) \
+ XX(404, NOT_FOUND, Not Found) \
+ XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
+ XX(406, NOT_ACCEPTABLE, Not Acceptable) \
+ XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
+ XX(408, REQUEST_TIMEOUT, Request Timeout) \
+ XX(409, CONFLICT, Conflict) \
+ XX(410, GONE, Gone) \
+ XX(411, LENGTH_REQUIRED, Length Required) \
+ XX(412, PRECONDITION_FAILED, Precondition Failed) \
+ XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
+ XX(414, URI_TOO_LONG, URI Too Long) \
+ XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
+ XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
+ XX(417, EXPECTATION_FAILED, Expectation Failed) \
+ XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
+ XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
+ XX(423, LOCKED, Locked) \
+ XX(424, FAILED_DEPENDENCY, Failed Dependency) \
+ XX(426, UPGRADE_REQUIRED, Upgrade Required) \
+ XX(428, PRECONDITION_REQUIRED, Precondition Required) \
+ XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
+ XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
+ XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
+ XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
+ XX(501, NOT_IMPLEMENTED, Not Implemented) \
+ XX(502, BAD_GATEWAY, Bad Gateway) \
+ XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
+ XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
+ XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
+ XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
+ XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
+ XX(508, LOOP_DETECTED, Loop Detected) \
+ XX(510, NOT_EXTENDED, Not Extended) \
+ XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
+
+enum http_status
+ {
+#define XX(num, name, string) HTTP_STATUS_##name = num,
+ HTTP_STATUS_MAP(XX)
+#undef XX
+ };
+
+
+/* Request Methods */
+#define HTTP_METHOD_MAP(XX) \
+ XX(0, DELETE, DELETE) \
+ XX(1, GET, GET) \
+ XX(2, HEAD, HEAD) \
+ XX(3, POST, POST) \
+ XX(4, PUT, PUT) \
+ /* pathological */ \
+ XX(5, CONNECT, CONNECT) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(7, TRACE, TRACE) \
+ /* WebDAV */ \
+ XX(8, COPY, COPY) \
+ XX(9, LOCK, LOCK) \
+ XX(10, MKCOL, MKCOL) \
+ XX(11, MOVE, MOVE) \
+ XX(12, PROPFIND, PROPFIND) \
+ XX(13, PROPPATCH, PROPPATCH) \
+ XX(14, SEARCH, SEARCH) \
+ XX(15, UNLOCK, UNLOCK) \
+ XX(16, BIND, BIND) \
+ XX(17, REBIND, REBIND) \
+ XX(18, UNBIND, UNBIND) \
+ XX(19, ACL, ACL) \
+ /* subversion */ \
+ XX(20, REPORT, REPORT) \
+ XX(21, MKACTIVITY, MKACTIVITY) \
+ XX(22, CHECKOUT, CHECKOUT) \
+ XX(23, MERGE, MERGE) \
+ /* upnp */ \
+ XX(24, MSEARCH, M-SEARCH) \
+ XX(25, NOTIFY, NOTIFY) \
+ XX(26, SUBSCRIBE, SUBSCRIBE) \
+ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
+ /* RFC-5789 */ \
+ XX(28, PATCH, PATCH) \
+ XX(29, PURGE, PURGE) \
+ /* CalDAV */ \
+ XX(30, MKCALENDAR, MKCALENDAR) \
+ /* RFC-2068, section 19.6.1.2 */ \
+ XX(31, LINK, LINK) \
+ XX(32, UNLINK, UNLINK) \
+ /* icecast */ \
+ XX(33, SOURCE, SOURCE) \
+
+enum http_method
+ {
+#define XX(num, name, string) HTTP_##name = num,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+
+/* Flag values for http_parser.flags field */
+enum flags
+ { F_CHUNKED = 1 << 0
+ , F_CONNECTION_KEEP_ALIVE = 1 << 1
+ , F_CONNECTION_CLOSE = 1 << 2
+ , F_CONNECTION_UPGRADE = 1 << 3
+ , F_TRAILING = 1 << 4
+ , F_UPGRADE = 1 << 5
+ , F_SKIPBODY = 1 << 6
+ , F_CONTENTLENGTH = 1 << 7
+ };
+
+
+/* Map for errno-related constants
+ *
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define HTTP_ERRNO_MAP(XX) \
+ /* No error */ \
+ XX(OK, "success") \
+ \
+ /* Callback-related errors */ \
+ XX(CB_message_begin, "the on_message_begin callback failed") \
+ XX(CB_url, "the on_url callback failed") \
+ XX(CB_header_field, "the on_header_field callback failed") \
+ XX(CB_header_value, "the on_header_value callback failed") \
+ XX(CB_headers_complete, "the on_headers_complete callback failed") \
+ XX(CB_body, "the on_body callback failed") \
+ XX(CB_message_complete, "the on_message_complete callback failed") \
+ XX(CB_status, "the on_status callback failed") \
+ XX(CB_chunk_header, "the on_chunk_header callback failed") \
+ XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
+ \
+ /* Parsing-related errors */ \
+ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
+ XX(HEADER_OVERFLOW, \
+ "too many header bytes seen; overflow detected") \
+ XX(CLOSED_CONNECTION, \
+ "data received after completed connection: close message") \
+ XX(INVALID_VERSION, "invalid HTTP version") \
+ XX(INVALID_STATUS, "invalid HTTP status code") \
+ XX(INVALID_METHOD, "invalid HTTP method") \
+ XX(INVALID_URL, "invalid URL") \
+ XX(INVALID_HOST, "invalid host") \
+ XX(INVALID_PORT, "invalid port") \
+ XX(INVALID_PATH, "invalid path") \
+ XX(INVALID_QUERY_STRING, "invalid query string") \
+ XX(INVALID_FRAGMENT, "invalid fragment") \
+ XX(LF_EXPECTED, "LF character expected") \
+ XX(INVALID_HEADER_TOKEN, "invalid character in header") \
+ XX(INVALID_CONTENT_LENGTH, \
+ "invalid character in content-length header") \
+ XX(UNEXPECTED_CONTENT_LENGTH, \
+ "unexpected content-length header") \
+ XX(INVALID_CHUNK_SIZE, \
+ "invalid character in chunk size header") \
+ XX(INVALID_CONSTANT, "invalid constant string") \
+ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
+ XX(STRICT, "strict mode assertion failed") \
+ XX(PAUSED, "parser is paused") \
+ XX(UNKNOWN, "an unknown error occurred")
+
+
+/* Define HPE_* values for each errno value above */
+#define HTTP_ERRNO_GEN(n, s) HPE_##n,
+enum http_errno {
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+};
+#undef HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
+
+
+struct http_parser {
+ /** PRIVATE **/
+ unsigned int type : 2; /* enum http_parser_type */
+ unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
+ unsigned int state : 7; /* enum state from http_parser.c */
+ unsigned int header_state : 7; /* enum header_state from http_parser.c */
+ unsigned int index : 7; /* index into current matcher */
+ unsigned int lenient_http_headers : 1;
+
+ uint32_t nread; /* # bytes read in various scenarios */
+ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
+
+ /** READ-ONLY **/
+ unsigned short http_major;
+ unsigned short http_minor;
+ unsigned int status_code : 16; /* responses only */
+ unsigned int method : 8; /* requests only */
+ unsigned int http_errno : 7;
+
+ /* 1 = Upgrade header was present and the parser has exited because of that.
+ * 0 = No upgrade header present.
+ * Should be checked when http_parser_execute() returns in addition to
+ * error checking.
+ */
+ unsigned int upgrade : 1;
+
+ /** PUBLIC **/
+ void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+ http_cb on_message_begin;
+ http_data_cb on_url;
+ http_data_cb on_status;
+ http_data_cb on_header_field;
+ http_data_cb on_header_value;
+ http_cb on_headers_complete;
+ http_data_cb on_body;
+ http_cb on_message_complete;
+ /* When on_chunk_header is called, the current chunk length is stored
+ * in parser->content_length.
+ */
+ http_cb on_chunk_header;
+ http_cb on_chunk_complete;
+};
+
+
+enum http_parser_url_fields
+ { UF_SCHEMA = 0
+ , UF_HOST = 1
+ , UF_PORT = 2
+ , UF_PATH = 3
+ , UF_QUERY = 4
+ , UF_FRAGMENT = 5
+ , UF_USERINFO = 6
+ , UF_MAX = 7
+ };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+ uint16_t field_set; /* Bitmask of (1 << UF_*) values */
+ uint16_t port; /* Converted UF_PORT string */
+
+ struct {
+ uint16_t off; /* Offset into buffer in which field starts */
+ uint16_t len; /* Length of run in buffer */
+ } field_data[UF_MAX];
+};
+
+
+/* Returns the library version. Bits 16-23 contain the major version number,
+ * bits 8-15 the minor version number and bits 0-7 the patch level.
+ * Usage example:
+ *
+ * unsigned long version = http_parser_version();
+ * unsigned major = (version >> 16) & 255;
+ * unsigned minor = (version >> 8) & 255;
+ * unsigned patch = version & 255;
+ * printf("http_parser v%u.%u.%u\n", major, minor, patch);
+ */
+unsigned long http_parser_version(void);
+
+void http_parser_init(http_parser *parser, enum http_parser_type type);
+
+
+/* Initialize http_parser_settings members to 0
+ */
+void http_parser_settings_init(http_parser_settings *settings);
+
+
+/* Executes the parser. Returns number of parsed bytes. Sets
+ * `parser->http_errno` on error. */
+size_t http_parser_execute(http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns 0, then this should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+int http_should_keep_alive(const http_parser *parser);
+
+/* Returns a string version of the HTTP method. */
+const char *http_method_str(enum http_method m);
+
+/* Return a string name of the given error */
+const char *http_errno_name(enum http_errno err);
+
+/* Return a string description of the given error */
+const char *http_errno_description(enum http_errno err);
+
+/* Initialize all http_parser_url members to 0 */
+void http_parser_url_init(struct http_parser_url *u);
+
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, size_t buflen,
+ int is_connect,
+ struct http_parser_url *u);
+
+/* Pause or un-pause the parser; a nonzero value pauses */
+void http_parser_pause(http_parser *parser, int paused);
+
+/* Checks if this is the final chunk of the body. */
+int http_body_is_final(const http_parser *parser);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/test.c b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/test.c
new file mode 100644
index 0000000..df170e4
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/ext/ruby_http_parser/vendor/http-parser/test.c
@@ -0,0 +1,4456 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h> /* rand */
+#include <string.h>
+#include <stdarg.h>
+
+#if defined(__APPLE__)
+# undef strlncpy
+#endif /* defined(__APPLE__) */
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+
+#define MAX_HEADERS 13
+#define MAX_ELEMENT_SIZE 2048
+#define MAX_CHUNKS 16
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
+
+static http_parser *parser;
+
+struct message {
+ const char *name; // for debugging purposes
+ const char *raw;
+ enum http_parser_type type;
+ enum http_method method;
+ int status_code;
+ char response_status[MAX_ELEMENT_SIZE];
+ char request_path[MAX_ELEMENT_SIZE];
+ char request_url[MAX_ELEMENT_SIZE];
+ char fragment[MAX_ELEMENT_SIZE];
+ char query_string[MAX_ELEMENT_SIZE];
+ char body[MAX_ELEMENT_SIZE];
+ size_t body_size;
+ const char *host;
+ const char *userinfo;
+ uint16_t port;
+ int num_headers;
+ enum { NONE=0, FIELD, VALUE } last_header_element;
+ char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
+ int should_keep_alive;
+
+ int num_chunks;
+ int num_chunks_complete;
+ int chunk_lengths[MAX_CHUNKS];
+
+ const char *upgrade; // upgraded body
+
+ unsigned short http_major;
+ unsigned short http_minor;
+
+ int message_begin_cb_called;
+ int headers_complete_cb_called;
+ int message_complete_cb_called;
+ int status_cb_called;
+ int message_complete_on_eof;
+ int body_is_final;
+};
+
+static int currently_parsing_eof;
+
+static struct message messages[5];
+static int num_messages;
+static http_parser_settings *current_pause_parser;
+
+/* * R E Q U E S T S * */
+const struct message requests[] =
+#define CURL_GET 0
+{ {.name= "curl get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test HTTP/1.1\r\n"
+ "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
+ "Host: 0.0.0.0=5000\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 3
+ ,.headers=
+ { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
+ , { "Host", "0.0.0.0=5000" }
+ , { "Accept", "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define FIREFOX_GET 1
+, {.name= "firefox get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /favicon.ico HTTP/1.1\r\n"
+ "Host: 0.0.0.0=5000\r\n"
+ "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+ "Accept-Language: en-us,en;q=0.5\r\n"
+ "Accept-Encoding: gzip,deflate\r\n"
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
+ "Keep-Alive: 300\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/favicon.ico"
+ ,.request_url= "/favicon.ico"
+ ,.num_headers= 8
+ ,.headers=
+ { { "Host", "0.0.0.0=5000" }
+ , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
+ , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
+ , { "Accept-Language", "en-us,en;q=0.5" }
+ , { "Accept-Encoding", "gzip,deflate" }
+ , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
+ , { "Keep-Alive", "300" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= ""
+ }
+
+#define DUMBFUCK 2
+, {.name= "dumbfuck"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
+ "aaaaaaaaaaaaa:++++++++++\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/dumbfuck"
+ ,.request_url= "/dumbfuck"
+ ,.num_headers= 1
+ ,.headers=
+ { { "aaaaaaaaaaaaa", "++++++++++" }
+ }
+ ,.body= ""
+ }
+
+#define FRAGMENT_IN_URI 3
+, {.name= "fragment in url"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "page=1"
+ ,.fragment= "posts-17408"
+ ,.request_path= "/forums/1/topics/2375"
+ /* XXX request url does include fragment? */
+ ,.request_url= "/forums/1/topics/2375?page=1#posts-17408"
+ ,.num_headers= 0
+ ,.body= ""
+ }
+
+#define GET_NO_HEADERS_NO_BODY 4
+, {.name= "get no headers no body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_no_headers_no_body/world"
+ ,.request_url= "/get_no_headers_no_body/world"
+ ,.num_headers= 0
+ ,.body= ""
+ }
+
+#define GET_ONE_HEADER_NO_BODY 5
+, {.name= "get one header no body"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE /* would need Connection: close */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_one_header_no_body"
+ ,.request_url= "/get_one_header_no_body"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Accept" , "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define GET_FUNKY_CONTENT_LENGTH 6
+, {.name= "get funky content length body hello"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
+ "conTENT-Length: 5\r\n"
+ "\r\n"
+ "HELLO"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/get_funky_content_length_body_hello"
+ ,.request_url= "/get_funky_content_length_body_hello"
+ ,.num_headers= 1
+ ,.headers=
+ { { "conTENT-Length" , "5" }
+ }
+ ,.body= "HELLO"
+ }
+
+#define POST_IDENTITY_BODY_WORLD 7
+, {.name= "post identity body world"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "Transfer-Encoding: identity\r\n"
+ "Content-Length: 5\r\n"
+ "\r\n"
+ "World"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= "q=search"
+ ,.fragment= "hey"
+ ,.request_path= "/post_identity_body_world"
+ ,.request_url= "/post_identity_body_world?q=search#hey"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Accept", "*/*" }
+ , { "Transfer-Encoding", "identity" }
+ , { "Content-Length", "5" }
+ }
+ ,.body= "World"
+ }
+
+#define POST_CHUNKED_ALL_YOUR_BASE 8
+, {.name= "post - chunked body: all your base are belong to us"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "1e\r\nall your base are belong to us\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/post_chunked_all_your_base"
+ ,.request_url= "/post_chunked_all_your_base"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding" , "chunked" }
+ }
+ ,.body= "all your base are belong to us"
+ ,.num_chunks_complete= 2
+ ,.chunk_lengths= { 0x1e }
+ }
+
+#define TWO_CHUNKS_MULT_ZERO_END 9
+, {.name= "two chunks ; triple zero ending"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5\r\nhello\r\n"
+ "6\r\n world\r\n"
+ "000\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/two_chunks_mult_zero_end"
+ ,.request_url= "/two_chunks_mult_zero_end"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "hello world"
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 5, 6 }
+ }
+
+#define CHUNKED_W_TRAILING_HEADERS 10
+, {.name= "chunked with trailing headers. blech."
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5\r\nhello\r\n"
+ "6\r\n world\r\n"
+ "0\r\n"
+ "Vary: *\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/chunked_w_trailing_headers"
+ ,.request_url= "/chunked_w_trailing_headers"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ , { "Vary", "*" }
+ , { "Content-Type", "text/plain" }
+ }
+ ,.body= "hello world"
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 5, 6 }
+ }
+
+#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
+, {.name= "with bullshit after the length"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
+ "6; blahblah; blah\r\n world\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/chunked_w_bullshit_after_length"
+ ,.request_url= "/chunked_w_bullshit_after_length"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "hello world"
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 5, 6 }
+ }
+
+#define WITH_QUOTES 12
+, {.name= "with quotes"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "foo=\"bar\""
+ ,.fragment= ""
+ ,.request_path= "/with_\"stupid\"_quotes"
+ ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define APACHEBENCH_GET 13
+/* The server receiving this request SHOULD NOT wait for EOF
+ * to know that content-length == 0.
+ * How to represent this in a unit test? message_complete_on_eof
+ * Compare with NO_CONTENT_LENGTH_RESPONSE.
+ */
+, {.name = "apachebench get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test HTTP/1.0\r\n"
+ "Host: 0.0.0.0:5000\r\n"
+ "User-Agent: ApacheBench/2.3\r\n"
+ "Accept: */*\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 3
+ ,.headers= { { "Host", "0.0.0.0:5000" }
+ , { "User-Agent", "ApacheBench/2.3" }
+ , { "Accept", "*/*" }
+ }
+ ,.body= ""
+ }
+
+#define QUERY_URL_WITH_QUESTION_MARK_GET 14
+/* Some clients include '?' characters in query strings.
+ */
+, {.name = "query url with question mark"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "foo=bar?baz"
+ ,.fragment= ""
+ ,.request_path= "/test.cgi"
+ ,.request_url= "/test.cgi?foo=bar?baz"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define PREFIX_NEWLINE_GET 15
+/* Some clients, especially after a POST in a keep-alive connection,
+ * will send an extra CRLF before the next request
+ */
+, {.name = "newline prefix get"
+ ,.type= HTTP_REQUEST
+ ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define UPGRADE_REQUEST 16
+, {.name = "upgrade request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 7
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Host", "example.com" }
+ , { "Connection", "Upgrade" }
+ , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" }
+ , { "Sec-WebSocket-Protocol", "sample" }
+ , { "Upgrade", "WebSocket" }
+ , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" }
+ , { "Origin", "http://example.com" }
+ }
+ ,.body= ""
+ }
+
+#define CONNECT_REQUEST 17
+, {.name = "connect request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ "some data\r\n"
+ "and yet even more data"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "0-home0.netscape.com:443"
+ ,.num_headers= 2
+ ,.upgrade="some data\r\nand yet even more data"
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+
+#define REPORT_REQ 18
+, {.name= "report request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "REPORT /test HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_REPORT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/test"
+ ,.request_url= "/test"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define NO_HTTP_VERSION 19
+, {.name= "request with no http version"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 0
+ ,.http_minor= 9
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define MSEARCH_REQ 20
+, {.name= "m-search request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: \"ssdp:all\"\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_MSEARCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "*"
+ ,.request_url= "*"
+ ,.num_headers= 3
+ ,.headers= { { "HOST", "239.255.255.250:1900" }
+ , { "MAN", "\"ssdp:discover\"" }
+ , { "ST", "\"ssdp:all\"" }
+ }
+ ,.body= ""
+ }
+
+#define LINE_FOLDING_IN_HEADER 21
+, {.name= "line folding in header value"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET / HTTP/1.1\r\n"
+ "Line1: abc\r\n"
+ "\tdef\r\n"
+ " ghi\r\n"
+ "\t\tjkl\r\n"
+ " mno \r\n"
+ "\t \tqrs\r\n"
+ "Line2: \t line2\t\r\n"
+ "Line3:\r\n"
+ " line3\r\n"
+ "Line4: \r\n"
+ " \r\n"
+ "Connection:\r\n"
+ " close\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 5
+ ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" }
+ , { "Line2", "line2\t" }
+ , { "Line3", "line3" }
+ , { "Line4", "" }
+ , { "Connection", "close" },
+ }
+ ,.body= ""
+ }
+
+
+#define QUERY_TERMINATED_HOST 22
+, {.name= "host terminated by a query string"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "hail=all"
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org?hail=all"
+ ,.host= "hypnotoad.org"
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define QUERY_TERMINATED_HOSTPORT 23
+, {.name= "host:port terminated by a query string"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "hail=all"
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org:1234?hail=all"
+ ,.host= "hypnotoad.org"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define SPACE_TERMINATED_HOSTPORT 24
+, {.name= "host:port terminated by a space"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "http://hypnotoad.org:1234"
+ ,.host= "hypnotoad.org"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define PATCH_REQ 25
+, {.name = "PATCH request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "PATCH /file.txt HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/example\r\n"
+ "If-Match: \"e0023aa4e\"\r\n"
+ "Content-Length: 10\r\n"
+ "\r\n"
+ "cccccccccc"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_PATCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/file.txt"
+ ,.request_url= "/file.txt"
+ ,.num_headers= 4
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/example" }
+ , { "If-Match", "\"e0023aa4e\"" }
+ , { "Content-Length", "10" }
+ }
+ ,.body= "cccccccccc"
+ }
+
+#define CONNECT_CAPS_REQUEST 26
+, {.name = "connect caps request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "HOME0.NETSCAPE.COM:443"
+ ,.num_headers= 2
+ ,.upgrade=""
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+
+#if !HTTP_PARSER_STRICT
+#define UTF8_PATH_REQ 27
+, {.name= "utf-8 path request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n"
+ "Host: github.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= "q=1"
+ ,.fragment= "narf"
+ ,.request_path= "/δ¶/δt/pope"
+ ,.request_url= "/δ¶/δt/pope?q=1#narf"
+ ,.num_headers= 1
+ ,.headers= { {"Host", "github.com" }
+ }
+ ,.body= ""
+ }
+
+#define HOSTNAME_UNDERSCORE 28
+, {.name = "hostname underscore"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= ""
+ ,.request_url= "home_0.netscape.com:443"
+ ,.num_headers= 2
+ ,.upgrade=""
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ }
+ ,.body= ""
+ }
+#endif /* !HTTP_PARSER_STRICT */
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29
+, {.name = "eat CRLF between requests, no \"Connection: close\" header"
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 4\r\n"
+ "\r\n"
+ "q=42\r\n" /* note the trailing CRLF */
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 3
+ ,.upgrade= 0
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/x-www-form-urlencoded" }
+ , { "Content-Length", "4" }
+ }
+ ,.body= "q=42"
+ }
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30
+, {.name = "eat CRLF between requests even if \"Connection: close\" is set"
+ ,.raw= "POST / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: 4\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "q=42\r\n" /* note the trailing CRLF */
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 4
+ ,.upgrade= 0
+ ,.headers= { { "Host", "www.example.com" }
+ , { "Content-Type", "application/x-www-form-urlencoded" }
+ , { "Content-Length", "4" }
+ , { "Connection", "close" }
+ }
+ ,.body= "q=42"
+ }
+
+#define PURGE_REQ 31
+, {.name = "PURGE request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "PURGE /file.txt HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_PURGE
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/file.txt"
+ ,.request_url= "/file.txt"
+ ,.num_headers= 1
+ ,.headers= { { "Host", "www.example.com" } }
+ ,.body= ""
+ }
+
+#define SEARCH_REQ 32
+, {.name = "SEARCH request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "SEARCH / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_SEARCH
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 1
+ ,.headers= { { "Host", "www.example.com" } }
+ ,.body= ""
+ }
+
+#define PROXY_WITH_BASIC_AUTH 33
+, {.name= "host:port and basic_auth"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.fragment= ""
+ ,.request_path= "/toto"
+ ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto"
+ ,.host= "hypnotoad.org"
+ ,.userinfo= "a%12:b!&*$"
+ ,.port= 1234
+ ,.num_headers= 0
+ ,.headers= { }
+ ,.body= ""
+ }
+
+#define LINE_FOLDING_IN_HEADER_WITH_LF 34
+, {.name= "line folding in header value"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET / HTTP/1.1\n"
+ "Line1: abc\n"
+ "\tdef\n"
+ " ghi\n"
+ "\t\tjkl\n"
+ " mno \n"
+ "\t \tqrs\n"
+ "Line2: \t line2\t\n"
+ "Line3:\n"
+ " line3\n"
+ "Line4: \n"
+ " \n"
+ "Connection:\n"
+ " close\n"
+ "\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/"
+ ,.request_url= "/"
+ ,.num_headers= 5
+ ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" }
+ , { "Line2", "line2\t" }
+ , { "Line3", "line3" }
+ , { "Line4", "" }
+ , { "Connection", "close" },
+ }
+ ,.body= ""
+ }
+
+#define CONNECTION_MULTI 35
+, {.name = "multiple connection header values with folding"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Something,\r\n"
+ " Upgrade, ,Keep-Alive\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 7
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Host", "example.com" }
+ , { "Connection", "Something, Upgrade, ,Keep-Alive" }
+ , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" }
+ , { "Sec-WebSocket-Protocol", "sample" }
+ , { "Upgrade", "WebSocket" }
+ , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" }
+ , { "Origin", "http://example.com" }
+ }
+ ,.body= ""
+ }
+
+#define CONNECTION_MULTI_LWS 36
+, {.name = "multiple connection header values with folding and lws"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /demo HTTP/1.1\r\n"
+ "Connection: keep-alive, upgrade\r\n"
+ "Upgrade: WebSocket\r\n"
+ "\r\n"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 2
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Connection", "keep-alive, upgrade" }
+ , { "Upgrade", "WebSocket" }
+ }
+ ,.body= ""
+ }
+
+#define CONNECTION_MULTI_LWS_CRLF 37
+, {.name = "multiple connection header values with folding and lws"
+ ,.type= HTTP_REQUEST
+ ,.raw= "GET /demo HTTP/1.1\r\n"
+ "Connection: keep-alive, \r\n upgrade\r\n"
+ "Upgrade: WebSocket\r\n"
+ "\r\n"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_GET
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 2
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Connection", "keep-alive, upgrade" }
+ , { "Upgrade", "WebSocket" }
+ }
+ ,.body= ""
+ }
+
+#define UPGRADE_POST_REQUEST 38
+, {.name = "upgrade post request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "POST /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Upgrade: HTTP/2.0\r\n"
+ "Content-Length: 15\r\n"
+ "\r\n"
+ "sweet post body"
+ "Hot diggity dogg"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_POST
+ ,.request_path= "/demo"
+ ,.request_url= "/demo"
+ ,.num_headers= 4
+ ,.upgrade="Hot diggity dogg"
+ ,.headers= { { "Host", "example.com" }
+ , { "Connection", "Upgrade" }
+ , { "Upgrade", "HTTP/2.0" }
+ , { "Content-Length", "15" }
+ }
+ ,.body= "sweet post body"
+ }
+
+#define CONNECT_WITH_BODY_REQUEST 39
+, {.name = "connect with body request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n"
+ "User-agent: Mozilla/1.1N\r\n"
+ "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+ "Content-Length: 10\r\n"
+ "\r\n"
+ "blarfcicle"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.method= HTTP_CONNECT
+ ,.request_url= "foo.bar.com:443"
+ ,.num_headers= 3
+ ,.upgrade="blarfcicle"
+ ,.headers= { { "User-agent", "Mozilla/1.1N" }
+ , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+ , { "Content-Length", "10" }
+ }
+ ,.body= ""
+ }
+
+/* Examples from the Internet draft for LINK/UNLINK methods:
+ * https://tools.ietf.org/id/draft-snell-link-method-01.html#rfc.section.5
+ */
+
+#define LINK_REQUEST 40
+, {.name = "link request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Link: <http://example.com/profiles/joe>; rel=\"tag\"\r\n"
+ "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_LINK
+ ,.request_path= "/images/my_dog.jpg"
+ ,.request_url= "/images/my_dog.jpg"
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.num_headers= 3
+ ,.headers= { { "Host", "example.com" }
+ , { "Link", "<http://example.com/profiles/joe>; rel=\"tag\"" }
+ , { "Link", "<http://example.com/profiles/sally>; rel=\"tag\"" }
+ }
+ ,.body= ""
+ }
+
+#define UNLINK_REQUEST 41
+, {.name = "unlink request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_UNLINK
+ ,.request_path= "/images/my_dog.jpg"
+ ,.request_url= "/images/my_dog.jpg"
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.num_headers= 2
+ ,.headers= { { "Host", "example.com" }
+ , { "Link", "<http://example.com/profiles/sally>; rel=\"tag\"" }
+ }
+ ,.body= ""
+ }
+
+#define SOURCE_REQUEST 42
+, {.name = "source request"
+ ,.type= HTTP_REQUEST
+ ,.raw= "SOURCE /music/sweet/music HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.method= HTTP_SOURCE
+ ,.request_path= "/music/sweet/music"
+ ,.request_url= "/music/sweet/music"
+ ,.query_string= ""
+ ,.fragment= ""
+ ,.num_headers= 1
+ ,.headers= { { "Host", "example.com" } }
+ ,.body= ""
+ }
+};
+
+/* * R E S P O N S E S * */
+const struct message responses[] =
+#define GOOGLE_301 0
+{ {.name= "google 301"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: http://www.google.com/\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
+ "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
+ "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */
+ "Cache-Control: public, max-age=2592000\r\n"
+ "Server: gws\r\n"
+ "Content-Length: 219 \r\n"
+ "\r\n"
+ "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+ "<H1>301 Moved</H1>\n"
+ "The document has moved\n"
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+ "</BODY></HTML>\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 301
+ ,.response_status= "Moved Permanently"
+ ,.num_headers= 8
+ ,.headers=
+ { { "Location", "http://www.google.com/" }
+ , { "Content-Type", "text/html; charset=UTF-8" }
+ , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
+ , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
+ , { "X-$PrototypeBI-Version", "1.6.0.3" }
+ , { "Cache-Control", "public, max-age=2592000" }
+ , { "Server", "gws" }
+ , { "Content-Length", "219 " }
+ }
+ ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+ "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+ "<H1>301 Moved</H1>\n"
+ "The document has moved\n"
+ "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+ "</BODY></HTML>\r\n"
+ }
+
+#define NO_CONTENT_LENGTH_RESPONSE 1
+/* The client should wait for the server's EOF. That is, when content-length
+ * is not specified, and "Connection: close", the end of body is specified
+ * by the EOF.
+ * Compare with APACHEBENCH_GET
+ */
+, {.name= "no content-length response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
+ "Server: Apache\r\n"
+ "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+ " <SOAP-ENV:Body>\n"
+ " <SOAP-ENV:Fault>\n"
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
+ " <faultstring>Client Error</faultstring>\n"
+ " </SOAP-ENV:Fault>\n"
+ " </SOAP-ENV:Body>\n"
+ "</SOAP-ENV:Envelope>"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 5
+ ,.headers=
+ { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
+ , { "Server", "Apache" }
+ , { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
+ , { "Content-Type", "text/xml; charset=utf-8" }
+ , { "Connection", "close" }
+ }
+ ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+ " <SOAP-ENV:Body>\n"
+ " <SOAP-ENV:Fault>\n"
+ " <faultcode>SOAP-ENV:Client</faultcode>\n"
+ " <faultstring>Client Error</faultstring>\n"
+ " </SOAP-ENV:Fault>\n"
+ " </SOAP-ENV:Body>\n"
+ "</SOAP-ENV:Envelope>"
+ }
+
+#define NO_HEADERS_NO_BODY_404 2
+, {.name= "404 no headers no body"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 404
+ ,.response_status= "Not Found"
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_REASON_PHRASE 3
+, {.name= "301 no response phrase"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 301\r\n\r\n"
+ ,.should_keep_alive = FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 301
+ ,.response_status= ""
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define TRAILING_SPACE_ON_CHUNKED_BODY 4
+, {.name="200 trailing space on chunked body"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "25 \r\n"
+ "This is the data in the first chunk\r\n"
+ "\r\n"
+ "1C\r\n"
+ "and this is the second one\r\n"
+ "\r\n"
+ "0 \r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 2
+ ,.headers=
+ { {"Content-Type", "text/plain" }
+ , {"Transfer-Encoding", "chunked" }
+ }
+ ,.body_size = 37+28
+ ,.body =
+ "This is the data in the first chunk\r\n"
+ "and this is the second one\r\n"
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 0x25, 0x1c }
+ }
+
+#define NO_CARRIAGE_RET 5
+, {.name="no carriage ret"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\n"
+ "Content-Type: text/html; charset=utf-8\n"
+ "Connection: close\n"
+ "\n"
+ "these headers are from http://news.ycombinator.com/"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 2
+ ,.headers=
+ { {"Content-Type", "text/html; charset=utf-8" }
+ , {"Connection", "close" }
+ }
+ ,.body= "these headers are from http://news.ycombinator.com/"
+ }
+
+#define PROXY_CONNECTION 6
+, {.name="proxy connection"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Content-Length: 11\r\n"
+ "Proxy-Connection: close\r\n"
+ "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n"
+ "\r\n"
+ "hello world"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 4
+ ,.headers=
+ { {"Content-Type", "text/html; charset=UTF-8" }
+ , {"Content-Length", "11" }
+ , {"Proxy-Connection", "close" }
+ , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"}
+ }
+ ,.body= "hello world"
+ }
+
+#define UNDERSTORE_HEADER_KEY 7
+ // shown by
+ // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;"
+, {.name="underscore header key"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Server: DCLK-AdSvr\r\n"
+ "Content-Type: text/xml\r\n"
+ "Content-Length: 0\r\n"
+ "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 4
+ ,.headers=
+ { {"Server", "DCLK-AdSvr" }
+ , {"Content-Type", "text/xml" }
+ , {"Content-Length", "0" }
+ , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" }
+ }
+ ,.body= ""
+ }
+
+#define BONJOUR_MADAME_FR 8
+/* The client should not merge two headers fields when the first one doesn't
+ * have a value.
+ */
+, {.name= "bonjourmadame.fr"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 301 Moved Permanently\r\n"
+ "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n"
+ "Server: Apache/2.2.3 (Red Hat)\r\n"
+ "Cache-Control: public\r\n"
+ "Pragma: \r\n"
+ "Location: http://www.bonjourmadame.fr/\r\n"
+ "Vary: Accept-Encoding\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 301
+ ,.response_status= "Moved Permanently"
+ ,.num_headers= 9
+ ,.headers=
+ { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" }
+ , { "Server", "Apache/2.2.3 (Red Hat)" }
+ , { "Cache-Control", "public" }
+ , { "Pragma", "" }
+ , { "Location", "http://www.bonjourmadame.fr/" }
+ , { "Vary", "Accept-Encoding" }
+ , { "Content-Length", "0" }
+ , { "Content-Type", "text/html; charset=UTF-8" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= ""
+ }
+
+#define RES_FIELD_UNDERSCORE 9
+/* Should handle spaces in header fields */
+, {.name= "field underscore"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n"
+ "Server: Apache\r\n"
+ "Cache-Control: no-cache, must-revalidate\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n"
+ "Vary: Accept-Encoding\r\n"
+ "_eep-Alive: timeout=45\r\n" /* semantic value ignored */
+ "_onnection: Keep-Alive\r\n" /* semantic value ignored */
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Type: text/html\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "0\r\n\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 11
+ ,.headers=
+ { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" }
+ , { "Server", "Apache" }
+ , { "Cache-Control", "no-cache, must-revalidate" }
+ , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" }
+ , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" }
+ , { "Vary", "Accept-Encoding" }
+ , { "_eep-Alive", "timeout=45" }
+ , { "_onnection", "Keep-Alive" }
+ , { "Transfer-Encoding", "chunked" }
+ , { "Content-Type", "text/html" }
+ , { "Connection", "close" }
+ }
+ ,.body= ""
+ ,.num_chunks_complete= 1
+ ,.chunk_lengths= {}
+ }
+
+#define NON_ASCII_IN_STATUS_LINE 10
+/* Should handle non-ASCII in status line */
+, {.name= "non-ASCII in status line"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n"
+ "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n"
+ "Content-Length: 0\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 500
+ ,.response_status= "Oriëntatieprobleem"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" }
+ , { "Content-Length", "0" }
+ , { "Connection", "close" }
+ }
+ ,.body= ""
+ }
+
+#define HTTP_VERSION_0_9 11
+/* Should handle HTTP/0.9 */
+, {.name= "http version 0.9"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/0.9 200 OK\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 0
+ ,.http_minor= 9
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 0
+ ,.headers=
+ {}
+ ,.body= ""
+ }
+
+#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12
+/* The client should wait for the server's EOF. That is, when neither
+ * content-length nor transfer-encoding is specified, the end of body
+ * is specified by the EOF.
+ */
+, {.name= "neither content-length nor transfer-encoding response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "hello world"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Content-Type", "text/plain" }
+ }
+ ,.body= "hello world"
+ }
+
+#define NO_BODY_HTTP10_KA_200 13
+, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 200 OK\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "keep-alive" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP10_KA_204 14
+, {.name= "HTTP/1.0 with keep-alive and a 204 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.0 204 No content\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 204
+ ,.response_status= "No content"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "keep-alive" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_200 15
+, {.name= "HTTP/1.1 with an EOF-terminated 200 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 0
+ ,.headers={}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_204 16
+, {.name= "HTTP/1.1 with a 204 status"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 204 No content\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 204
+ ,.response_status= "No content"
+ ,.num_headers= 0
+ ,.headers={}
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_NOKA_204 17
+, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 204 No content\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 204
+ ,.response_status= "No content"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Connection", "close" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ }
+
+#define NO_BODY_HTTP11_KA_CHUNKED_200 18
+, {.name= "HTTP/1.1 with chunked endocing and a 200 response"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 1
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ }
+ ,.body_size= 0
+ ,.body= ""
+ ,.num_chunks_complete= 1
+ }
+
+#if !HTTP_PARSER_STRICT
+#define SPACE_IN_FIELD_RES 19
+/* Should handle spaces in header fields */
+, {.name= "field space"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Server: Microsoft-IIS/6.0\r\n"
+ "X-Powered-By: ASP.NET\r\n"
+ "en-US Content-Type: text/xml\r\n" /* this is the problem */
+ "Content-Type: text/xml\r\n"
+ "Content-Length: 16\r\n"
+ "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"
+ "<xml>hello</xml>" /* fake body */
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 7
+ ,.headers=
+ { { "Server", "Microsoft-IIS/6.0" }
+ , { "X-Powered-By", "ASP.NET" }
+ , { "en-US Content-Type", "text/xml" }
+ , { "Content-Type", "text/xml" }
+ , { "Content-Length", "16" }
+ , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" }
+ , { "Connection", "keep-alive" }
+ }
+ ,.body= "<xml>hello</xml>"
+ }
+#endif /* !HTTP_PARSER_STRICT */
+
+#define AMAZON_COM 20
+, {.name= "amazon.com"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 301 MovedPermanently\r\n"
+ "Date: Wed, 15 May 2013 17:06:33 GMT\r\n"
+ "Server: Server\r\n"
+ "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n"
+ "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n"
+ "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n"
+ "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n"
+ "Vary: Accept-Encoding,User-Agent\r\n"
+ "Content-Type: text/html; charset=ISO-8859-1\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "1\r\n"
+ "\n\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 301
+ ,.response_status= "MovedPermanently"
+ ,.num_headers= 9
+ ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" }
+ , { "Server", "Server" }
+ , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" }
+ , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" }
+ , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" }
+ , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" }
+ , { "Vary", "Accept-Encoding,User-Agent" }
+ , { "Content-Type", "text/html; charset=ISO-8859-1" }
+ , { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "\n"
+ ,.num_chunks_complete= 2
+ ,.chunk_lengths= { 1 }
+ }
+
+#define EMPTY_REASON_PHRASE_AFTER_SPACE 20
+, {.name= "empty reason phrase after space"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 \r\n"
+ "\r\n"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= ""
+ ,.num_headers= 0
+ ,.headers= {}
+ ,.body= ""
+ }
+
+#define CONTENT_LENGTH_X 21
+, {.name= "Content-Length-X"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Content-Length-X: 0\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "2\r\n"
+ "OK\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 2
+ ,.headers= { { "Content-Length-X", "0" }
+ , { "Transfer-Encoding", "chunked" }
+ }
+ ,.body= "OK"
+ ,.num_chunks_complete= 2
+ ,.chunk_lengths= { 2 }
+ }
+
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER 22
+, {.name= "HTTP 101 response with Upgrade header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "\r\n"
+ "proto"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 101
+ ,.response_status= "Switching Protocols"
+ ,.upgrade= "proto"
+ ,.num_headers= 2
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ }
+ }
+
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 23
+, {.name= "HTTP 101 response with Upgrade and Content-Length header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "Content-Length: 4\r\n"
+ "\r\n"
+ "body"
+ "proto"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 101
+ ,.response_status= "Switching Protocols"
+ ,.body= "body"
+ ,.upgrade= "proto"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ , { "Content-Length", "4" }
+ }
+ }
+
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 24
+, {.name= "HTTP 101 response with Upgrade and Transfer-Encoding header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "2\r\n"
+ "bo\r\n"
+ "2\r\n"
+ "dy\r\n"
+ "0\r\n"
+ "\r\n"
+ "proto"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 101
+ ,.response_status= "Switching Protocols"
+ ,.body= "body"
+ ,.upgrade= "proto"
+ ,.num_headers= 3
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ , { "Transfer-Encoding", "chunked" }
+ }
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 2, 2 }
+ }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER 25
+, {.name= "HTTP 200 response with Upgrade header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "\r\n"
+ "body"
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= TRUE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.body= "body"
+ ,.upgrade= NULL
+ ,.num_headers= 2
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ }
+ }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 26
+, {.name= "HTTP 200 response with Upgrade and Content-Length header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "Content-Length: 4\r\n"
+ "\r\n"
+ "body"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 3
+ ,.body= "body"
+ ,.upgrade= NULL
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ , { "Content-Length", "4" }
+ }
+ }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 27
+, {.name= "HTTP 200 response with Upgrade and Transfer-Encoding header"
+ ,.type= HTTP_RESPONSE
+ ,.raw= "HTTP/1.1 200 OK\r\n"
+ "Connection: upgrade\r\n"
+ "Upgrade: h2c\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "2\r\n"
+ "bo\r\n"
+ "2\r\n"
+ "dy\r\n"
+ "0\r\n"
+ "\r\n"
+ ,.should_keep_alive= TRUE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 1
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 3
+ ,.body= "body"
+ ,.upgrade= NULL
+ ,.headers=
+ { { "Connection", "upgrade" }
+ , { "Upgrade", "h2c" }
+ , { "Transfer-Encoding", "chunked" }
+ }
+ ,.num_chunks_complete= 3
+ ,.chunk_lengths= { 2, 2 }
+ }
+};
+
+/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
+ * define it ourselves.
+ */
+size_t
+strnlen(const char *s, size_t maxlen)
+{
+ const char *p;
+
+ p = memchr(s, '\0', maxlen);
+ if (p == NULL)
+ return maxlen;
+
+ return p - s;
+}
+
+size_t
+strlncat(char *dst, size_t len, const char *src, size_t n)
+{
+ size_t slen;
+ size_t dlen;
+ size_t rlen;
+ size_t ncpy;
+
+ slen = strnlen(src, n);
+ dlen = strnlen(dst, len);
+
+ if (dlen < len) {
+ rlen = len - dlen;
+ ncpy = slen < rlen ? slen : (rlen - 1);
+ memcpy(dst + dlen, src, ncpy);
+ dst[dlen + ncpy] = '\0';
+ }
+
+ assert(len > slen + dlen);
+ return slen + dlen;
+}
+
+size_t
+strlncpy(char *dst, size_t len, const char *src, size_t n)
+{
+ size_t slen;
+ size_t ncpy;
+
+ slen = strnlen(src, n);
+
+ if (len > 0) {
+ ncpy = slen < len ? slen : (len - 1);
+ memcpy(dst, src, ncpy);
+ dst[ncpy] = '\0';
+ }
+
+ assert(len > slen);
+ return slen;
+}
+
+int
+request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ strlncat(messages[num_messages].request_url,
+ sizeof(messages[num_messages].request_url),
+ buf,
+ len);
+ return 0;
+}
+
+int
+header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ struct message *m = &messages[num_messages];
+
+ if (m->last_header_element != FIELD)
+ m->num_headers++;
+
+ strlncat(m->headers[m->num_headers-1][0],
+ sizeof(m->headers[m->num_headers-1][0]),
+ buf,
+ len);
+
+ m->last_header_element = FIELD;
+
+ return 0;
+}
+
+int
+header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ struct message *m = &messages[num_messages];
+
+ strlncat(m->headers[m->num_headers-1][1],
+ sizeof(m->headers[m->num_headers-1][1]),
+ buf,
+ len);
+
+ m->last_header_element = VALUE;
+
+ return 0;
+}
+
+void
+check_body_is_final (const http_parser *p)
+{
+ if (messages[num_messages].body_is_final) {
+ fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+ "on last on_body callback call "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+ messages[num_messages].body_is_final = http_body_is_final(p);
+}
+
+int
+body_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ strlncat(messages[num_messages].body,
+ sizeof(messages[num_messages].body),
+ buf,
+ len);
+ messages[num_messages].body_size += len;
+ check_body_is_final(p);
+ // printf("body_cb: '%s'\n", requests[num_messages].body);
+ return 0;
+}
+
+int
+count_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+ assert(buf);
+ messages[num_messages].body_size += len;
+ check_body_is_final(p);
+ return 0;
+}
+
+int
+message_begin_cb (http_parser *p)
+{
+ assert(p == parser);
+ messages[num_messages].message_begin_cb_called = TRUE;
+ return 0;
+}
+
+int
+headers_complete_cb (http_parser *p)
+{
+ assert(p == parser);
+ messages[num_messages].method = parser->method;
+ messages[num_messages].status_code = parser->status_code;
+ messages[num_messages].http_major = parser->http_major;
+ messages[num_messages].http_minor = parser->http_minor;
+ messages[num_messages].headers_complete_cb_called = TRUE;
+ messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+ return 0;
+}
+
+int
+message_complete_cb (http_parser *p)
+{
+ assert(p == parser);
+ if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
+ {
+ fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
+ "value in both on_message_complete and on_headers_complete "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+
+ if (messages[num_messages].body_size &&
+ http_body_is_final(p) &&
+ !messages[num_messages].body_is_final)
+ {
+ fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+ "on last on_body callback call "
+ "but it doesn't! ***\n\n");
+ assert(0);
+ abort();
+ }
+
+ messages[num_messages].message_complete_cb_called = TRUE;
+
+ messages[num_messages].message_complete_on_eof = currently_parsing_eof;
+
+ num_messages++;
+ return 0;
+}
+
+int
+response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+ assert(p == parser);
+
+ messages[num_messages].status_cb_called = TRUE;
+
+ strlncat(messages[num_messages].response_status,
+ sizeof(messages[num_messages].response_status),
+ buf,
+ len);
+ return 0;
+}
+
+int
+chunk_header_cb (http_parser *p)
+{
+ assert(p == parser);
+ int chunk_idx = messages[num_messages].num_chunks;
+ messages[num_messages].num_chunks++;
+ if (chunk_idx < MAX_CHUNKS) {
+ messages[num_messages].chunk_lengths[chunk_idx] = p->content_length;
+ }
+
+ return 0;
+}
+
+int
+chunk_complete_cb (http_parser *p)
+{
+ assert(p == parser);
+
+ /* Here we want to verify that each chunk_header_cb is matched by a
+ * chunk_complete_cb, so not only should the total number of calls to
+ * both callbacks be the same, but they also should be interleaved
+ * properly */
+ assert(messages[num_messages].num_chunks ==
+ messages[num_messages].num_chunks_complete + 1);
+
+ messages[num_messages].num_chunks_complete++;
+ return 0;
+}
+
+/* These dontcall_* callbacks exist so that we can verify that when we're
+ * paused, no additional callbacks are invoked */
+int
+dontcall_message_begin_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_headers_complete_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_headers_complete() called on paused "
+ "parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_message_complete_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_message_complete() called on paused "
+ "parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+ if (p || buf || len) { } // gcc
+ fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n");
+ abort();
+}
+
+int
+dontcall_chunk_header_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_chunk_header() called on paused parser ***\n\n");
+ exit(1);
+}
+
+int
+dontcall_chunk_complete_cb (http_parser *p)
+{
+ if (p) { } // gcc
+ fprintf(stderr, "\n\n*** on_chunk_complete() "
+ "called on paused parser ***\n\n");
+ exit(1);
+}
+
+static http_parser_settings settings_dontcall =
+ {.on_message_begin = dontcall_message_begin_cb
+ ,.on_header_field = dontcall_header_field_cb
+ ,.on_header_value = dontcall_header_value_cb
+ ,.on_url = dontcall_request_url_cb
+ ,.on_status = dontcall_response_status_cb
+ ,.on_body = dontcall_body_cb
+ ,.on_headers_complete = dontcall_headers_complete_cb
+ ,.on_message_complete = dontcall_message_complete_cb
+ ,.on_chunk_header = dontcall_chunk_header_cb
+ ,.on_chunk_complete = dontcall_chunk_complete_cb
+ };
+
+/* These pause_* callbacks always pause the parser and just invoke the regular
+ * callback that tracks content. Before returning, we overwrite the parser
+ * settings to point to the _dontcall variety so that we can verify that
+ * the pause actually did, you know, pause. */
+int
+pause_message_begin_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return message_begin_cb(p);
+}
+
+int
+pause_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return header_field_cb(p, buf, len);
+}
+
+int
+pause_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return header_value_cb(p, buf, len);
+}
+
+int
+pause_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return request_url_cb(p, buf, len);
+}
+
+int
+pause_body_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return body_cb(p, buf, len);
+}
+
+int
+pause_headers_complete_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return headers_complete_cb(p);
+}
+
+int
+pause_message_complete_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return message_complete_cb(p);
+}
+
+int
+pause_response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return response_status_cb(p, buf, len);
+}
+
+int
+pause_chunk_header_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return chunk_header_cb(p);
+}
+
+int
+pause_chunk_complete_cb (http_parser *p)
+{
+ http_parser_pause(p, 1);
+ *current_pause_parser = settings_dontcall;
+ return chunk_complete_cb(p);
+}
+
+int
+connect_headers_complete_cb (http_parser *p)
+{
+ headers_complete_cb(p);
+ return 1;
+}
+
+int
+connect_message_complete_cb (http_parser *p)
+{
+ messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+ return message_complete_cb(p);
+}
+
+static http_parser_settings settings_pause =
+ {.on_message_begin = pause_message_begin_cb
+ ,.on_header_field = pause_header_field_cb
+ ,.on_header_value = pause_header_value_cb
+ ,.on_url = pause_request_url_cb
+ ,.on_status = pause_response_status_cb
+ ,.on_body = pause_body_cb
+ ,.on_headers_complete = pause_headers_complete_cb
+ ,.on_message_complete = pause_message_complete_cb
+ ,.on_chunk_header = pause_chunk_header_cb
+ ,.on_chunk_complete = pause_chunk_complete_cb
+ };
+
+static http_parser_settings settings =
+ {.on_message_begin = message_begin_cb
+ ,.on_header_field = header_field_cb
+ ,.on_header_value = header_value_cb
+ ,.on_url = request_url_cb
+ ,.on_status = response_status_cb
+ ,.on_body = body_cb
+ ,.on_headers_complete = headers_complete_cb
+ ,.on_message_complete = message_complete_cb
+ ,.on_chunk_header = chunk_header_cb
+ ,.on_chunk_complete = chunk_complete_cb
+ };
+
+static http_parser_settings settings_count_body =
+ {.on_message_begin = message_begin_cb
+ ,.on_header_field = header_field_cb
+ ,.on_header_value = header_value_cb
+ ,.on_url = request_url_cb
+ ,.on_status = response_status_cb
+ ,.on_body = count_body_cb
+ ,.on_headers_complete = headers_complete_cb
+ ,.on_message_complete = message_complete_cb
+ ,.on_chunk_header = chunk_header_cb
+ ,.on_chunk_complete = chunk_complete_cb
+ };
+
+static http_parser_settings settings_connect =
+ {.on_message_begin = message_begin_cb
+ ,.on_header_field = header_field_cb
+ ,.on_header_value = header_value_cb
+ ,.on_url = request_url_cb
+ ,.on_status = response_status_cb
+ ,.on_body = dontcall_body_cb
+ ,.on_headers_complete = connect_headers_complete_cb
+ ,.on_message_complete = connect_message_complete_cb
+ ,.on_chunk_header = chunk_header_cb
+ ,.on_chunk_complete = chunk_complete_cb
+ };
+
+static http_parser_settings settings_null =
+ {.on_message_begin = 0
+ ,.on_header_field = 0
+ ,.on_header_value = 0
+ ,.on_url = 0
+ ,.on_status = 0
+ ,.on_body = 0
+ ,.on_headers_complete = 0
+ ,.on_message_complete = 0
+ ,.on_chunk_header = 0
+ ,.on_chunk_complete = 0
+ };
+
+void
+parser_init (enum http_parser_type type)
+{
+ num_messages = 0;
+
+ assert(parser == NULL);
+
+ parser = malloc(sizeof(http_parser));
+
+ http_parser_init(parser, type);
+
+ memset(&messages, 0, sizeof messages);
+
+}
+
+void
+parser_free ()
+{
+ assert(parser);
+ free(parser);
+ parser = NULL;
+}
+
+size_t parse (const char *buf, size_t len)
+{
+ size_t nparsed;
+ currently_parsing_eof = (len == 0);
+ nparsed = http_parser_execute(parser, &settings, buf, len);
+ return nparsed;
+}
+
+size_t parse_count_body (const char *buf, size_t len)
+{
+ size_t nparsed;
+ currently_parsing_eof = (len == 0);
+ nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
+ return nparsed;
+}
+
+size_t parse_pause (const char *buf, size_t len)
+{
+ size_t nparsed;
+ http_parser_settings s = settings_pause;
+
+ currently_parsing_eof = (len == 0);
+ current_pause_parser = &s;
+ nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
+ return nparsed;
+}
+
+size_t parse_connect (const char *buf, size_t len)
+{
+ size_t nparsed;
+ currently_parsing_eof = (len == 0);
+ nparsed = http_parser_execute(parser, &settings_connect, buf, len);
+ return nparsed;
+}
+
+static inline int
+check_str_eq (const struct message *m,
+ const char *prop,
+ const char *expected,
+ const char *found) {
+ if ((expected == NULL) != (found == NULL)) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected %s\n", (expected == NULL) ? "NULL" : expected);
+ printf(" found %s\n", (found == NULL) ? "NULL" : found);
+ return 0;
+ }
+ if (expected != NULL && 0 != strcmp(expected, found)) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected '%s'\n", expected);
+ printf(" found '%s'\n", found);
+ return 0;
+ }
+ return 1;
+}
+
+static inline int
+check_num_eq (const struct message *m,
+ const char *prop,
+ int expected,
+ int found) {
+ if (expected != found) {
+ printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+ printf("expected %d\n", expected);
+ printf(" found %d\n", found);
+ return 0;
+ }
+ return 1;
+}
+
+#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
+ if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
+ if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \
+do { \
+ char ubuf[256]; \
+ \
+ if ((u)->field_set & (1 << (fn))) { \
+ memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \
+ (u)->field_data[(fn)].len); \
+ ubuf[(u)->field_data[(fn)].len] = '\0'; \
+ } else { \
+ ubuf[0] = '\0'; \
+ } \
+ \
+ check_str_eq(expected, #prop, expected->prop, ubuf); \
+} while(0)
+
+int
+message_eq (int index, int connect, const struct message *expected)
+{
+ int i;
+ struct message *m = &messages[index];
+
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
+ MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
+
+ if (expected->type == HTTP_REQUEST) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, method);
+ } else {
+ MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
+ MESSAGE_CHECK_STR_EQ(expected, m, response_status);
+ assert(m->status_cb_called);
+ }
+
+ if (!connect) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
+ MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
+ }
+
+ assert(m->message_begin_cb_called);
+ assert(m->headers_complete_cb_called);
+ assert(m->message_complete_cb_called);
+
+
+ MESSAGE_CHECK_STR_EQ(expected, m, request_url);
+
+ /* Check URL components; we can't do this w/ CONNECT since it doesn't
+ * send us a well-formed URL.
+ */
+ if (*m->request_url && m->method != HTTP_CONNECT) {
+ struct http_parser_url u;
+
+ if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) {
+ fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n",
+ m->request_url);
+ abort();
+ }
+
+ if (expected->host) {
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST);
+ }
+
+ if (expected->userinfo) {
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO);
+ }
+
+ m->port = (u.field_set & (1 << UF_PORT)) ?
+ u.port : 0;
+
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY);
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT);
+ MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH);
+ MESSAGE_CHECK_NUM_EQ(expected, m, port);
+ }
+
+ if (connect) {
+ check_num_eq(m, "body_size", 0, m->body_size);
+ } else if (expected->body_size) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
+ } else {
+ MESSAGE_CHECK_STR_EQ(expected, m, body);
+ }
+
+ if (connect) {
+ check_num_eq(m, "num_chunks_complete", 0, m->num_chunks_complete);
+ } else {
+ assert(m->num_chunks == m->num_chunks_complete);
+ MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete);
+ for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) {
+ MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]);
+ }
+ }
+
+ MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
+
+ int r;
+ for (i = 0; i < m->num_headers; i++) {
+ r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
+ if (!r) return 0;
+ r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
+ if (!r) return 0;
+ }
+
+ if (!connect) {
+ MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
+ }
+
+ return 1;
+}
+
+/* Given a sequence of varargs messages, return the number of them that the
+ * parser should successfully parse, taking into account that upgraded
+ * messages prevent all subsequent messages from being parsed.
+ */
+size_t
+count_parsed_messages(const size_t nmsgs, ...) {
+ size_t i;
+ va_list ap;
+
+ va_start(ap, nmsgs);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct message *m = va_arg(ap, struct message *);
+
+ if (m->upgrade) {
+ va_end(ap);
+ return i + 1;
+ }
+ }
+
+ va_end(ap);
+ return nmsgs;
+}
+
+/* Given a sequence of bytes and the number of these that we were able to
+ * parse, verify that upgrade bodies are correct.
+ */
+void
+upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
+ va_list ap;
+ size_t i;
+ size_t off = 0;
+
+ va_start(ap, nmsgs);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct message *m = va_arg(ap, struct message *);
+
+ off += strlen(m->raw);
+
+ if (m->upgrade) {
+ off -= strlen(m->upgrade);
+
+ /* Check the portion of the response after its specified upgrade */
+ if (!check_str_eq(m, "upgrade", body + off, body + nread)) {
+ abort();
+ }
+
+ /* Fix up the response so that message_eq() will verify the beginning
+ * of the upgrade */
+ *(body + nread + strlen(m->upgrade)) = '\0';
+ messages[num_messages -1 ].upgrade = body + nread;
+
+ va_end(ap);
+ return;
+ }
+ }
+
+ va_end(ap);
+ printf("\n\n*** Error: expected a message with upgrade ***\n");
+
+ abort();
+}
+
+static void
+print_error (const char *raw, size_t error_location)
+{
+ fprintf(stderr, "\n*** %s ***\n\n",
+ http_errno_description(HTTP_PARSER_ERRNO(parser)));
+
+ int this_line = 0, char_len = 0;
+ size_t i, j, len = strlen(raw), error_location_line = 0;
+ for (i = 0; i < len; i++) {
+ if (i == error_location) this_line = 1;
+ switch (raw[i]) {
+ case '\r':
+ char_len = 2;
+ fprintf(stderr, "\\r");
+ break;
+
+ case '\n':
+ fprintf(stderr, "\\n\n");
+
+ if (this_line) goto print;
+
+ error_location_line = 0;
+ continue;
+
+ default:
+ char_len = 1;
+ fputc(raw[i], stderr);
+ break;
+ }
+ if (!this_line) error_location_line += char_len;
+ }
+
+ fprintf(stderr, "[eof]\n");
+
+ print:
+ for (j = 0; j < error_location_line; j++) {
+ fputc(' ', stderr);
+ }
+ fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location);
+}
+
+void
+test_preserve_data (void)
+{
+ char my_data[] = "application-specific data";
+ http_parser parser;
+ parser.data = my_data;
+ http_parser_init(&parser, HTTP_REQUEST);
+ if (parser.data != my_data) {
+ printf("\n*** parser.data not preserved accross http_parser_init ***\n\n");
+ abort();
+ }
+}
+
+struct url_test {
+ const char *name;
+ const char *url;
+ int is_connect;
+ struct http_parser_url u;
+ int rv;
+};
+
+const struct url_test url_tests[] =
+{ {.name="proxy request"
+ ,.url="http://hostname/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 15, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy request with port"
+ ,.url="http://hostname:444/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+ ,.port=444
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 16, 3 } /* UF_PORT */
+ ,{ 19, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT request"
+ ,.url="hostname:443"
+ ,.is_connect=1
+ ,.u=
+ {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+ ,.port=443
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 8 } /* UF_HOST */
+ ,{ 9, 3 } /* UF_PORT */
+ ,{ 0, 0 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT request but not connect"
+ ,.url="hostname:443"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy ipv6 request"
+ ,.url="http://[1:2::3:4]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 17, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy ipv6 request with port"
+ ,.url="http://[1:2::3:4]:67/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+ ,.port=67
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 18, 2 } /* UF_PORT */
+ ,{ 20, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="CONNECT ipv6 address"
+ ,.url="[1:2::3:4]:443"
+ ,.is_connect=1
+ ,.u=
+ {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+ ,.port=443
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 1, 8 } /* UF_HOST */
+ ,{ 11, 3 } /* UF_PORT */
+ ,{ 0, 0 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="ipv4 in ipv6 address"
+ ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 37 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 46, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="extra ? in query string"
+ ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,"
+ "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,"
+ "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css"
+ ,.is_connect=0
+ ,.u=
+ {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 10 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 17, 12 } /* UF_PATH */
+ ,{ 30,187 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="space URL encoded"
+ ,.url="/toto.html?toto=a%20b"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_PATH) | (1<<UF_QUERY)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 10 } /* UF_PATH */
+ ,{ 11, 10 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+
+, {.name="URL fragment"
+ ,.url="/toto.html#titi"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 10 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 11, 4 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL fragment"
+ ,.url="http://www.webmasterworld.com/r.cgi?f=21&d=8405&url="
+ "http://www.example.com/index.html?foo=bar&hello=world#midpage"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\
+ (1<<UF_FRAGMENT)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 22 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 29, 6 } /* UF_PATH */
+ ,{ 36, 69 } /* UF_QUERY */
+ ,{106, 7 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL from node js url parser doc"
+ ,.url="http://host.com:8080/p/a/t/h?query=string#hash"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+ (1<<UF_QUERY) | (1<<UF_FRAGMENT)
+ ,.port=8080
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 7, 8 } /* UF_HOST */
+ ,{ 16, 4 } /* UF_PORT */
+ ,{ 20, 8 } /* UF_PATH */
+ ,{ 29, 12 } /* UF_QUERY */
+ ,{ 42, 4 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="complex URL with basic auth from node js url parser doc"
+ ,.url="http://a:b@host.com:8080/p/a/t/h?query=string#hash"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+ (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO)
+ ,.port=8080
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 11, 8 } /* UF_HOST */
+ ,{ 20, 4 } /* UF_PORT */
+ ,{ 24, 8 } /* UF_PATH */
+ ,{ 33, 12 } /* UF_QUERY */
+ ,{ 46, 4 } /* UF_FRAGMENT */
+ ,{ 7, 3 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="double @"
+ ,.url="http://a:b@@hostname:443/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy empty host"
+ ,.url="http://:443/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="proxy empty port"
+ ,.url="http://hostname:/"
+ ,.is_connect=0
+ ,.rv=1
+ }
+
+, {.name="CONNECT with basic auth"
+ ,.url="a:b@hostname:443"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT empty host"
+ ,.url=":443"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT empty port"
+ ,.url="hostname:"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="CONNECT with extra bits"
+ ,.url="hostname:443/"
+ ,.is_connect=1
+ ,.rv=1
+ }
+
+, {.name="space in URL"
+ ,.url="/foo bar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with space url encoded"
+ ,.url="http://a%20:b@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 14, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 22, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 6 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="carriage return in URL"
+ ,.url="/foo\rbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy double : in URL"
+ ,.url="http://hostname::443/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with double :"
+ ,.url="http://a::b@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 12, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 20, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 4 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="line feed in URL"
+ ,.url="/foo\nbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy empty basic auth"
+ ,.url="http://@hostname/fo"
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 16, 3 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+, {.name="proxy line feed in hostname"
+ ,.url="http://host\name/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy % in hostname"
+ ,.url="http://host%name/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy ; in hostname"
+ ,.url="http://host;ame/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy basic auth with unreservedchars"
+ ,.url="http://a!;-_!=+$@host.com/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 17, 8 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 25, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 7, 9 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="proxy only empty basic auth"
+ ,.url="http://@/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy only basic auth"
+ ,.url="http://toto@/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy emtpy hostname"
+ ,.url="http:///fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="proxy = in URL"
+ ,.url="http://host=ame/fo"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="ipv6 address with Zone ID"
+ ,.url="http://[fe80::a%25eth0]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 14 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 23, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="ipv6 address with Zone ID, but '%' is not percent-encoded"
+ ,.url="http://[fe80::a%eth0]/"
+ ,.is_connect=0
+ ,.u=
+ {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)
+ ,.port=0
+ ,.field_data=
+ {{ 0, 4 } /* UF_SCHEMA */
+ ,{ 8, 12 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 21, 1 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="ipv6 address ending with '%'"
+ ,.url="http://[fe80::a%]/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="ipv6 address with Zone ID including bad character"
+ ,.url="http://[fe80::a%$HOME]/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="just ipv6 Zone ID"
+ ,.url="http://[%eth0]/"
+ ,.rv=1 /* s_dead */
+ }
+
+#if HTTP_PARSER_STRICT
+
+, {.name="tab in URL"
+ ,.url="/foo\tbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+, {.name="form feed in URL"
+ ,.url="/foo\fbar/"
+ ,.rv=1 /* s_dead */
+ }
+
+#else /* !HTTP_PARSER_STRICT */
+
+, {.name="tab in URL"
+ ,.url="/foo\tbar/"
+ ,.u=
+ {.field_set=(1 << UF_PATH)
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 9 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+
+, {.name="form feed in URL"
+ ,.url="/foo\fbar/"
+ ,.u=
+ {.field_set=(1 << UF_PATH)
+ ,.field_data=
+ {{ 0, 0 } /* UF_SCHEMA */
+ ,{ 0, 0 } /* UF_HOST */
+ ,{ 0, 0 } /* UF_PORT */
+ ,{ 0, 9 } /* UF_PATH */
+ ,{ 0, 0 } /* UF_QUERY */
+ ,{ 0, 0 } /* UF_FRAGMENT */
+ ,{ 0, 0 } /* UF_USERINFO */
+ }
+ }
+ ,.rv=0
+ }
+#endif
+};
+
+void
+dump_url (const char *url, const struct http_parser_url *u)
+{
+ unsigned int i;
+
+ printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
+ for (i = 0; i < UF_MAX; i++) {
+ if ((u->field_set & (1 << i)) == 0) {
+ printf("\tfield_data[%u]: unset\n", i);
+ continue;
+ }
+
+ printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"",
+ i,
+ u->field_data[i].off,
+ u->field_data[i].len,
+ u->field_data[i].len,
+ url + u->field_data[i].off);
+ }
+}
+
+void
+test_parse_url (void)
+{
+ struct http_parser_url u;
+ const struct url_test *test;
+ unsigned int i;
+ int rv;
+
+ for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) {
+ test = &url_tests[i];
+ memset(&u, 0, sizeof(u));
+
+ rv = http_parser_parse_url(test->url,
+ strlen(test->url),
+ test->is_connect,
+ &u);
+
+ if (test->rv == 0) {
+ if (rv != 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+ "unexpected rv %d ***\n\n", test->url, test->name, rv);
+ abort();
+ }
+
+ if (memcmp(&u, &test->u, sizeof(u)) != 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n",
+ test->url, test->name);
+
+ printf("target http_parser_url:\n");
+ dump_url(test->url, &test->u);
+ printf("result http_parser_url:\n");
+ dump_url(test->url, &u);
+
+ abort();
+ }
+ } else {
+ /* test->rv != 0 */
+ if (rv == 0) {
+ printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+ "unexpected rv %d ***\n\n", test->url, test->name, rv);
+ abort();
+ }
+ }
+ }
+}
+
+void
+test_method_str (void)
+{
+ assert(0 == strcmp("GET", http_method_str(HTTP_GET)));
+ assert(0 == strcmp("<unknown>", http_method_str(1337)));
+}
+
+void
+test_message (const struct message *message)
+{
+ size_t raw_len = strlen(message->raw);
+ size_t msg1len;
+ for (msg1len = 0; msg1len < raw_len; msg1len++) {
+ parser_init(message->type);
+
+ size_t read;
+ const char *msg1 = message->raw;
+ const char *msg2 = msg1 + msg1len;
+ size_t msg2len = raw_len - msg1len;
+
+ if (msg1len) {
+ read = parse(msg1, msg1len);
+
+ if (message->upgrade && parser->upgrade && num_messages > 0) {
+ messages[num_messages - 1].upgrade = msg1 + read;
+ goto test;
+ }
+
+ if (read != msg1len) {
+ print_error(msg1, read);
+ abort();
+ }
+ }
+
+
+ read = parse(msg2, msg2len);
+
+ if (message->upgrade && parser->upgrade) {
+ messages[num_messages - 1].upgrade = msg2 + read;
+ goto test;
+ }
+
+ if (read != msg2len) {
+ print_error(msg2, read);
+ abort();
+ }
+
+ read = parse(NULL, 0);
+
+ if (read != 0) {
+ print_error(message->raw, read);
+ abort();
+ }
+
+ test:
+
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+ abort();
+ }
+
+ if(!message_eq(0, 0, message)) abort();
+
+ parser_free();
+ }
+}
+
+void
+test_message_count_body (const struct message *message)
+{
+ parser_init(message->type);
+
+ size_t read;
+ size_t l = strlen(message->raw);
+ size_t i, toread;
+ size_t chunk = 4024;
+
+ for (i = 0; i < l; i+= chunk) {
+ toread = MIN(l-i, chunk);
+ read = parse_count_body(message->raw + i, toread);
+ if (read != toread) {
+ print_error(message->raw, read);
+ abort();
+ }
+ }
+
+
+ read = parse_count_body(NULL, 0);
+ if (read != 0) {
+ print_error(message->raw, read);
+ abort();
+ }
+
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+ abort();
+ }
+
+ if(!message_eq(0, 0, message)) abort();
+
+ parser_free();
+}
+
+void
+test_simple_type (const char *buf,
+ enum http_errno err_expected,
+ enum http_parser_type type)
+{
+ parser_init(type);
+
+ enum http_errno err;
+
+ parse(buf, strlen(buf));
+ err = HTTP_PARSER_ERRNO(parser);
+ parse(NULL, 0);
+
+ parser_free();
+
+ /* In strict mode, allow us to pass with an unexpected HPE_STRICT as
+ * long as the caller isn't expecting success.
+ */
+#if HTTP_PARSER_STRICT
+ if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) {
+#else
+ if (err_expected != err) {
+#endif
+ fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n",
+ http_errno_name(err_expected), http_errno_name(err), buf);
+ abort();
+ }
+}
+
+void
+test_simple (const char *buf, enum http_errno err_expected)
+{
+ test_simple_type(buf, err_expected, HTTP_REQUEST);
+}
+
+void
+test_invalid_header_content (int req, const char* str)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ?
+ "GET / HTTP/1.1\r\n" :
+ "HTTP/1.1 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = str;
+ size_t buflen = strlen(buf);
+
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
+ return;
+ }
+
+ fprintf(stderr,
+ "\n*** Error expected but none in invalid header content test ***\n");
+ abort();
+}
+
+void
+test_invalid_header_field_content_error (int req)
+{
+ test_invalid_header_content(req, "Foo: F\01ailure");
+ test_invalid_header_content(req, "Foo: B\02ar");
+}
+
+void
+test_invalid_header_field (int req, const char* str)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ?
+ "GET / HTTP/1.1\r\n" :
+ "HTTP/1.1 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = str;
+ size_t buflen = strlen(buf);
+
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
+ return;
+ }
+
+ fprintf(stderr,
+ "\n*** Error expected but none in invalid header token test ***\n");
+ abort();
+}
+
+void
+test_invalid_header_field_token_error (int req)
+{
+ test_invalid_header_field(req, "Fo@: Failure");
+ test_invalid_header_field(req, "Foo\01\test: Bar");
+}
+
+void
+test_double_content_length_error (int req)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ?
+ "GET / HTTP/1.1\r\n" :
+ "HTTP/1.1 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
+ size_t buflen = strlen(buf);
+
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
+ return;
+ }
+
+ fprintf(stderr,
+ "\n*** Error expected but none in double content-length test ***\n");
+ abort();
+}
+
+void
+test_chunked_content_length_error (int req)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ?
+ "GET / HTTP/1.1\r\n" :
+ "HTTP/1.1 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
+ size_t buflen = strlen(buf);
+
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
+ return;
+ }
+
+ fprintf(stderr,
+ "\n*** Error expected but none in chunked content-length test ***\n");
+ abort();
+}
+
+void
+test_header_cr_no_lf_error (int req)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ?
+ "GET / HTTP/1.1\r\n" :
+ "HTTP/1.1 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = "Foo: 1\rBar: 1\r\n\r\n";
+ size_t buflen = strlen(buf);
+
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
+ return;
+ }
+
+ fprintf(stderr,
+ "\n*** Error expected but none in header whitespace test ***\n");
+ abort();
+}
+
+void
+test_no_overflow_parse_url (void)
+{
+ int rv;
+ struct http_parser_url u;
+
+ http_parser_url_init(&u);
+ rv = http_parser_parse_url("http://example.com:8001", 22, 0, &u);
+
+ if (rv != 0) {
+ fprintf(stderr,
+ "\n*** test_no_overflow_parse_url invalid return value=%d\n",
+ rv);
+ abort();
+ }
+
+ if (u.port != 800) {
+ fprintf(stderr,
+ "\n*** test_no_overflow_parse_url invalid port number=%d\n",
+ u.port);
+ abort();
+ }
+}
+
+void
+test_header_overflow_error (int req)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ const char *buf;
+ buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ buf = "header-key: header-value\r\n";
+ size_t buflen = strlen(buf);
+
+ int i;
+ for (i = 0; i < 10000; i++) {
+ parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+ if (parsed != buflen) {
+ //fprintf(stderr, "error found on iter %d\n", i);
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW);
+ return;
+ }
+ }
+
+ fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
+ abort();
+}
+
+
+void
+test_header_nread_value ()
+{
+ http_parser parser;
+ http_parser_init(&parser, HTTP_REQUEST);
+ size_t parsed;
+ const char *buf;
+ buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n";
+ parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+ assert(parsed == strlen(buf));
+
+ assert(parser.nread == strlen(buf));
+}
+
+
+static void
+test_content_length_overflow (const char *buf, size_t buflen, int expect_ok)
+{
+ http_parser parser;
+ http_parser_init(&parser, HTTP_RESPONSE);
+ http_parser_execute(&parser, &settings_null, buf, buflen);
+
+ if (expect_ok)
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK);
+ else
+ assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH);
+}
+
+void
+test_header_content_length_overflow_error (void)
+{
+#define X(size) \
+ "HTTP/1.1 200 OK\r\n" \
+ "Content-Length: " #size "\r\n" \
+ "\r\n"
+ const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */
+ const char b[] = X(18446744073709551615); /* 2^64-1 */
+ const char c[] = X(18446744073709551616); /* 2^64 */
+#undef X
+ test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */
+ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_chunk_content_length_overflow_error (void)
+{
+#define X(size) \
+ "HTTP/1.1 200 OK\r\n" \
+ "Transfer-Encoding: chunked\r\n" \
+ "\r\n" \
+ #size "\r\n" \
+ "..."
+ const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */
+ const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */
+ const char c[] = X(10000000000000000); /* 2^64 */
+#undef X
+ test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */
+ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_no_overflow_long_body (int req, size_t length)
+{
+ http_parser parser;
+ http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+ size_t parsed;
+ size_t i;
+ char buf1[3000];
+ size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n",
+ req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length);
+ parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+ if (parsed != buf1len)
+ goto err;
+
+ for (i = 0; i < length; i++) {
+ char foo = 'a';
+ parsed = http_parser_execute(&parser, &settings_null, &foo, 1);
+ if (parsed != 1)
+ goto err;
+ }
+
+ parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+ if (parsed != buf1len) goto err;
+ return;
+
+ err:
+ fprintf(stderr,
+ "\n*** error in test_no_overflow_long_body %s of length %lu ***\n",
+ req ? "REQUEST" : "RESPONSE",
+ (unsigned long)length);
+ abort();
+}
+
+void
+test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+ int message_count = count_parsed_messages(3, r1, r2, r3);
+
+ char total[ strlen(r1->raw)
+ + strlen(r2->raw)
+ + strlen(r3->raw)
+ + 1
+ ];
+ total[0] = '\0';
+
+ strcat(total, r1->raw);
+ strcat(total, r2->raw);
+ strcat(total, r3->raw);
+
+ parser_init(r1->type);
+
+ size_t read;
+
+ read = parse(total, strlen(total));
+
+ if (parser->upgrade) {
+ upgrade_message_fix(total, read, 3, r1, r2, r3);
+ goto test;
+ }
+
+ if (read != strlen(total)) {
+ print_error(total, read);
+ abort();
+ }
+
+ read = parse(NULL, 0);
+
+ if (read != 0) {
+ print_error(total, read);
+ abort();
+ }
+
+test:
+
+ if (message_count != num_messages) {
+ fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages);
+ abort();
+ }
+
+ if (!message_eq(0, 0, r1)) abort();
+ if (message_count > 1 && !message_eq(1, 0, r2)) abort();
+ if (message_count > 2 && !message_eq(2, 0, r3)) abort();
+
+ parser_free();
+}
+
+/* SCAN through every possible breaking to make sure the
+ * parser can handle getting the content in any chunks that
+ * might come from the socket
+ */
+void
+test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+ char total[80*1024] = "\0";
+ char buf1[80*1024] = "\0";
+ char buf2[80*1024] = "\0";
+ char buf3[80*1024] = "\0";
+
+ strcat(total, r1->raw);
+ strcat(total, r2->raw);
+ strcat(total, r3->raw);
+
+ size_t read;
+
+ int total_len = strlen(total);
+
+ int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;
+ int ops = 0 ;
+
+ size_t buf1_len, buf2_len, buf3_len;
+ int message_count = count_parsed_messages(3, r1, r2, r3);
+
+ int i,j,type_both;
+ for (type_both = 0; type_both < 2; type_both ++ ) {
+ for (j = 2; j < total_len; j ++ ) {
+ for (i = 1; i < j; i ++ ) {
+
+ if (ops % 1000 == 0) {
+ printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
+ fflush(stdout);
+ }
+ ops += 1;
+
+ parser_init(type_both ? HTTP_BOTH : r1->type);
+
+ buf1_len = i;
+ strlncpy(buf1, sizeof(buf1), total, buf1_len);
+ buf1[buf1_len] = 0;
+
+ buf2_len = j - i;
+ strlncpy(buf2, sizeof(buf1), total+i, buf2_len);
+ buf2[buf2_len] = 0;
+
+ buf3_len = total_len - j;
+ strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
+ buf3[buf3_len] = 0;
+
+ read = parse(buf1, buf1_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len) {
+ print_error(buf1, read);
+ goto error;
+ }
+
+ read += parse(buf2, buf2_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len + buf2_len) {
+ print_error(buf2, read);
+ goto error;
+ }
+
+ read += parse(buf3, buf3_len);
+
+ if (parser->upgrade) goto test;
+
+ if (read != buf1_len + buf2_len + buf3_len) {
+ print_error(buf3, read);
+ goto error;
+ }
+
+ parse(NULL, 0);
+
+test:
+ if (parser->upgrade) {
+ upgrade_message_fix(total, read, 3, r1, r2, r3);
+ }
+
+ if (message_count != num_messages) {
+ fprintf(stderr, "\n\nParser didn't see %d messages only %d\n",
+ message_count, num_messages);
+ goto error;
+ }
+
+ if (!message_eq(0, 0, r1)) {
+ fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
+ goto error;
+ }
+
+ if (message_count > 1 && !message_eq(1, 0, r2)) {
+ fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
+ goto error;
+ }
+
+ if (message_count > 2 && !message_eq(2, 0, r3)) {
+ fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
+ goto error;
+ }
+
+ parser_free();
+ }
+ }
+ }
+ puts("\b\b\b\b100%");
+ return;
+
+ error:
+ fprintf(stderr, "i=%d j=%d\n", i, j);
+ fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1);
+ fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2);
+ fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3);
+ abort();
+}
+
+// user required to free the result
+// string terminated by \0
+char *
+create_large_chunked_message (int body_size_in_kb, const char* headers)
+{
+ int i;
+ size_t wrote = 0;
+ size_t headers_len = strlen(headers);
+ size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6;
+ char * buf = malloc(bufsize);
+
+ memcpy(buf, headers, headers_len);
+ wrote += headers_len;
+
+ for (i = 0; i < body_size_in_kb; i++) {
+ // write 1kb chunk into the body.
+ memcpy(buf + wrote, "400\r\n", 5);
+ wrote += 5;
+ memset(buf + wrote, 'C', 1024);
+ wrote += 1024;
+ strcpy(buf + wrote, "\r\n");
+ wrote += 2;
+ }
+
+ memcpy(buf + wrote, "0\r\n\r\n", 6);
+ wrote += 6;
+ assert(wrote == bufsize);
+
+ return buf;
+}
+
+/* Verify that we can pause parsing at any of the bytes in the
+ * message and still get the result that we're expecting. */
+void
+test_message_pause (const struct message *msg)
+{
+ char *buf = (char*) msg->raw;
+ size_t buflen = strlen(msg->raw);
+ size_t nread;
+
+ parser_init(msg->type);
+
+ do {
+ nread = parse_pause(buf, buflen);
+
+ // We can only set the upgrade buffer once we've gotten our message
+ // completion callback.
+ if (messages[0].message_complete_cb_called &&
+ msg->upgrade &&
+ parser->upgrade) {
+ messages[0].upgrade = buf + nread;
+ goto test;
+ }
+
+ if (nread < buflen) {
+
+ // Not much do to if we failed a strict-mode check
+ if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
+ parser_free();
+ return;
+ }
+
+ assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
+ }
+
+ buf += nread;
+ buflen -= nread;
+ http_parser_pause(parser, 0);
+ } while (buflen > 0);
+
+ nread = parse_pause(NULL, 0);
+ assert (nread == 0);
+
+test:
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
+ abort();
+ }
+
+ if(!message_eq(0, 0, msg)) abort();
+
+ parser_free();
+}
+
+/* Verify that body and next message won't be parsed in responses to CONNECT */
+void
+test_message_connect (const struct message *msg)
+{
+ char *buf = (char*) msg->raw;
+ size_t buflen = strlen(msg->raw);
+
+ parser_init(msg->type);
+
+ parse_connect(buf, buflen);
+
+ if (num_messages != 1) {
+ printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
+ abort();
+ }
+
+ if(!message_eq(0, 1, msg)) abort();
+
+ parser_free();
+}
+
+int
+main (void)
+{
+ parser = NULL;
+ unsigned i, j, k;
+ unsigned long version;
+ unsigned major;
+ unsigned minor;
+ unsigned patch;
+
+ version = http_parser_version();
+ major = (version >> 16) & 255;
+ minor = (version >> 8) & 255;
+ patch = version & 255;
+ printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version);
+
+ printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
+
+ //// API
+ test_preserve_data();
+ test_parse_url();
+ test_method_str();
+
+ //// NREAD
+ test_header_nread_value();
+
+ //// OVERFLOW CONDITIONS
+ test_no_overflow_parse_url();
+
+ test_header_overflow_error(HTTP_REQUEST);
+ test_no_overflow_long_body(HTTP_REQUEST, 1000);
+ test_no_overflow_long_body(HTTP_REQUEST, 100000);
+
+ test_header_overflow_error(HTTP_RESPONSE);
+ test_no_overflow_long_body(HTTP_RESPONSE, 1000);
+ test_no_overflow_long_body(HTTP_RESPONSE, 100000);
+
+ test_header_content_length_overflow_error();
+ test_chunk_content_length_overflow_error();
+
+ //// HEADER FIELD CONDITIONS
+ test_double_content_length_error(HTTP_REQUEST);
+ test_chunked_content_length_error(HTTP_REQUEST);
+ test_header_cr_no_lf_error(HTTP_REQUEST);
+ test_invalid_header_field_token_error(HTTP_REQUEST);
+ test_invalid_header_field_content_error(HTTP_REQUEST);
+ test_double_content_length_error(HTTP_RESPONSE);
+ test_chunked_content_length_error(HTTP_RESPONSE);
+ test_header_cr_no_lf_error(HTTP_RESPONSE);
+ test_invalid_header_field_token_error(HTTP_RESPONSE);
+ test_invalid_header_field_content_error(HTTP_RESPONSE);
+
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 42 \r\n" // Note the surrounding whitespace.
+ "\r\n",
+ HPE_OK,
+ HTTP_REQUEST);
+
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 4 2\r\n"
+ "\r\n",
+ HPE_INVALID_CONTENT_LENGTH,
+ HTTP_REQUEST);
+
+ test_simple_type(
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 13 37\r\n"
+ "\r\n",
+ HPE_INVALID_CONTENT_LENGTH,
+ HTTP_REQUEST);
+
+ //// RESPONSES
+
+ test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+ test_simple_type("HTTP/01.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+ test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+ test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+ test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
+ test_message(&responses[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
+ test_message_pause(&responses[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
+ test_message_connect(&responses[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(responses); i++) {
+ if (!responses[i].should_keep_alive) continue;
+ for (j = 0; j < ARRAY_SIZE(responses); j++) {
+ if (!responses[j].should_keep_alive) continue;
+ for (k = 0; k < ARRAY_SIZE(responses); k++) {
+ test_multiple3(&responses[i], &responses[j], &responses[k]);
+ }
+ }
+ }
+
+ test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
+ test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
+
+ // test very large chunked response
+ {
+ char * msg = create_large_chunked_message(31337,
+ "HTTP/1.0 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n");
+ struct message large_chunked =
+ {.name= "large chunked"
+ ,.type= HTTP_RESPONSE
+ ,.raw= msg
+ ,.should_keep_alive= FALSE
+ ,.message_complete_on_eof= FALSE
+ ,.http_major= 1
+ ,.http_minor= 0
+ ,.status_code= 200
+ ,.response_status= "OK"
+ ,.num_headers= 2
+ ,.headers=
+ { { "Transfer-Encoding", "chunked" }
+ , { "Content-Type", "text/plain" }
+ }
+ ,.body_size= 31337*1024
+ ,.num_chunks_complete= 31338
+ };
+ for (i = 0; i < MAX_CHUNKS; i++) {
+ large_chunked.chunk_lengths[i] = 1024;
+ }
+ test_message_count_body(&large_chunked);
+ free(msg);
+ }
+
+
+
+ printf("response scan 1/2 ");
+ test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
+ , &responses[NO_BODY_HTTP10_KA_204]
+ , &responses[NO_REASON_PHRASE]
+ );
+
+ printf("response scan 2/2 ");
+ test_scan( &responses[BONJOUR_MADAME_FR]
+ , &responses[UNDERSTORE_HEADER_KEY]
+ , &responses[NO_CARRIAGE_RET]
+ );
+
+ puts("responses okay");
+
+
+ /// REQUESTS
+
+ test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
+ test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION);
+ test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION);
+ test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION);
+
+ // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
+ test_simple("GET / HTTP/1.1\r\n"
+ "Test: Düsseldorf\r\n",
+ HPE_OK);
+
+ // Well-formed but incomplete
+ test_simple("GET / HTTP/1.1\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 6\r\n"
+ "\r\n"
+ "fooba",
+ HPE_OK);
+
+ static const char *all_methods[] = {
+ "DELETE",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel
+ "OPTIONS",
+ "TRACE",
+ "COPY",
+ "LOCK",
+ "MKCOL",
+ "MOVE",
+ "PROPFIND",
+ "PROPPATCH",
+ "SEARCH",
+ "UNLOCK",
+ "BIND",
+ "REBIND",
+ "UNBIND",
+ "ACL",
+ "REPORT",
+ "MKACTIVITY",
+ "CHECKOUT",
+ "MERGE",
+ "M-SEARCH",
+ "NOTIFY",
+ "SUBSCRIBE",
+ "UNSUBSCRIBE",
+ "PATCH",
+ "PURGE",
+ "MKCALENDAR",
+ "LINK",
+ "UNLINK",
+ 0 };
+ const char **this_method;
+ for (this_method = all_methods; *this_method; this_method++) {
+ char buf[200];
+ sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+ test_simple(buf, HPE_OK);
+ }
+
+ static const char *bad_methods[] = {
+ "ASDF",
+ "C******",
+ "COLA",
+ "GEM",
+ "GETA",
+ "M****",
+ "MKCOLA",
+ "PROPPATCHA",
+ "PUN",
+ "PX",
+ "SA",
+ "hello world",
+ 0 };
+ for (this_method = bad_methods; *this_method; this_method++) {
+ char buf[200];
+ sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+ test_simple(buf, HPE_INVALID_METHOD);
+ }
+
+ // illegal header field name line folding
+ test_simple("GET / HTTP/1.1\r\n"
+ "name\r\n"
+ " : value\r\n"
+ "\r\n",
+ HPE_INVALID_HEADER_TOKEN);
+
+ const char *dumbfuck2 =
+ "GET / HTTP/1.1\r\n"
+ "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
+ "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
+ "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
+ "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
+ "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
+ "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
+ "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
+ "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
+ "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
+ "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
+ "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
+ "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
+ "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
+ "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
+ "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
+ "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
+ "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
+ "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
+ "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
+ "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
+ "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
+ "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
+ "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
+ "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
+ "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
+ "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
+ "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
+ "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
+ "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
+ "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
+ "\tRA==\r\n"
+ "\t-----END CERTIFICATE-----\r\n"
+ "\r\n";
+ test_simple(dumbfuck2, HPE_OK);
+
+ const char *corrupted_connection =
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection\r\033\065\325eep-Alive\r\n"
+ "Accept-Encoding: gzip\r\n"
+ "\r\n";
+ test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN);
+
+ const char *corrupted_header_name =
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "X-Some-Header\r\033\065\325eep-Alive\r\n"
+ "Accept-Encoding: gzip\r\n"
+ "\r\n";
+ test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN);
+
+#if 0
+ // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
+ // until EOF.
+ //
+ // no content-length
+ // error if there is a body without content length
+ const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
+ "Accept: */*\r\n"
+ "\r\n"
+ "HELLO";
+ test_simple(bad_get_no_headers_no_body, 0);
+#endif
+ /* TODO sending junk and large headers gets rejected */
+
+
+ /* check to make sure our predefined requests are okay */
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
+ test_message(&requests[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
+ test_message_pause(&requests[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(requests); i++) {
+ if (!requests[i].should_keep_alive) continue;
+ for (j = 0; j < ARRAY_SIZE(requests); j++) {
+ if (!requests[j].should_keep_alive) continue;
+ for (k = 0; k < ARRAY_SIZE(requests); k++) {
+ test_multiple3(&requests[i], &requests[j], &requests[k]);
+ }
+ }
+ }
+
+ printf("request scan 1/4 ");
+ test_scan( &requests[GET_NO_HEADERS_NO_BODY]
+ , &requests[GET_ONE_HEADER_NO_BODY]
+ , &requests[GET_NO_HEADERS_NO_BODY]
+ );
+
+ printf("request scan 2/4 ");
+ test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
+ , &requests[POST_IDENTITY_BODY_WORLD]
+ , &requests[GET_FUNKY_CONTENT_LENGTH]
+ );
+
+ printf("request scan 3/4 ");
+ test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
+ , &requests[CHUNKED_W_TRAILING_HEADERS]
+ , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
+ );
+
+ printf("request scan 4/4 ");
+ test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]
+ , &requests[PREFIX_NEWLINE_GET ]
+ , &requests[CONNECT_REQUEST]
+ );
+
+ puts("requests okay");
+
+ return 0;
+}
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/http_parser.rb.gemspec b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/http_parser.rb.gemspec
new file mode 100644
index 0000000..2125a27
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/http_parser.rb.gemspec
@@ -0,0 +1,32 @@
+Gem::Specification.new do |s|
+ s.name = "http_parser.rb"
+ s.version = "0.8.1"
+ s.summary = "Simple callback-based HTTP request/response parser"
+ s.description = "Ruby bindings to https://github.com/joyent/http-parser and https://github.com/http-parser/http-parser.java"
+
+ s.authors = ["Marc-Andre Cournoyer", "Aman Gupta"]
+ s.email = ["macournoyer@gmail.com", "aman@tmm1.net"]
+ s.license = 'MIT'
+
+ s.homepage = "https://github.com/tmm1/http_parser.rb"
+ s.files = `git ls-files`.split("\n").grep_v(%r{spec/}) + Dir['ext/ruby_http_parser/vendor/**/*']
+
+ s.require_paths = ["lib"]
+ s.extensions = ["ext/ruby_http_parser/extconf.rb"]
+
+ s.add_development_dependency 'rake-compiler', '~> 1.0'
+ s.add_development_dependency 'rspec', '~> 3'
+ s.add_development_dependency 'json', '~> 2.1'
+ s.add_development_dependency 'benchmark_suite', '~> 1.0'
+ s.add_development_dependency 'ffi', '~> 1.9'
+
+ if RUBY_PLATFORM =~ /java/
+ s.add_development_dependency 'jruby-openssl'
+ else
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
+ s.add_development_dependency 'yajl-ruby', '~> 1.3'
+ else
+ s.add_development_dependency 'yajl-ruby', '= 1.2.1'
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http/parser.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http/parser.rb
new file mode 100644
index 0000000..4881b03
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http/parser.rb
@@ -0,0 +1 @@
+require 'http_parser'
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http_parser.rb b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http_parser.rb
new file mode 100644
index 0000000..c69f7a0
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/lib/http_parser.rb
@@ -0,0 +1,21 @@
+$:.unshift File.expand_path('../', __FILE__)
+require 'ruby_http_parser'
+
+Http = HTTP
+
+module HTTP
+ class Parser
+ class << self
+ attr_reader :default_header_value_type
+
+ def default_header_value_type=(val)
+ if (val != :mixed && val != :strings && val != :arrays)
+ raise ArgumentError, "Invalid header value type"
+ end
+ @default_header_value_type = val
+ end
+ end
+ end
+end
+
+HTTP::Parser.default_header_value_type = :mixed
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/compile.rake b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/compile.rake
new file mode 100644
index 0000000..3345fbe
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/compile.rake
@@ -0,0 +1,42 @@
+require 'rubygems/package_task'
+require 'rake/extensiontask'
+require 'rake/javaextensiontask'
+
+def gemspec
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../http_parser.rb.gemspec', __FILE__)))
+end
+
+Gem::PackageTask.new(gemspec) do |pkg|
+end
+
+if RUBY_PLATFORM =~ /java/
+ Rake::JavaExtensionTask.new("ruby_http_parser", gemspec) do |ext|
+ ext.classpath = File.expand_path('../../ext/ruby_http_parser/vendor/http-parser-java/ext/primitives.jar', __FILE__)
+ end
+else
+ Rake::ExtensionTask.new("ruby_http_parser", gemspec) do |ext|
+ unless RUBY_PLATFORM =~ /mswin|mingw/
+ ext.cross_compile = true
+ ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
+
+ # inject 1.8/1.9 pure-ruby entry point
+ ext.cross_compiling do |spec|
+ spec.files += ['lib/ruby_http_parser.rb']
+ end
+ end
+ end
+end
+
+file 'lib/ruby_http_parser.rb' do |t|
+ File.open(t.name, 'wb') do |f|
+ f.write <<-eoruby
+RUBY_VERSION =~ /(\\d+.\\d+)/
+require "\#{$1}/ruby_http_parser"
+ eoruby
+ end
+ at_exit{ FileUtils.rm t.name if File.exist?(t.name) }
+end
+
+if Rake::Task.task_defined?(:cross)
+ task :cross => 'lib/ruby_http_parser.rb'
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/fixtures.rake b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/fixtures.rake
new file mode 100644
index 0000000..b5d36ef
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/fixtures.rake
@@ -0,0 +1,71 @@
+desc "Generate test fixtures"
+task :fixtures => :submodules do
+ require 'yajl'
+ data = File.read File.expand_path('../../ext/ruby_http_parser/vendor/http-parser/test.c', __FILE__)
+
+ %w[ requests responses ].each do |type|
+ # find test definitions in between requests/responses[]= and .name=NULL
+ tmp = data[/#{type}\[\]\s*=(.+?),\s*\{\s*\.name=\s*NULL/m, 1]
+
+ # replace first { with a [ (parsing an array of test cases)
+ tmp.sub!('{','[')
+
+ # replace booleans
+ tmp.gsub!('TRUE', 'true')
+ tmp.gsub!('FALSE', 'false')
+
+ # mark strict mode tests
+ tmp.gsub!(%r|#if\s+!HTTP_PARSER_STRICT(.+?)#endif\s*/\*\s*!HTTP_PARSER_STRICT.+\n|m){
+ $1.gsub(/^(.+,\.type= .+)$/, "\\1\n, .strict= false")
+ }
+
+ # remove macros and comments
+ tmp.gsub!(/^#(if|elif|endif|define).+$/,'')
+ tmp.gsub!(/\/\*(.+?)\*\/$/,'')
+
+ # HTTP_* enums become strings
+ tmp.gsub!(/(= )(HTTP_\w+)/){
+ "#{$1}#{$2.sub('MSEARCH','M-SEARCH').dump}"
+ }
+
+ # join multiline strings for body and raw data
+ tmp.gsub!(/((body|raw)\s*=)(.+?)(\n\s+[\},])/m){
+ before, after = $1, $4
+ raw = $3.split("\n").map{ |l| l.strip[1..-2] }.join('')
+ "#{before} \"#{raw}\" #{after}"
+ }
+
+ # make headers an array of array tuples
+ tmp.gsub!(/(\.headers\s*=)(.+?)(\s*,\.)/m){
+ before, after = $1, $3
+ raw = $2.gsub('{', '[').gsub('}', ']')
+ "#{before} #{raw} #{after}"
+ }
+
+ # .name= becomes "name":
+ tmp.gsub!(/^(.{2,5})\.(\w+)\s*=/){
+ "#{$1}#{$2.dump}: "
+ }
+
+ # evaluate addition expressions
+ tmp.gsub!(/(body_size\":\s*)(\d+)\+(\d+)/){
+ "#{$1}#{$2.to_i+$3.to_i}"
+ }
+
+ # end result array
+ tmp << ']'
+
+ # normalize data
+ results = Yajl.load(tmp, :symbolize_keys => true)
+ results.map{ |res|
+ res[:headers] and res[:headers] = Hash[*res[:headers].flatten]
+ res[:method] and res[:method].gsub!(/^HTTP_/, '')
+ res[:strict] = true unless res.has_key?(:strict)
+ }
+
+ # write to a file
+ File.open("spec/support/#{type}.json", 'w'){ |f|
+ f.write Yajl.dump(results, :pretty => true)
+ }
+ end
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/spec.rake b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/spec.rake
new file mode 100644
index 0000000..3deb568
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/spec.rake
@@ -0,0 +1,5 @@
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new do |t|
+ t.rspec_opts = %w(--format documentation --color)
+end
diff --git a/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/submodules.rake b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/submodules.rake
new file mode 100644
index 0000000..d978e9f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/http_parser.rb-0.8.1/tasks/submodules.rake
@@ -0,0 +1,7 @@
+desc "Fetch upstream submodules"
+task :submodules do
+ if Dir['ext/ruby_http_parser/vendor/http-parser/*'].empty?
+ sh 'git submodule init'
+ sh 'git submodule update'
+ end
+end