h1p 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4fb3573e65df46350986981759a454c8bc2c4166901a46dd827fdc17a27b08b5
4
+ data.tar.gz: afc10ead82e3286f61fa2bee1739499f275b75a386ed3e3401988805470c9b3a
5
+ SHA512:
6
+ metadata.gz: 72ba304cda888fcfb1c8afaee1a03b94ddb15fde0110c303886e4036125fb5b464d214ebb731ea91dac792070c9cc3d5eccb789f667c8a590fae01afb88d5665
7
+ data.tar.gz: 31cd6c6d7bb6695d338596b56be808cd1fed99cc76f11ee5f38fbdf7ff5d1206960a7134932ac82e9ca9c19116a20ee99db8f2a002fb76c1997134ac4d432dae
@@ -0,0 +1,31 @@
1
+ name: Tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os: [ubuntu-latest]
11
+ ruby: [2.6, 2.7, 3.0]
12
+
13
+ name: >-
14
+ ${{matrix.os}}, ${{matrix.ruby}}
15
+
16
+ runs-on: ${{matrix.os}}
17
+ steps:
18
+ - uses: actions/checkout@v1
19
+ - uses: actions/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{matrix.ruby}}
22
+ - name: Install dependencies
23
+ run: |
24
+ gem install bundler
25
+ bundle install
26
+ - name: Show Linux kernel version
27
+ run: uname -r
28
+ - name: Compile C-extension
29
+ run: bundle exec rake compile
30
+ - name: Run tests
31
+ run: bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,57 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+ lib/*.so
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## 0.42 2021-08-16
2
+
3
+ ## 0.1 2021-08-19
4
+
5
+ - Import code from Tipi
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ h1p (0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ builder (3.2.4)
11
+ minitest (5.11.3)
12
+ minitest-reporters (1.4.3)
13
+ ansi
14
+ builder
15
+ minitest (>= 5.0)
16
+ ruby-progressbar
17
+ rake (12.3.3)
18
+ rake-compiler (1.1.1)
19
+ rake
20
+ ruby-progressbar (1.11.0)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ h1p!
27
+ minitest (~> 5.11.3)
28
+ minitest-reporters (~> 1.4.2)
29
+ rake (~> 12.3.3)
30
+ rake-compiler (= 1.1.1)
31
+
32
+ BUNDLED WITH
33
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Digital Fabric
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # h1p
2
+
3
+ h1p
4
+
5
+ HTTP 1.1 parser for Ruby
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/clean"
5
+
6
+ require "rake/extensiontask"
7
+ Rake::ExtensionTask.new("h1p_ext") do |ext|
8
+ ext.ext_dir = "ext/h1p"
9
+ end
10
+
11
+ task :recompile => [:clean, :compile]
12
+ task :default => [:compile, :test]
13
+
14
+ task :test do
15
+ exec 'ruby test/test_h1p.rb'
16
+ end
data/TODO.md ADDED
@@ -0,0 +1,106 @@
1
+ ## Add an API for reading a request body chunk into an IO (pipe)
2
+
3
+ ```ruby
4
+ # currently
5
+ chunk = req.next_chunk
6
+ # or
7
+ req.each_chunk { |c| do_something(c) }
8
+
9
+ # what we'd like to do
10
+ r, w = IO.pipe
11
+ len = req.splice_chunk(w)
12
+ sock << "Here comes a chunk of #{len} bytes\n"
13
+ sock.splice(r, len)
14
+
15
+ # or:
16
+ r, w = IO.pipe
17
+ req.splice_each_chunk(w) do |len|
18
+ sock << "Here comes a chunk of #{len} bytes\n"
19
+ sock.splice(r, len)
20
+ end
21
+ ```
22
+
23
+ # HTTP/1.1 parser
24
+
25
+ - httparser.rb is not actively updated
26
+ - the httparser.rb C parser code comes originally from https://github.com/nodejs/llhttp
27
+ - there's a Ruby gem https://github.com/metabahn/llhttp, but its API is too low-level
28
+ (lots of callbacks, headers need to be retained across callbacks)
29
+ - the basic idea is to import the C-code, then build a parser object with the following
30
+ callbacks:
31
+
32
+ ```ruby
33
+ on_headers_complete(headers)
34
+ on_body_chunk(chunk)
35
+ on_message_complete
36
+ ```
37
+
38
+ - The llhttp gem's C-code is here: https://github.com/metabahn/llhttp/tree/main/mri
39
+
40
+ - Actually, if you do a C extension, instead of a callback-based API, we can
41
+ design a blocking API:
42
+
43
+ ```ruby
44
+ parser = Tipi::HTTP1::Parser.new
45
+ parser.each_request(socket) do |headers|
46
+ request = Request.new(normalize_headers(headers))
47
+ handle_request(request)
48
+ end
49
+ ```
50
+
51
+ # What about HTTP/2?
52
+
53
+ It would be a nice exercise in converting a callback-based API to a blocking
54
+ one:
55
+
56
+ ```ruby
57
+ parser = Tipi::HTTP2::Parser.new(socket)
58
+ parser.each_stream(socket) do |stream|
59
+ spin { handle_stream(stream) }
60
+ end
61
+ ```
62
+
63
+
64
+
65
+ # DF
66
+
67
+ - Add attack protection for IP-address HTTP host:
68
+
69
+ ```ruby
70
+ IPV4_REGEXP = /^\d+\.\d+\.\d+\.\d+$/.freeze
71
+
72
+ def is_attack_request?(req)
73
+ return true if req.host =~ IPV4_REGEXP && req.query[:q] != 'ping'
74
+ end
75
+ ```
76
+
77
+ - Add attack route to Qeweney routing API
78
+
79
+
80
+
81
+ # Roadmap
82
+
83
+ - Update README (get rid of non-http stuff)
84
+ - Improve Rack spec compliance, add tests
85
+ - Homogenize HTTP 1 and HTTP 2 headers - downcase symbols
86
+
87
+ - Use `http-2-next` instead of `http-2` for http/2
88
+ - https://gitlab.com/honeyryderchuck/http-2-next
89
+ - Open an issue there, ask what's the difference between the two gems?
90
+
91
+ ## 0.38
92
+
93
+ - Add more poly CLI commands and options:
94
+
95
+ - serve static files from given directory
96
+ - serve from rack up file
97
+ - serve both http and https
98
+ - use custom certificate files for SSL
99
+ - set host address to bind to
100
+ - set port to bind to
101
+ - set forking process count
102
+
103
+ ## 0.39 Working Sinatra application
104
+
105
+ - app with database access (postgresql)
106
+ - benchmarks!
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
6
+
7
+ def measure_time_and_allocs
8
+ 4.times { GC.start }
9
+ GC.disable
10
+
11
+ t0 = Time.now
12
+ a0 = object_count
13
+ yield
14
+ t1 = Time.now
15
+ a1 = object_count
16
+ [t1 - t0, a1 - a0]
17
+ ensure
18
+ GC.enable
19
+ end
20
+
21
+ def object_count
22
+ count = ObjectSpace.count_objects
23
+ count[:TOTAL] - count[:FREE]
24
+ end
25
+
26
+ def benchmark_other_http1_parser(iterations)
27
+ STDOUT << "http_parser.rb: "
28
+ require 'http_parser.rb'
29
+
30
+ i, o = IO.pipe
31
+ parser = Http::Parser.new
32
+ done = false
33
+ headers = nil
34
+ parser.on_headers_complete = proc do |h|
35
+ headers = h
36
+ headers[':method'] = parser.http_method
37
+ headers[':path'] = parser.request_url
38
+ end
39
+ parser.on_message_complete = proc { done = true }
40
+
41
+ elapsed, allocated = measure_time_and_allocs do
42
+ iterations.times do
43
+ o << HTTP_REQUEST
44
+ done = false
45
+ while !done
46
+ msg = i.readpartial(4096)
47
+ parser << msg
48
+ end
49
+ end
50
+ end
51
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
52
+ end
53
+
54
+ def benchmark_tipi_http1_parser(iterations)
55
+ STDOUT << "tipi parser: "
56
+ require_relative '../lib/tipi_ext'
57
+ i, o = IO.pipe
58
+ reader = proc { |len| i.readpartial(len) }
59
+ parser = Tipi::HTTP1Parser.new(reader)
60
+
61
+ elapsed, allocated = measure_time_and_allocs do
62
+ iterations.times do
63
+ o << HTTP_REQUEST
64
+ headers = parser.parse_headers
65
+ end
66
+ end
67
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
68
+ end
69
+
70
+ def fork_benchmark(method, iterations)
71
+ pid = fork do
72
+ send(method, iterations)
73
+ rescue Exception => e
74
+ p e
75
+ p e.backtrace
76
+ exit!
77
+ end
78
+ Process.wait(pid)
79
+ end
80
+
81
+ x = 500000
82
+ # fork_benchmark(:benchmark_other_http1_parser, x)
83
+ # fork_benchmark(:benchmark_tipi_http1_parser, x)
84
+
85
+ benchmark_tipi_http1_parser(x)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ puts "pid: #{Process.pid}"
12
+ puts 'Listening on port 10080...'
13
+
14
+ # GC.disable
15
+ # Thread.current.backend.idle_gc_period = 60
16
+
17
+ spin_loop(interval: 10) { p Thread.backend.stats }
18
+
19
+ spin_loop(interval: 10) do
20
+ GC.compact
21
+ end
22
+
23
+ spin do
24
+ Tipi.serve('0.0.0.0', 10080, opts) do |req|
25
+ if req.path == '/stream'
26
+ req.send_headers('Foo' => 'Bar')
27
+ sleep 1
28
+ req.send_chunk("foo\n")
29
+ sleep 1
30
+ req.send_chunk("bar\n")
31
+ req.finish
32
+ elsif req.path == '/upload'
33
+ body = req.read
34
+ req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
35
+ else
36
+ req.respond("Hello world!\n")
37
+ end
38
+ # p req.transfer_counts
39
+ end
40
+ p 'done...'
41
+ end.await
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'mkmf'
5
+
6
+ $CFLAGS << " -Wno-format-security"
7
+ CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
8
+
9
+ require_relative './limits'
10
+ H1P_LIMITS.each { |k, v| $defs << "-D#{k.upcase}=#{v}" }
11
+
12
+ dir_config 'h1p_ext'
13
+ create_makefile 'h1p_ext'
data/ext/h1p/h1p.c ADDED
@@ -0,0 +1,860 @@
1
+ #include "ruby.h"
2
+ #include "h1p.h"
3
+
4
+ // Security-related limits are defined in limits.rb and injected as
5
+ // defines in extconf.rb
6
+
7
+ #define INITIAL_BUFFER_SIZE 4096
8
+ #define BUFFER_TRIM_MIN_LEN 4096
9
+ #define BUFFER_TRIM_MIN_POS 2048
10
+ #define MAX_HEADERS_READ_LENGTH 4096
11
+ #define MAX_BODY_READ_LENGTH (1 << 20) // 1MB
12
+
13
+ #define BODY_READ_MODE_UNKNOWN -2
14
+ #define BODY_READ_MODE_CHUNKED -1
15
+
16
+ ID ID_arity;
17
+ ID ID_backend_read;
18
+ ID ID_backend_recv;
19
+ ID ID_call;
20
+ ID ID_downcase;
21
+ ID ID_eof_p;
22
+ ID ID_eq;
23
+ ID ID_parser_read_method;
24
+ ID ID_read;
25
+ ID ID_readpartial;
26
+ ID ID_to_i;
27
+
28
+ static VALUE mPolyphony = Qnil;
29
+ static VALUE cError;
30
+
31
+ VALUE NUM_max_headers_read_length;
32
+ VALUE NUM_buffer_start;
33
+ VALUE NUM_buffer_end;
34
+
35
+ VALUE STR_pseudo_method;
36
+ VALUE STR_pseudo_path;
37
+ VALUE STR_pseudo_protocol;
38
+ VALUE STR_pseudo_rx;
39
+
40
+ VALUE STR_chunked;
41
+ VALUE STR_content_length;
42
+ VALUE STR_transfer_encoding;
43
+
44
+ VALUE SYM_backend_read;
45
+ VALUE SYM_backend_recv;
46
+ VALUE SYM_stock_readpartial;
47
+
48
+ enum read_method {
49
+ method_readpartial, // receiver.readpartial(len, buf, pos, raise_on_eof: false) (Polyphony-specific)
50
+ method_backend_read, // Polyphony.backend_read (Polyphony-specific)
51
+ method_backend_recv, // Polyphony.backend_recv (Polyphony-specific)
52
+ method_call, // receiver.call(len) (Universal)
53
+ method_stock_readpartial // receiver.readpartial(len)
54
+ };
55
+
56
+ typedef struct parser {
57
+ VALUE io;
58
+ VALUE buffer;
59
+ VALUE headers;
60
+ int pos;
61
+ int current_request_rx;
62
+
63
+ enum read_method read_method;
64
+ int body_read_mode;
65
+ int body_left;
66
+ int request_completed;
67
+ } Parser_t;
68
+
69
+ VALUE cParser = Qnil;
70
+
71
+ static void Parser_mark(void *ptr) {
72
+ Parser_t *parser = ptr;
73
+ rb_gc_mark(parser->io);
74
+ rb_gc_mark(parser->buffer);
75
+ rb_gc_mark(parser->headers);
76
+ }
77
+
78
+ static void Parser_free(void *ptr) {
79
+ xfree(ptr);
80
+ }
81
+
82
+ static size_t Parser_size(const void *ptr) {
83
+ return sizeof(Parser_t);
84
+ }
85
+
86
+ static const rb_data_type_t Parser_type = {
87
+ "Parser",
88
+ {Parser_mark, Parser_free, Parser_size,},
89
+ 0, 0, 0
90
+ };
91
+
92
+ static VALUE Parser_allocate(VALUE klass) {
93
+ Parser_t *parser;
94
+
95
+ parser = ALLOC(Parser_t);
96
+ return TypedData_Wrap_Struct(klass, &Parser_type, parser);
97
+ }
98
+
99
+ #define GetParser(obj, parser) \
100
+ TypedData_Get_Struct((obj), Parser_t, &Parser_type, (parser))
101
+
102
+ static inline void get_polyphony() {
103
+ if (mPolyphony != Qnil) return;
104
+
105
+ mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
106
+ rb_gc_register_mark_object(mPolyphony);
107
+ }
108
+
109
+ enum read_method detect_read_method(VALUE io) {
110
+ if (rb_respond_to(io, ID_parser_read_method)) {
111
+ VALUE method = rb_funcall(io, ID_parser_read_method, 0);
112
+ if (method == SYM_stock_readpartial) return method_stock_readpartial;
113
+
114
+ get_polyphony();
115
+ if (method == SYM_backend_read) return method_backend_read;
116
+ if (method == SYM_backend_recv) return method_backend_recv;
117
+
118
+ return method_readpartial;
119
+ }
120
+ else if (rb_respond_to(io, ID_call)) {
121
+ return method_call;
122
+ }
123
+ else
124
+ rb_raise(rb_eRuntimeError, "Provided reader should be a callable or respond to #__parser_read_method__");
125
+ }
126
+
127
+ VALUE Parser_initialize(VALUE self, VALUE io) {
128
+ Parser_t *parser;
129
+ GetParser(self, parser);
130
+
131
+ parser->io = io;
132
+ parser->buffer = rb_str_new_literal("");
133
+ parser->headers = Qnil;
134
+ parser->pos = 0;
135
+
136
+ // pre-allocate the buffer
137
+ rb_str_modify_expand(parser->buffer, INITIAL_BUFFER_SIZE);
138
+
139
+ parser->read_method = detect_read_method(io);
140
+ parser->body_read_mode = BODY_READ_MODE_UNKNOWN;
141
+ parser->body_left = 0;
142
+ return self;
143
+ }
144
+
145
+ ////////////////////////////////////////////////////////////////////////////////
146
+
147
+ #define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
148
+
149
+ #define FILL_BUFFER_OR_GOTO_EOF(state) { if (!fill_buffer(state)) goto eof; }
150
+
151
+ #define BUFFER_POS(state) ((state)->parser->pos)
152
+ #define BUFFER_LEN(state) ((state)->len)
153
+ #define BUFFER_CUR(state) ((state)->ptr[(state)->parser->pos])
154
+ #define BUFFER_AT(state, pos) ((state)->ptr[pos])
155
+ #define BUFFER_PTR(state, pos) ((state)->ptr + pos)
156
+ #define BUFFER_STR(state, pos, len) (rb_obj_freeze(rb_utf8_str_new((state)->ptr + pos, len)))
157
+ #define BUFFER_STR_DOWNCASE(state, pos, len) (rb_obj_freeze(str_downcase(rb_utf8_str_new((state)->ptr + pos, len))))
158
+
159
+ #define INC_BUFFER_POS(state) { \
160
+ BUFFER_POS(state)++; \
161
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
162
+ }
163
+
164
+ #define INC_BUFFER_POS_NO_FILL(state) BUFFER_POS(state)++;
165
+
166
+ #define INC_BUFFER_POS_UTF8(state, len) { \
167
+ unsigned char c = BUFFER_CUR(state); \
168
+ if ((c & 0xf0) == 0xf0) { \
169
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 4) FILL_BUFFER_OR_GOTO_EOF(state); \
170
+ BUFFER_POS(state) += 4; \
171
+ len += 4; \
172
+ } \
173
+ else if ((c & 0xe0) == 0xe0) { \
174
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 3) FILL_BUFFER_OR_GOTO_EOF(state); \
175
+ BUFFER_POS(state) += 3; \
176
+ len += 3; \
177
+ } \
178
+ else if ((c & 0xc0) == 0xc0) { \
179
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 2) FILL_BUFFER_OR_GOTO_EOF(state); \
180
+ BUFFER_POS(state) += 2; \
181
+ len += 2; \
182
+ } \
183
+ else { \
184
+ BUFFER_POS(state)++; \
185
+ len ++; \
186
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
187
+ } \
188
+ }
189
+
190
+ #define INIT_PARSER_STATE(state) { \
191
+ (state)->len = RSTRING_LEN((state)->parser->buffer); \
192
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) \
193
+ FILL_BUFFER_OR_GOTO_EOF(state) \
194
+ else \
195
+ (state)->ptr = RSTRING_PTR((state)->parser->buffer); \
196
+ }
197
+
198
+ #define RAISE_BAD_REQUEST(msg) rb_raise(cError, msg)
199
+
200
+ #define SET_HEADER_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
201
+ VALUE value = BUFFER_STR(state, pos, len); \
202
+ rb_hash_aset(headers, key, value); \
203
+ RB_GC_GUARD(value); \
204
+ }
205
+
206
+ #define SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
207
+ VALUE value = BUFFER_STR_DOWNCASE(state, pos, len); \
208
+ rb_hash_aset(headers, key, value); \
209
+ RB_GC_GUARD(value); \
210
+ }
211
+
212
+ #define CONSUME_CRLF(state) { \
213
+ INC_BUFFER_POS(state); \
214
+ if (BUFFER_CUR(state) != '\n') goto bad_request; \
215
+ INC_BUFFER_POS(state); \
216
+ }
217
+
218
+ #define CONSUME_CRLF_NO_FILL(state) { \
219
+ INC_BUFFER_POS(state); \
220
+ if (BUFFER_CUR(state) != '\n') goto bad_request; \
221
+ INC_BUFFER_POS_NO_FILL(state); \
222
+ }
223
+
224
+ #define GLOBAL_STR(v, s) v = rb_str_new_literal(s); rb_global_variable(&v); rb_obj_freeze(v)
225
+
226
+ struct parser_state {
227
+ struct parser *parser;
228
+ char *ptr;
229
+ int len;
230
+ };
231
+
232
+ ////////////////////////////////////////////////////////////////////////////////
233
+
234
+ static inline VALUE io_call(VALUE io, VALUE maxlen, VALUE buf, VALUE buf_pos) {
235
+ VALUE result = rb_funcall(io, ID_call, 1, maxlen);
236
+ if (result == Qnil) return Qnil;
237
+
238
+ if (buf_pos == NUM_buffer_start) rb_str_set_len(buf, 0);
239
+ rb_str_append(buf, result);
240
+ RB_GC_GUARD(result);
241
+ return buf;
242
+ }
243
+
244
+ static inline VALUE io_stock_readpartial(VALUE io, VALUE maxlen, VALUE buf, VALUE buf_pos) {
245
+ VALUE eof = rb_funcall(io, ID_eof_p, 0);
246
+ if (RTEST(eof)) return Qnil;
247
+
248
+ VALUE result = rb_funcall(io, ID_readpartial, 1, maxlen);
249
+ if (result == Qnil) return Qnil;
250
+ if (buf == Qnil) return result;
251
+
252
+ if (buf_pos == NUM_buffer_start) rb_str_set_len(buf, 0);
253
+ rb_str_append(buf, result);
254
+ RB_GC_GUARD(result);
255
+ return buf;
256
+ }
257
+
258
+ static inline VALUE parser_io_read(Parser_t *parser, VALUE maxlen, VALUE buf, VALUE buf_pos) {
259
+ switch (parser->read_method) {
260
+ case method_backend_read:
261
+ return rb_funcall(mPolyphony, ID_backend_read, 5, parser->io, buf, maxlen, Qfalse, buf_pos);
262
+ case method_backend_recv:
263
+ return rb_funcall(mPolyphony, ID_backend_recv, 4, parser->io, buf, maxlen, buf_pos);
264
+ case method_readpartial:
265
+ return rb_funcall(parser-> io, ID_readpartial, 4, maxlen, buf, buf_pos, Qfalse);
266
+ case method_call:
267
+ return io_call(parser ->io, maxlen, buf, buf_pos);
268
+ case method_stock_readpartial:
269
+ return io_stock_readpartial(parser->io, maxlen, buf, buf_pos);
270
+ default:
271
+ return Qnil;
272
+ }
273
+ }
274
+
275
+ static inline int fill_buffer(struct parser_state *state) {
276
+ VALUE ret = parser_io_read(state->parser, NUM_max_headers_read_length, state->parser->buffer, NUM_buffer_end);
277
+ if (ret == Qnil) return 0;
278
+
279
+ state->parser->buffer = ret;
280
+ int len = RSTRING_LEN(state->parser->buffer);
281
+ int read_bytes = len - state->len;
282
+ if (!read_bytes) return 0;
283
+
284
+ state->ptr = RSTRING_PTR(state->parser->buffer);
285
+ state->len = len;
286
+ return read_bytes;
287
+ }
288
+
289
+ static inline void buffer_trim(struct parser_state *state) {
290
+ int len = RSTRING_LEN(state->parser->buffer);
291
+ int pos = state->parser->pos;
292
+ int left = len - pos;
293
+
294
+ // The buffer is trimmed only if length and position thresholds are passed,
295
+ // *and* position is past the halfway point.
296
+ if (len < BUFFER_TRIM_MIN_LEN ||
297
+ pos < BUFFER_TRIM_MIN_POS ||
298
+ left >= pos) return;
299
+
300
+ if (left > 0) {
301
+ char *ptr = RSTRING_PTR(state->parser->buffer);
302
+ memcpy(ptr, ptr + pos, left);
303
+ }
304
+ rb_str_set_len(state->parser->buffer, left);
305
+ state->parser->pos = 0;
306
+ }
307
+
308
+ static inline void str_append_from_buffer(VALUE str, char *ptr, int len) {
309
+ int str_len = RSTRING_LEN(str);
310
+ rb_str_modify_expand(str, len);
311
+ memcpy(RSTRING_PTR(str) + str_len, ptr, len);
312
+ rb_str_set_len(str, str_len + len);
313
+ }
314
+
315
+ ////////////////////////////////////////////////////////////////////////////////
316
+
317
+ static inline int parse_method(struct parser_state *state, VALUE headers) {
318
+ int pos = BUFFER_POS(state);
319
+ int len = 0;
320
+
321
+ while (1) {
322
+ switch (BUFFER_CUR(state)) {
323
+ case ' ':
324
+ if (len < 1 || len > MAX_METHOD_LENGTH) goto bad_request;
325
+ INC_BUFFER_POS(state);
326
+ goto done;
327
+ case '\r':
328
+ case '\n':
329
+ goto bad_request;
330
+ default:
331
+ INC_BUFFER_POS(state);
332
+ len++;
333
+ // INC_BUFFER_POS_UTF8(state, len);
334
+ if (len > MAX_METHOD_LENGTH) goto bad_request;
335
+ }
336
+ }
337
+ done:
338
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
339
+ return 1;
340
+ bad_request:
341
+ RAISE_BAD_REQUEST("Invalid method");
342
+ eof:
343
+ return 0;
344
+ }
345
+
346
+ static int parse_request_target(struct parser_state *state, VALUE headers) {
347
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
348
+ int pos = BUFFER_POS(state);
349
+ int len = 0;
350
+ while (1) {
351
+ switch (BUFFER_CUR(state)) {
352
+ case ' ':
353
+ if (len < 1 || len > MAX_PATH_LENGTH) goto bad_request;
354
+ INC_BUFFER_POS(state);
355
+ goto done;
356
+ case '\r':
357
+ case '\n':
358
+ goto bad_request;
359
+ default:
360
+ INC_BUFFER_POS(state);
361
+ len++;
362
+ // INC_BUFFER_POS_UTF8(state, len);
363
+ if (len > MAX_PATH_LENGTH) goto bad_request;
364
+ }
365
+ }
366
+ done:
367
+ SET_HEADER_VALUE_FROM_BUFFER(state, headers, STR_pseudo_path, pos, len);
368
+ return 1;
369
+ bad_request:
370
+ RAISE_BAD_REQUEST("Invalid request target");
371
+ eof:
372
+ return 0;
373
+ }
374
+
375
+ // case-insensitive compare
376
+ #define CMP_CI(state, down, up) ((BUFFER_CUR(state) == down) || (BUFFER_CUR(state) == up))
377
+
378
+ static int parse_protocol(struct parser_state *state, VALUE headers) {
379
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
380
+ int pos = BUFFER_POS(state);
381
+ int len = 0;
382
+
383
+ if (CMP_CI(state, 'H', 'h')) INC_BUFFER_POS(state) else goto bad_request;
384
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
385
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
386
+ if (CMP_CI(state, 'P', 'p')) INC_BUFFER_POS(state) else goto bad_request;
387
+ if (BUFFER_CUR(state) == '/') INC_BUFFER_POS(state) else goto bad_request;
388
+ if (BUFFER_CUR(state) == '1') INC_BUFFER_POS(state) else goto bad_request;
389
+ len = 6;
390
+ while (1) {
391
+ switch (BUFFER_CUR(state)) {
392
+ case '\r':
393
+ CONSUME_CRLF(state);
394
+ goto done;
395
+ case '\n':
396
+ INC_BUFFER_POS(state);
397
+ goto done;
398
+ case '.':
399
+ INC_BUFFER_POS(state);
400
+ char c = BUFFER_CUR(state);
401
+ if (c == '0' || c == '1') {
402
+ INC_BUFFER_POS(state);
403
+ len += 2;
404
+ continue;
405
+ }
406
+ goto bad_request;
407
+ default:
408
+ goto bad_request;
409
+ }
410
+ }
411
+ done:
412
+ if (len < 6 || len > 8) goto bad_request;
413
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_protocol, pos, len);
414
+ return 1;
415
+ bad_request:
416
+ RAISE_BAD_REQUEST("Invalid protocol");
417
+ eof:
418
+ return 0;
419
+ }
420
+
421
+ int parse_request_line(struct parser_state *state, VALUE headers) {
422
+ if (!parse_method(state, headers)) goto eof;
423
+ if (!parse_request_target(state, headers)) goto eof;
424
+ if (!parse_protocol(state, headers)) goto eof;
425
+
426
+ return 1;
427
+ eof:
428
+ return 0;
429
+ }
430
+
431
+ static inline int parse_header_key(struct parser_state *state, VALUE *key) {
432
+ int pos = BUFFER_POS(state);
433
+ int len = 0;
434
+
435
+ while (1) {
436
+ switch (BUFFER_CUR(state)) {
437
+ case ' ':
438
+ goto bad_request;
439
+ case ':':
440
+ if (len < 1 || len > MAX_HEADER_KEY_LENGTH)
441
+ goto bad_request;
442
+ INC_BUFFER_POS(state);
443
+ goto done;
444
+ case '\r':
445
+ if (BUFFER_POS(state) > pos) goto bad_request;
446
+ CONSUME_CRLF_NO_FILL(state);
447
+ goto done;
448
+ case '\n':
449
+ if (BUFFER_POS(state) > pos) goto bad_request;
450
+
451
+ INC_BUFFER_POS_NO_FILL(state);
452
+ goto done;
453
+ default:
454
+ INC_BUFFER_POS(state);
455
+ len++;
456
+ // INC_BUFFER_POS_UTF8(state, len);
457
+ if (len > MAX_HEADER_KEY_LENGTH) goto bad_request;
458
+ }
459
+ }
460
+ done:
461
+ if (len == 0) return -1;
462
+ (*key) = BUFFER_STR_DOWNCASE(state, pos, len);
463
+ return 1;
464
+ bad_request:
465
+ RAISE_BAD_REQUEST("Invalid header key");
466
+ eof:
467
+ return 0;
468
+ }
469
+
470
+ static inline int parse_header_value(struct parser_state *state, VALUE *value) {
471
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
472
+
473
+ int pos = BUFFER_POS(state);
474
+ int len = 0;
475
+
476
+ while (1) {
477
+ switch (BUFFER_CUR(state)) {
478
+ case '\r':
479
+ CONSUME_CRLF(state);
480
+ goto done;
481
+ case '\n':
482
+ INC_BUFFER_POS(state);
483
+ goto done;
484
+ default:
485
+ INC_BUFFER_POS_UTF8(state, len);
486
+ if (len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
487
+ }
488
+ }
489
+ done:
490
+ if (len < 1 || len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
491
+ (*value) = BUFFER_STR(state, pos, len);
492
+ return 1;
493
+ bad_request:
494
+ RAISE_BAD_REQUEST("Invalid header value");
495
+ eof:
496
+ return 0;
497
+ }
498
+
499
+ static inline int parse_header(struct parser_state *state, VALUE headers) {
500
+ VALUE key, value;
501
+
502
+ switch (parse_header_key(state, &key)) {
503
+ case -1: return -1;
504
+ case 0: goto eof;
505
+ }
506
+
507
+ if (!parse_header_value(state, &value)) goto eof;
508
+
509
+ VALUE existing = rb_hash_aref(headers, key);
510
+ if (existing != Qnil) {
511
+ if (TYPE(existing) != T_ARRAY) {
512
+ existing = rb_ary_new3(2, existing, value);
513
+ rb_hash_aset(headers, key, existing);
514
+ }
515
+ else
516
+ rb_ary_push(existing, value);
517
+ }
518
+ else
519
+ rb_hash_aset(headers, key, value);
520
+
521
+ RB_GC_GUARD(existing);
522
+ RB_GC_GUARD(key);
523
+ RB_GC_GUARD(value);
524
+ return 1;
525
+ eof:
526
+ return 0;
527
+ }
528
+
529
+ VALUE Parser_parse_headers(VALUE self) {
530
+ struct parser_state state;
531
+ GetParser(self, state.parser);
532
+ state.parser->headers = rb_hash_new();
533
+
534
+ buffer_trim(&state);
535
+ int initial_pos = state.parser->pos;
536
+ INIT_PARSER_STATE(&state);
537
+ state.parser->current_request_rx = 0;
538
+
539
+ if (!parse_request_line(&state, state.parser->headers)) goto eof;
540
+
541
+ int header_count = 0;
542
+ while (1) {
543
+ if (header_count > MAX_HEADER_COUNT) RAISE_BAD_REQUEST("Too many headers");
544
+ switch (parse_header(&state, state.parser->headers)) {
545
+ case -1: goto done; // empty header => end of headers
546
+ case 0: goto eof;
547
+ }
548
+ header_count++;
549
+ }
550
+ eof:
551
+ state.parser->headers = Qnil;
552
+ done:
553
+ state.parser->body_read_mode = BODY_READ_MODE_UNKNOWN;
554
+ int read_bytes = BUFFER_POS(&state) - initial_pos;
555
+
556
+ state.parser->current_request_rx += read_bytes;
557
+ if (state.parser->headers != Qnil)
558
+ rb_hash_aset(state.parser->headers, STR_pseudo_rx, INT2NUM(read_bytes));
559
+ return state.parser->headers;
560
+ }
561
+
562
+ ////////////////////////////////////////////////////////////////////////////////
563
+
564
+ static inline int str_to_int(VALUE value, const char *error_msg) {
565
+ char *ptr = RSTRING_PTR(value);
566
+ int len = RSTRING_LEN(value);
567
+ int int_value = 0;
568
+
569
+ while (len) {
570
+ char c = *ptr;
571
+ if ((c >= '0') && (c <= '9'))
572
+ int_value = int_value * 10 + (c - '0');
573
+ else
574
+ RAISE_BAD_REQUEST(error_msg);
575
+ len--;
576
+ ptr++;
577
+ }
578
+
579
+ return int_value;
580
+ }
581
+
582
+ VALUE read_body_with_content_length(Parser_t *parser, int read_entire_body, int buffered_only) {
583
+ if (parser->body_left <= 0) return Qnil;
584
+
585
+ VALUE body = Qnil;
586
+
587
+ int len = RSTRING_LEN(parser->buffer);
588
+ int pos = parser->pos;
589
+
590
+ if (pos < len) {
591
+ int available = len - pos;
592
+ if (available > parser->body_left) available = parser->body_left;
593
+ body = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
594
+ parser->pos += available;
595
+ parser->current_request_rx += available;
596
+ parser->body_left -= available;
597
+ if (!parser->body_left) parser->request_completed = 1;
598
+ }
599
+ else {
600
+ body = Qnil;
601
+ len = 0;
602
+ }
603
+ if (buffered_only) return body;
604
+
605
+ while (parser->body_left) {
606
+ int maxlen = parser->body_left <= MAX_BODY_READ_LENGTH ? parser->body_left : MAX_BODY_READ_LENGTH;
607
+ VALUE tmp_buf = parser_io_read(parser, INT2NUM(maxlen), Qnil, NUM_buffer_start);
608
+ if (tmp_buf == Qnil) goto eof;
609
+ if (body != Qnil)
610
+ rb_str_append(body, tmp_buf);
611
+ else
612
+ body = tmp_buf;
613
+ int read_bytes = RSTRING_LEN(tmp_buf);
614
+ parser->current_request_rx += read_bytes;
615
+ parser->body_left -= read_bytes;
616
+ if (!parser->body_left) parser->request_completed = 1;
617
+ RB_GC_GUARD(tmp_buf);
618
+ if (!read_entire_body) goto done;
619
+ }
620
+ done:
621
+ rb_hash_aset(parser->headers, STR_pseudo_rx, INT2NUM(parser->current_request_rx));
622
+ RB_GC_GUARD(body);
623
+ return body;
624
+ eof:
625
+ RAISE_BAD_REQUEST("Incomplete body");
626
+ }
627
+
628
+ int chunked_encoding_p(VALUE transfer_encoding) {
629
+ if (transfer_encoding == Qnil) return 0;
630
+ return rb_funcall(str_downcase(transfer_encoding), ID_eq, 1, STR_chunked) == Qtrue;
631
+ }
632
+
633
+ int parse_chunk_size(struct parser_state *state, int *chunk_size) {
634
+ int len = 0;
635
+ int value = 0;
636
+ int initial_pos = BUFFER_POS(state);
637
+
638
+ while (1) {
639
+ char c = BUFFER_CUR(state);
640
+ if ((c >= '0') && (c <= '9')) value = (value << 4) + (c - '0');
641
+ else if ((c >= 'a') && (c <= 'f')) value = (value << 4) + (c - 'a' + 10);
642
+ else if ((c >= 'A') && (c <= 'F')) value = (value << 4) + (c - 'A' + 10);
643
+ else switch (c) {
644
+ case '\r':
645
+ CONSUME_CRLF_NO_FILL(state);
646
+ goto done;
647
+ case '\n':
648
+ INC_BUFFER_POS_NO_FILL(state);
649
+ goto done;
650
+ default:
651
+ goto bad_request;
652
+ }
653
+ INC_BUFFER_POS(state);
654
+ len++;
655
+ if (len >= MAX_CHUNKED_ENCODING_CHUNK_SIZE_LENGTH) goto bad_request;
656
+ }
657
+ done:
658
+ if (len == 0) goto bad_request;
659
+ (*chunk_size) = value;
660
+ state->parser->current_request_rx += BUFFER_POS(state) - initial_pos;
661
+ return 1;
662
+ bad_request:
663
+ RAISE_BAD_REQUEST("Invalid chunk size");
664
+ eof:
665
+ return 0;
666
+ }
667
+
668
+ int read_body_chunk_with_chunked_encoding(struct parser_state *state, VALUE *body, int chunk_size, int buffered_only) {
669
+ int len = RSTRING_LEN(state->parser->buffer);
670
+ int pos = state->parser->pos;
671
+ int left = chunk_size;
672
+
673
+ if (pos < len) {
674
+ int available = len - pos;
675
+ if (available > left) available = left;
676
+ if (*body != Qnil)
677
+ str_append_from_buffer(*body, RSTRING_PTR(state->parser->buffer) + pos, available);
678
+ else
679
+ *body = rb_str_new(RSTRING_PTR(state->parser->buffer) + pos, available);
680
+ state->parser->pos += available;
681
+ state->parser->current_request_rx += available;
682
+ left -= available;
683
+ }
684
+ if (buffered_only) return 1;
685
+
686
+ while (left) {
687
+ int maxlen = left <= MAX_BODY_READ_LENGTH ? left : MAX_BODY_READ_LENGTH;
688
+
689
+ VALUE tmp_buf = parser_io_read(state->parser, INT2NUM(maxlen), Qnil, NUM_buffer_start);
690
+ if (tmp_buf == Qnil) goto eof;
691
+ if (*body != Qnil)
692
+ rb_str_append(*body, tmp_buf);
693
+ else
694
+ *body = tmp_buf;
695
+ int read_bytes = RSTRING_LEN(tmp_buf);
696
+ state->parser->current_request_rx += read_bytes;
697
+ left -= read_bytes;
698
+ RB_GC_GUARD(tmp_buf);
699
+ }
700
+ return 1;
701
+ eof:
702
+ return 0;
703
+ }
704
+
705
+ static inline int parse_chunk_postfix(struct parser_state *state) {
706
+ int initial_pos = BUFFER_POS(state);
707
+ if (initial_pos == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state);
708
+ switch (BUFFER_CUR(state)) {
709
+ case '\r':
710
+ CONSUME_CRLF_NO_FILL(state);
711
+ goto done;
712
+ case '\n':
713
+ INC_BUFFER_POS_NO_FILL(state);
714
+ goto done;
715
+ default:
716
+ goto bad_request;
717
+ }
718
+ done:
719
+ state->parser->current_request_rx += BUFFER_POS(state) - initial_pos;
720
+ return 1;
721
+ bad_request:
722
+ RAISE_BAD_REQUEST("Invalid protocol");
723
+ eof:
724
+ return 0;
725
+ }
726
+
727
+ VALUE read_body_with_chunked_encoding(Parser_t *parser, int read_entire_body, int buffered_only) {
728
+ struct parser_state state;
729
+ state.parser = parser;
730
+ buffer_trim(&state);
731
+ INIT_PARSER_STATE(&state);
732
+ VALUE body = Qnil;
733
+
734
+ while (1) {
735
+ int chunk_size = 0;
736
+ if (BUFFER_POS(&state) == BUFFER_LEN(&state)) FILL_BUFFER_OR_GOTO_EOF(&state);
737
+ if (!parse_chunk_size(&state, &chunk_size)) goto bad_request;
738
+
739
+ if (chunk_size) {
740
+ if (!read_body_chunk_with_chunked_encoding(&state, &body, chunk_size, buffered_only)) goto bad_request;
741
+ }
742
+ else parser->request_completed = 1;
743
+
744
+ if (!parse_chunk_postfix(&state)) goto bad_request;
745
+ if (!chunk_size || !read_entire_body) goto done;
746
+ }
747
+ bad_request:
748
+ RAISE_BAD_REQUEST("Malformed request body");
749
+ eof:
750
+ RAISE_BAD_REQUEST("Incomplete request body");
751
+ done:
752
+ rb_hash_aset(parser->headers, STR_pseudo_rx, INT2NUM(state.parser->current_request_rx));
753
+ RB_GC_GUARD(body);
754
+ return body;
755
+ }
756
+
757
+ static inline void detect_body_read_mode(Parser_t *parser) {
758
+ VALUE content_length = rb_hash_aref(parser->headers, STR_content_length);
759
+ if (content_length != Qnil) {
760
+ int int_content_length = str_to_int(content_length, "Invalid content length");
761
+ if (int_content_length < 0) RAISE_BAD_REQUEST("Invalid body content length");
762
+ parser->body_read_mode = parser->body_left = int_content_length;
763
+ parser->request_completed = 0;
764
+ return;
765
+ }
766
+
767
+ VALUE transfer_encoding = rb_hash_aref(parser->headers, STR_transfer_encoding);
768
+ if (chunked_encoding_p(transfer_encoding)) {
769
+ parser->body_read_mode = BODY_READ_MODE_CHUNKED;
770
+ parser->request_completed = 0;
771
+ return;
772
+ }
773
+ parser->request_completed = 1;
774
+
775
+ }
776
+
777
+ static inline VALUE read_body(VALUE self, int read_entire_body, int buffered_only) {
778
+ Parser_t *parser;
779
+ GetParser(self, parser);
780
+
781
+ if (parser->body_read_mode == BODY_READ_MODE_UNKNOWN)
782
+ detect_body_read_mode(parser);
783
+
784
+ if (parser->body_read_mode == BODY_READ_MODE_CHUNKED)
785
+ return read_body_with_chunked_encoding(parser, read_entire_body, buffered_only);
786
+ return read_body_with_content_length(parser, read_entire_body, buffered_only);
787
+ }
788
+
789
+ VALUE Parser_read_body(VALUE self) {
790
+ return read_body(self, 1, 0);
791
+ }
792
+
793
+ VALUE Parser_read_body_chunk(VALUE self, VALUE buffered_only) {
794
+ return read_body(self, 0, buffered_only == Qtrue);
795
+ }
796
+
797
+ VALUE Parser_complete_p(VALUE self) {
798
+ Parser_t *parser;
799
+ GetParser(self, parser);
800
+
801
+ if (parser->body_read_mode == BODY_READ_MODE_UNKNOWN)
802
+ detect_body_read_mode(parser);
803
+
804
+ return parser->request_completed ? Qtrue : Qfalse;
805
+ }
806
+
807
+ void Init_H1P() {
808
+ VALUE mH1P;
809
+ VALUE cParser;
810
+
811
+ mH1P = rb_define_module("H1P");
812
+ rb_gc_register_mark_object(mH1P);
813
+ cParser = rb_define_class_under(mH1P, "Parser", rb_cObject);
814
+ rb_define_alloc_func(cParser, Parser_allocate);
815
+
816
+ cError = rb_define_class_under(mH1P, "Error", rb_eRuntimeError);
817
+ rb_gc_register_mark_object(cError);
818
+
819
+ // backend methods
820
+ rb_define_method(cParser, "initialize", Parser_initialize, 1);
821
+ rb_define_method(cParser, "parse_headers", Parser_parse_headers, 0);
822
+ rb_define_method(cParser, "read_body", Parser_read_body, 0);
823
+ rb_define_method(cParser, "read_body_chunk", Parser_read_body_chunk, 1);
824
+ rb_define_method(cParser, "complete?", Parser_complete_p, 0);
825
+
826
+ ID_arity = rb_intern("arity");
827
+ ID_backend_read = rb_intern("backend_read");
828
+ ID_backend_recv = rb_intern("backend_recv");
829
+ ID_call = rb_intern("call");
830
+ ID_downcase = rb_intern("downcase");
831
+ ID_eof_p = rb_intern("eof?");
832
+ ID_eq = rb_intern("==");
833
+ ID_parser_read_method = rb_intern("__parser_read_method__");
834
+ ID_read = rb_intern("read");
835
+ ID_readpartial = rb_intern("readpartial");
836
+ ID_to_i = rb_intern("to_i");
837
+
838
+ NUM_max_headers_read_length = INT2NUM(MAX_HEADERS_READ_LENGTH);
839
+ NUM_buffer_start = INT2NUM(0);
840
+ NUM_buffer_end = INT2NUM(-1);
841
+
842
+ GLOBAL_STR(STR_pseudo_method, ":method");
843
+ GLOBAL_STR(STR_pseudo_path, ":path");
844
+ GLOBAL_STR(STR_pseudo_protocol, ":protocol");
845
+ GLOBAL_STR(STR_pseudo_rx, ":rx");
846
+
847
+ GLOBAL_STR(STR_chunked, "chunked");
848
+ GLOBAL_STR(STR_content_length, "content-length");
849
+ GLOBAL_STR(STR_transfer_encoding, "transfer-encoding");
850
+
851
+ SYM_backend_read = ID2SYM(ID_backend_read);
852
+ SYM_backend_recv = ID2SYM(ID_backend_recv);
853
+ SYM_stock_readpartial = ID2SYM(rb_intern("stock_readpartial"));
854
+
855
+ rb_global_variable(&mH1P);
856
+ }
857
+
858
+ void Init_h1p_ext() {
859
+ Init_H1P();
860
+ }