tipi 0.41 → 0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ ::Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ certificate_db_path = File.expand_path('certificate_store.db', __dir__)
9
+ certificate_store = Tipi::ACME::SQLiteCertificateStore.new(certificate_db_path)
10
+
11
+ Tipi.full_service(
12
+ certificate_store: certificate_store
13
+ ) { |req| req.respond('Hello, world!') }
@@ -31,23 +31,25 @@ f = spin do
31
31
  break unless headers
32
32
  trace headers
33
33
 
34
- body = parser.read_body(headers)
34
+ body = parser.read_body
35
35
  trace "body: #{body ? body.bytesize : 0} bytes"
36
+ trace body if body && body.bytesize < 80
36
37
  end
37
38
  end
38
39
 
39
40
  o << "GET /a HTTP/1.1\r\n\r\n"
40
- o << "GET /b HTTP/1.1\r\n\r\n"
41
+
42
+ # o << "GET /a HTTP/1.1\r\nContent-Length: 0\r\n\r\n"
41
43
 
42
44
  # o << "GET / HTTP/1.1\r\nHost: localhost:10080\r\nUser-Agent: curl/7.74.0\r\nAccept: */*\r\n\r\n"
43
45
 
44
- # o << "post /?q=time&blah=blah HTTP/1\r\nHost: dev.realiteq.net\r\n\r\n"
46
+ o << "post /?q=time&blah=blah HTTP/1\r\nTransfer-Encoding: chunked\r\n\r\na\r\nabcdefghij\r\n0\r\n\r\n"
47
+
48
+ data = " " * 4000000
49
+ o << "get /?q=time HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
45
50
 
46
- # data = " " * 4000000
47
- # o << "get /?q=time HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
51
+ o << "get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
48
52
 
49
- # o << "get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
53
+ o.close
50
54
 
51
- # o.close
52
-
53
55
  f.await
@@ -14,7 +14,7 @@ puts 'Listening on port 10080...'
14
14
  # GC.disable
15
15
  # Thread.current.backend.idle_gc_period = 60
16
16
 
17
- spin_loop(interval: 10) { p Thread.current.fiber_scheduling_stats }
17
+ spin_loop(interval: 10) { p Thread.backend.stats }
18
18
 
19
19
  spin_loop(interval: 10) do
20
20
  GC.compact
@@ -29,6 +29,9 @@ spin do
29
29
  sleep 1
30
30
  req.send_chunk("bar\n")
31
31
  req.finish
32
+ elsif req.path == '/upload'
33
+ body = req.read
34
+ req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
32
35
  else
33
36
  req.respond("Hello world!\n")
34
37
  end
@@ -23,6 +23,9 @@ Tipi.serve('0.0.0.0', 1234, opts) do |req|
23
23
  req.send_chunk("foo\n")
24
24
  sleep 0.5
25
25
  req.send_chunk("bar\n", done: true)
26
+ elsif req.path == '/upload'
27
+ body = req.read
28
+ req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
26
29
  else
27
30
  req.respond("Hello world!\n")
28
31
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'fiber'
5
+
6
+ ctx = OpenSSL::SSL::SSLContext.new
7
+
8
+ f = Fiber.new { |peer| loop { p peer: peer; _name, peer = peer.transfer nil } }
9
+ ctx.servername_cb = proc { |_socket, name|
10
+ p servername_cb: name
11
+ f.transfer([name, Fiber.current]).tap { |r| p result: r }
12
+ }
13
+
14
+ socket = Socket.new(:INET, :STREAM).tap do |s|
15
+ s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
16
+ s.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
17
+ s.bind(Socket.sockaddr_in(12345, '0.0.0.0'))
18
+ s.listen(Socket::SOMAXCONN)
19
+ end
20
+ server = OpenSSL::SSL::SSLServer.new(socket, ctx)
21
+
22
+ Thread.new do
23
+ sleep 0.5
24
+ socket = TCPSocket.new('127.0.0.1', 12345)
25
+ client = OpenSSL::SSL::SSLSocket.new(socket)
26
+ client.hostname = 'example.com'
27
+ p client: client
28
+ client.connect
29
+ rescue => e
30
+ p client_error: e
31
+ end
32
+
33
+ while true
34
+ conn = server.accept
35
+ p accepted: conn
36
+ break
37
+ end
data/ext/tipi/extconf.rb CHANGED
@@ -3,10 +3,11 @@
3
3
  require 'rubygems'
4
4
  require 'mkmf'
5
5
 
6
- $CFLAGS << " -Wno-pointer-arith"
6
+ require_relative '../../security/http1'
7
7
 
8
+ $CFLAGS << " -Wno-format-security"
8
9
  CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
9
-
10
+ Tipi::HTTP1_LIMITS.each { |k, v| $defs << "-D#{k.upcase}=#{v}" }
10
11
 
11
12
  dir_config 'tipi_ext'
12
13
  create_makefile 'tipi_ext'
@@ -1,36 +1,66 @@
1
1
  #include "ruby.h"
2
2
  #include "http1_parser.h"
3
3
 
4
- #define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
4
+ // Security-related limits are defined in security/http1.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
5
12
 
6
- const int MAX_METHOD_LENGTH = 16;
7
- const int MAX_PATH_LENGTH = 1024;
8
- const int MAX_HEADER_KEY_LENGTH = 128;
9
- const int MAX_HEADER_VALUE_LENGTH = 2048;
13
+ #define BODY_READ_MODE_UNKNOWN -2
14
+ #define BODY_READ_MODE_CHUNKED -1
10
15
 
16
+ ID ID_arity;
11
17
  ID ID_backend_read;
18
+ ID ID_backend_recv;
19
+ ID ID_call;
12
20
  ID ID_downcase;
21
+ ID ID_eq;
22
+ ID ID_parser_read_method;
13
23
  ID ID_read;
14
24
  ID ID_readpartial;
15
25
  ID ID_to_i;
16
26
 
17
- VALUE mPolyphony;
18
- VALUE cError;
27
+ static VALUE mPolyphony = Qnil;
28
+ static VALUE cError;
19
29
 
20
- VALUE MAX_READ_LENGTH;
21
- VALUE BUFFER_END;
30
+ VALUE NUM_max_headers_read_length;
31
+ VALUE NUM_buffer_start;
32
+ VALUE NUM_buffer_end;
22
33
 
23
34
  VALUE STR_pseudo_method;
24
35
  VALUE STR_pseudo_path;
25
36
  VALUE STR_pseudo_protocol;
37
+ VALUE STR_pseudo_rx;
26
38
 
39
+ VALUE STR_chunked;
27
40
  VALUE STR_content_length;
28
41
  VALUE STR_transfer_encoding;
29
42
 
43
+ VALUE SYM_backend_read;
44
+ VALUE SYM_backend_recv;
45
+
46
+ enum read_method {
47
+ method_readpartial, // receiver.readpartial (Polyphony-specific)
48
+ method_backend_read, // Polyphony.backend_read (Polyphony-specific)
49
+ method_backend_recv, // Polyphony.backend_recv (Polyphony-specific)
50
+ method_call // receiver.call(len) (Universal)
51
+ };
52
+
30
53
  typedef struct parser {
31
54
  VALUE io;
32
55
  VALUE buffer;
33
- int pos;
56
+ VALUE headers;
57
+ int pos;
58
+ int current_request_rx;
59
+
60
+ enum read_method read_method;
61
+ int body_read_mode;
62
+ int body_left;
63
+ int request_completed;
34
64
  } Parser_t;
35
65
 
36
66
  VALUE cParser = Qnil;
@@ -39,10 +69,10 @@ static void Parser_mark(void *ptr) {
39
69
  Parser_t *parser = ptr;
40
70
  rb_gc_mark(parser->io);
41
71
  rb_gc_mark(parser->buffer);
72
+ rb_gc_mark(parser->headers);
42
73
  }
43
74
 
44
75
  static void Parser_free(void *ptr) {
45
- Parser_t *parser = ptr;
46
76
  xfree(ptr);
47
77
  }
48
78
 
@@ -66,40 +96,43 @@ static VALUE Parser_allocate(VALUE klass) {
66
96
  #define GetParser(obj, parser) \
67
97
  TypedData_Get_Struct((obj), Parser_t, &Parser_type, (parser))
68
98
 
99
+ enum read_method detect_read_method(VALUE io) {
100
+ if (rb_respond_to(io, ID_parser_read_method)) {
101
+ if (mPolyphony == Qnil)
102
+ mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
103
+ VALUE method = rb_funcall(io, ID_parser_read_method, 0);
104
+ if (method == SYM_backend_read) return method_backend_read;
105
+ if (method == SYM_backend_recv) return method_backend_recv;
106
+ return method_readpartial;
107
+ }
108
+ else if (rb_respond_to(io, ID_call)) {
109
+ return method_call;
110
+ }
111
+ else
112
+ rb_raise(rb_eRuntimeError, "Provided reader should be a callable or respond to #__parser_read_method__");
113
+ }
114
+
69
115
  VALUE Parser_initialize(VALUE self, VALUE io) {
70
116
  Parser_t *parser;
71
117
  GetParser(self, parser);
72
118
 
73
119
  parser->io = io;
74
120
  parser->buffer = rb_str_new_literal("");
121
+ parser->headers = Qnil;
75
122
  parser->pos = 0;
76
123
 
124
+ // pre-allocate the buffer
125
+ rb_str_modify_expand(parser->buffer, INITIAL_BUFFER_SIZE);
126
+
127
+ parser->read_method = detect_read_method(io);
128
+ parser->body_read_mode = BODY_READ_MODE_UNKNOWN;
129
+ parser->body_left = 0;
77
130
  return self;
78
131
  }
79
132
 
80
133
  ////////////////////////////////////////////////////////////////////////////////
81
- ////////////////////////////////////////////////////////////////////////////////
82
- ////////////////////////////////////////////////////////////////////////////////
83
-
84
- struct parser_state {
85
- struct parser *parser;
86
- char *ptr;
87
- int len;
88
- };
89
134
 
90
- #define READ_CONCAT(io, buffer, len) \
91
- rb_funcall(io, ID_readpartial, 4, len, buffer, BUFFER_END, Qfalse)
92
-
93
- static inline int fill_buffer(struct parser_state *state) {
94
- READ_CONCAT(state->parser->io, state->parser->buffer, MAX_READ_LENGTH);
95
- int len = RSTRING_LEN(state->parser->buffer);
96
- int read_bytes = len - state->len;
97
- if (!read_bytes) return 0;
98
-
99
- state->ptr = RSTRING_PTR(state->parser->buffer);
100
- state->len = len;
101
- return read_bytes;
102
- }
135
+ #define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
103
136
 
104
137
  #define FILL_BUFFER_OR_GOTO_EOF(state) { if (!fill_buffer(state)) goto eof; }
105
138
 
@@ -107,6 +140,7 @@ static inline int fill_buffer(struct parser_state *state) {
107
140
  #define BUFFER_LEN(state) ((state)->len)
108
141
  #define BUFFER_CUR(state) ((state)->ptr[(state)->parser->pos])
109
142
  #define BUFFER_AT(state, pos) ((state)->ptr[pos])
143
+ #define BUFFER_PTR(state, pos) ((state)->ptr + pos)
110
144
  #define BUFFER_STR(state, pos, len) (rb_utf8_str_new((state)->ptr + pos, len))
111
145
 
112
146
  #define INC_BUFFER_POS(state) { \
@@ -114,7 +148,7 @@ static inline int fill_buffer(struct parser_state *state) {
114
148
  if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
115
149
  }
116
150
 
117
- #define INC_BUFFER_POS_NO_READ(state) BUFFER_POS(state)++;
151
+ #define INC_BUFFER_POS_NO_FILL(state) BUFFER_POS(state)++;
118
152
 
119
153
  #define INC_BUFFER_POS_UTF8(state, len) { \
120
154
  unsigned char c = BUFFER_CUR(state); \
@@ -162,8 +196,66 @@ static inline int fill_buffer(struct parser_state *state) {
162
196
  RB_GC_GUARD(value); \
163
197
  }
164
198
 
165
- #define BUFFER_TRIM_MIN_LEN 4096
166
- #define BUFFER_TRIM_MIN_POS 2048
199
+ #define CONSUME_CRLF(state) { \
200
+ INC_BUFFER_POS(state); \
201
+ if (BUFFER_CUR(state) != '\n') goto bad_request; \
202
+ INC_BUFFER_POS(state); \
203
+ }
204
+
205
+ #define CONSUME_CRLF_NO_FILL(state) { \
206
+ INC_BUFFER_POS(state); \
207
+ if (BUFFER_CUR(state) != '\n') goto bad_request; \
208
+ INC_BUFFER_POS_NO_FILL(state); \
209
+ }
210
+
211
+ #define GLOBAL_STR(v, s) v = rb_str_new_literal(s); rb_global_variable(&v)
212
+
213
+ struct parser_state {
214
+ struct parser *parser;
215
+ char *ptr;
216
+ int len;
217
+ };
218
+
219
+ ////////////////////////////////////////////////////////////////////////////////
220
+
221
+ static inline VALUE io_read_call(VALUE io, VALUE maxlen, VALUE buf, VALUE buf_pos) {
222
+ VALUE result = rb_funcall(io, ID_call, 1, maxlen);
223
+ if (result == Qnil) return Qnil;
224
+
225
+ if (buf_pos == NUM_buffer_start) rb_str_set_len(buf, 0);
226
+ rb_str_append(buf, result);
227
+ RB_GC_GUARD(result);
228
+ return buf;
229
+ }
230
+
231
+ static inline VALUE parser_io_read(Parser_t *parser, VALUE maxlen, VALUE buf, VALUE buf_pos) {
232
+ switch (parser->read_method) {
233
+ case method_backend_read:
234
+ return rb_funcall(mPolyphony, ID_backend_read, 5, parser->io, buf, maxlen, Qfalse, buf_pos);
235
+ case method_backend_recv:
236
+ return rb_funcall(mPolyphony, ID_backend_recv, 4, parser->io, buf, maxlen, buf_pos);
237
+ case method_readpartial:
238
+ return rb_funcall(parser->io, ID_readpartial, 4, maxlen, buf, buf_pos, Qfalse);
239
+ case method_call:
240
+ return io_read_call(parser->io, maxlen, buf, buf_pos);
241
+ default:
242
+ return Qnil;
243
+ }
244
+ }
245
+
246
+ static inline int fill_buffer(struct parser_state *state) {
247
+ VALUE ret = parser_io_read(state->parser, NUM_max_headers_read_length, state->parser->buffer, NUM_buffer_end);
248
+ if (ret == Qnil) return 0;
249
+
250
+ state->parser->buffer = ret;
251
+ int len = RSTRING_LEN(state->parser->buffer);
252
+ int read_bytes = len - state->len;
253
+ if (!read_bytes) return 0;
254
+
255
+ state->ptr = RSTRING_PTR(state->parser->buffer);
256
+ state->len = len;
257
+ return read_bytes;
258
+ }
167
259
 
168
260
  static inline void buffer_trim(struct parser_state *state) {
169
261
  int len = RSTRING_LEN(state->parser->buffer);
@@ -184,27 +276,34 @@ static inline void buffer_trim(struct parser_state *state) {
184
276
  state->parser->pos = 0;
185
277
  }
186
278
 
187
- ////////////////////////////////////////////////////////////////////////////////
188
- ////////////////////////////////////////////////////////////////////////////////
279
+ static inline void str_append_from_buffer(VALUE str, char *ptr, int len) {
280
+ int str_len = RSTRING_LEN(str);
281
+ rb_str_modify_expand(str, len);
282
+ memcpy(RSTRING_PTR(str) + str_len, ptr, len);
283
+ rb_str_set_len(str, str_len + len);
284
+ }
285
+
189
286
  ////////////////////////////////////////////////////////////////////////////////
190
287
 
191
288
  static inline int parse_method(struct parser_state *state, VALUE headers) {
192
289
  int pos = BUFFER_POS(state);
193
290
  int len = 0;
194
291
 
195
- loop:
196
- switch (BUFFER_CUR(state)) {
197
- case ' ':
198
- if (len < 1 || len > MAX_METHOD_LENGTH) goto bad_request;
199
- INC_BUFFER_POS(state);
200
- goto done;
201
- case '\r':
202
- case '\n':
203
- goto bad_request;
204
- default:
205
- INC_BUFFER_POS_UTF8(state, len);
206
- if (len >= MAX_METHOD_LENGTH) goto bad_request;
207
- goto loop;
292
+ while (1) {
293
+ switch (BUFFER_CUR(state)) {
294
+ case ' ':
295
+ if (len < 1 || len > MAX_METHOD_LENGTH) goto bad_request;
296
+ INC_BUFFER_POS(state);
297
+ goto done;
298
+ case '\r':
299
+ case '\n':
300
+ goto bad_request;
301
+ default:
302
+ INC_BUFFER_POS(state);
303
+ len++;
304
+ // INC_BUFFER_POS_UTF8(state, len);
305
+ if (len > MAX_METHOD_LENGTH) goto bad_request;
306
+ }
208
307
  }
209
308
  done:
210
309
  SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
@@ -215,29 +314,31 @@ eof:
215
314
  return 0;
216
315
  }
217
316
 
218
- static int parse_path(struct parser_state *state, VALUE headers) {
317
+ static int parse_request_target(struct parser_state *state, VALUE headers) {
219
318
  while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
220
319
  int pos = BUFFER_POS(state);
221
320
  int len = 0;
222
- loop:
223
- switch (BUFFER_CUR(state)) {
224
- case ' ':
225
- if (len < 1 || len > MAX_PATH_LENGTH) goto bad_request;
226
- INC_BUFFER_POS(state);
227
- goto done;
228
- case '\r':
229
- case '\n':
230
- goto bad_request;
231
- default:
232
- INC_BUFFER_POS_UTF8(state, len);
233
- if (len >= MAX_PATH_LENGTH) goto bad_request;
234
- goto loop;
321
+ while (1) {
322
+ switch (BUFFER_CUR(state)) {
323
+ case ' ':
324
+ if (len < 1 || len > MAX_PATH_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_PATH_LENGTH) goto bad_request;
335
+ }
235
336
  }
236
337
  done:
237
338
  SET_HEADER_VALUE_FROM_BUFFER(state, headers, STR_pseudo_path, pos, len);
238
339
  return 1;
239
340
  bad_request:
240
- RAISE_BAD_REQUEST("Invalid path");
341
+ RAISE_BAD_REQUEST("Invalid request target");
241
342
  eof:
242
343
  return 0;
243
344
  }
@@ -257,26 +358,27 @@ static int parse_protocol(struct parser_state *state, VALUE headers) {
257
358
  if (BUFFER_CUR(state) == '/') INC_BUFFER_POS(state) else goto bad_request;
258
359
  if (BUFFER_CUR(state) == '1') INC_BUFFER_POS(state) else goto bad_request;
259
360
  len = 6;
260
- loop:
261
- switch (BUFFER_CUR(state)) {
262
- case '\r':
263
- INC_BUFFER_POS(state);
264
- goto eol;
265
- case '\n':
266
- INC_BUFFER_POS(state);
267
- goto done;
268
- case '.':
269
- case '1':
270
- len++;
271
- if (len > 8) goto bad_request;
272
- INC_BUFFER_POS(state);
273
- goto loop;
274
- default:
275
- goto bad_request;
361
+ while (1) {
362
+ switch (BUFFER_CUR(state)) {
363
+ case '\r':
364
+ CONSUME_CRLF(state);
365
+ goto done;
366
+ case '\n':
367
+ INC_BUFFER_POS(state);
368
+ goto done;
369
+ case '.':
370
+ INC_BUFFER_POS(state);
371
+ char c = BUFFER_CUR(state);
372
+ if (c == '0' || c == '1') {
373
+ INC_BUFFER_POS(state);
374
+ len += 2;
375
+ continue;
376
+ }
377
+ goto bad_request;
378
+ default:
379
+ goto bad_request;
380
+ }
276
381
  }
277
- eol:
278
- if (BUFFER_CUR(state) != '\n') goto bad_request;
279
- INC_BUFFER_POS(state);
280
382
  done:
281
383
  if (len < 6 || len > 8) goto bad_request;
282
384
  SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_protocol, pos, len);
@@ -289,7 +391,7 @@ eof:
289
391
 
290
392
  int parse_request_line(struct parser_state *state, VALUE headers) {
291
393
  if (!parse_method(state, headers)) goto eof;
292
- if (!parse_path(state, headers)) goto eof;
394
+ if (!parse_request_target(state, headers)) goto eof;
293
395
  if (!parse_protocol(state, headers)) goto eof;
294
396
 
295
397
  return 1;
@@ -301,31 +403,31 @@ static inline int parse_header_key(struct parser_state *state, VALUE *key) {
301
403
  int pos = BUFFER_POS(state);
302
404
  int len = 0;
303
405
 
304
- loop:
305
- switch (BUFFER_CUR(state)) {
306
- case ':':
307
- if (len < 1 || len > MAX_HEADER_KEY_LENGTH)
406
+ while (1) {
407
+ switch (BUFFER_CUR(state)) {
408
+ case ' ':
308
409
  goto bad_request;
309
- INC_BUFFER_POS(state);
310
- goto done;
311
- case '\r':
312
- if (BUFFER_POS(state) > pos) goto bad_request;
313
-
314
- INC_BUFFER_POS(state);
315
- goto eol;
316
- case '\n':
317
- if (BUFFER_POS(state) > pos) goto bad_request;
318
-
319
- INC_BUFFER_POS_NO_READ(state);
320
- goto done;
321
- default:
322
- INC_BUFFER_POS_UTF8(state, len);
323
- if (len >= MAX_HEADER_KEY_LENGTH) goto bad_request;
324
- goto loop;
410
+ case ':':
411
+ if (len < 1 || len > MAX_HEADER_KEY_LENGTH)
412
+ goto bad_request;
413
+ INC_BUFFER_POS(state);
414
+ goto done;
415
+ case '\r':
416
+ if (BUFFER_POS(state) > pos) goto bad_request;
417
+ CONSUME_CRLF_NO_FILL(state);
418
+ goto done;
419
+ case '\n':
420
+ if (BUFFER_POS(state) > pos) goto bad_request;
421
+
422
+ INC_BUFFER_POS_NO_FILL(state);
423
+ goto done;
424
+ default:
425
+ INC_BUFFER_POS(state);
426
+ len++;
427
+ // INC_BUFFER_POS_UTF8(state, len);
428
+ if (len > MAX_HEADER_KEY_LENGTH) goto bad_request;
429
+ }
325
430
  }
326
- eol:
327
- if (BUFFER_CUR(state) != '\n') goto bad_request;
328
- INC_BUFFER_POS_NO_READ(state);
329
431
  done:
330
432
  if (len == 0) return -1;
331
433
  (*key) = str_downcase(BUFFER_STR(state, pos, len));
@@ -342,21 +444,19 @@ static inline int parse_header_value(struct parser_state *state, VALUE *value) {
342
444
  int pos = BUFFER_POS(state);
343
445
  int len = 0;
344
446
 
345
- loop:
346
- switch (BUFFER_CUR(state)) {
347
- case '\r':
348
- INC_BUFFER_POS(state);
349
- goto eol;
350
- case '\n':
351
- goto done;
352
- default:
353
- INC_BUFFER_POS_UTF8(state, len);
354
- if (len >= MAX_HEADER_VALUE_LENGTH) goto bad_request;
355
- goto loop;
447
+ while (1) {
448
+ switch (BUFFER_CUR(state)) {
449
+ case '\r':
450
+ CONSUME_CRLF(state);
451
+ goto done;
452
+ case '\n':
453
+ INC_BUFFER_POS(state);
454
+ goto done;
455
+ default:
456
+ INC_BUFFER_POS_UTF8(state, len);
457
+ if (len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
458
+ }
356
459
  }
357
- eol:
358
- if (BUFFER_CUR(state) != '\n') goto bad_request;
359
- INC_BUFFER_POS(state);
360
460
  done:
361
461
  if (len < 1 || len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
362
462
  (*value) = BUFFER_STR(state, pos, len);
@@ -400,111 +500,285 @@ eof:
400
500
  VALUE Parser_parse_headers(VALUE self) {
401
501
  struct parser_state state;
402
502
  GetParser(self, state.parser);
403
- VALUE headers = rb_hash_new();
503
+ state.parser->headers = rb_hash_new();
404
504
 
405
505
  buffer_trim(&state);
506
+ int initial_pos = state.parser->pos;
406
507
  INIT_PARSER_STATE(&state);
508
+ state.parser->current_request_rx = 0;
407
509
 
408
- if (!parse_request_line(&state, headers)) goto eof;
510
+ if (!parse_request_line(&state, state.parser->headers)) goto eof;
409
511
 
410
- loop:
411
- switch (parse_header(&state, headers)) {
412
- case -1: goto done; // empty header => end of headers
413
- case 0: goto eof;
414
- default: goto loop;
512
+ int header_count = 0;
513
+ while (1) {
514
+ if (header_count > MAX_HEADER_COUNT) RAISE_BAD_REQUEST("Too many headers");
515
+ switch (parse_header(&state, state.parser->headers)) {
516
+ case -1: goto done; // empty header => end of headers
517
+ case 0: goto eof;
518
+ }
519
+ header_count++;
415
520
  }
416
-
417
- done:
418
- RB_GC_GUARD(headers);
419
- return headers;
420
521
  eof:
421
- return Qnil;
522
+ state.parser->headers = Qnil;
523
+ done:
524
+ state.parser->body_read_mode = BODY_READ_MODE_UNKNOWN;
525
+ int read_bytes = BUFFER_POS(&state) - initial_pos;
526
+
527
+ state.parser->current_request_rx += read_bytes;
528
+ if (state.parser->headers != Qnil)
529
+ rb_hash_aset(state.parser->headers, STR_pseudo_rx, INT2NUM(read_bytes));
530
+ return state.parser->headers;
422
531
  }
423
532
 
424
- ////////////////////////////////////////////////////////////////////////////////
425
- ////////////////////////////////////////////////////////////////////////////////
426
533
  ////////////////////////////////////////////////////////////////////////////////
427
534
 
428
- const int READ_MAX_LEN = 1 << 20;
535
+ static inline int str_to_int(VALUE value, const char *error_msg) {
536
+ char *ptr = RSTRING_PTR(value);
537
+ int len = RSTRING_LEN(value);
538
+ int int_value = 0;
429
539
 
430
- static inline int parse_content_length(VALUE value) {
431
- VALUE to_i = rb_funcall(value, ID_to_i, 0);
432
- return NUM2INT(to_i);
433
- }
540
+ while (len) {
541
+ char c = *ptr;
542
+ if ((c >= '0') && (c <= '9'))
543
+ int_value = int_value * 10 + (c - '0');
544
+ else
545
+ RAISE_BAD_REQUEST(error_msg);
546
+ len--;
547
+ ptr++;
548
+ }
434
549
 
435
- VALUE read_body_with_content_length(VALUE self, int content_length) {
436
- if (content_length < 0) return Qnil;
550
+ return int_value;
551
+ }
437
552
 
438
- Parser_t *parser;
439
- GetParser(self, parser);
553
+ VALUE read_body_with_content_length(Parser_t *parser, int read_entire_body, int buffered_only) {
554
+ if (parser->body_left <= 0) return Qnil;
440
555
 
441
- VALUE body;
556
+ VALUE body = Qnil;
442
557
 
443
558
  int len = RSTRING_LEN(parser->buffer);
444
559
  int pos = parser->pos;
445
- int left = content_length;
446
560
 
447
561
  if (pos < len) {
448
562
  int available = len - pos;
449
- if (available > content_length) available = content_length;
563
+ if (available > parser->body_left) available = parser->body_left;
450
564
  body = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
451
565
  parser->pos += available;
452
- left -= available;
453
- len = available;
566
+ parser->current_request_rx += available;
567
+ parser->body_left -= available;
568
+ if (!parser->body_left) parser->request_completed = 1;
454
569
  }
455
570
  else {
456
571
  body = Qnil;
457
572
  len = 0;
458
573
  }
574
+ if (buffered_only) return body;
459
575
 
460
- while (left) {
461
- int maxlen = left <= READ_MAX_LEN ? left : READ_MAX_LEN;
462
-
463
- if (body != Qnil) {
464
- VALUE tmp_buf = rb_funcall(
465
- mPolyphony, ID_backend_read, 5, parser->io, Qnil, INT2NUM(maxlen), Qfalse, INT2NUM(0)
466
- );
576
+ while (parser->body_left) {
577
+ int maxlen = parser->body_left <= MAX_BODY_READ_LENGTH ? parser->body_left : MAX_BODY_READ_LENGTH;
578
+ VALUE tmp_buf = parser_io_read(parser, INT2NUM(maxlen), Qnil, NUM_buffer_start);
579
+ if (tmp_buf == Qnil) goto eof;
580
+ if (body != Qnil)
467
581
  rb_str_append(body, tmp_buf);
468
- RB_GC_GUARD(tmp_buf);
469
- }
470
- else {
471
- body = rb_funcall(
472
- mPolyphony, ID_backend_read, 5, parser->io, Qnil, INT2NUM(maxlen), Qfalse, INT2NUM(0)
473
- );
582
+ else
583
+ body = tmp_buf;
584
+ int read_bytes = RSTRING_LEN(tmp_buf);
585
+ parser->current_request_rx += read_bytes;
586
+ parser->body_left -= read_bytes;
587
+ if (!parser->body_left) parser->request_completed = 1;
588
+ RB_GC_GUARD(tmp_buf);
589
+ if (!read_entire_body) goto done;
590
+ }
591
+ done:
592
+ rb_hash_aset(parser->headers, STR_pseudo_rx, INT2NUM(parser->current_request_rx));
593
+ RB_GC_GUARD(body);
594
+ return body;
595
+ eof:
596
+ RAISE_BAD_REQUEST("Incomplete body");
597
+ }
598
+
599
+ int chunked_encoding_p(VALUE transfer_encoding) {
600
+ if (transfer_encoding == Qnil) return 0;
601
+ return rb_funcall(str_downcase(transfer_encoding), ID_eq, 1, STR_chunked) == Qtrue;
602
+ }
603
+
604
+ int parse_chunk_size(struct parser_state *state, int *chunk_size) {
605
+ int len = 0;
606
+ int value = 0;
607
+ int initial_pos = BUFFER_POS(state);
608
+
609
+ while (1) {
610
+ char c = BUFFER_CUR(state);
611
+ if ((c >= '0') && (c <= '9')) value = (value << 4) + (c - '0');
612
+ else if ((c >= 'a') && (c <= 'f')) value = (value << 4) + (c - 'a' + 10);
613
+ else if ((c >= 'A') && (c <= 'F')) value = (value << 4) + (c - 'A' + 10);
614
+ else switch (c) {
615
+ case '\r':
616
+ CONSUME_CRLF_NO_FILL(state);
617
+ goto done;
618
+ case '\n':
619
+ INC_BUFFER_POS_NO_FILL(state);
620
+ goto done;
621
+ default:
622
+ goto bad_request;
474
623
  }
475
- int new_len = RSTRING_LEN(body);
476
- int read_bytes = new_len - len;
477
- if (!read_bytes) RAISE_BAD_REQUEST("Incomplete request body");
624
+ INC_BUFFER_POS(state);
625
+ len++;
626
+ if (len >= MAX_CHUNKED_ENCODING_CHUNK_SIZE_LENGTH) goto bad_request;
627
+ }
628
+ done:
629
+ if (len == 0) goto bad_request;
630
+ (*chunk_size) = value;
631
+ state->parser->current_request_rx += BUFFER_POS(state) - initial_pos;
632
+ return 1;
633
+ bad_request:
634
+ RAISE_BAD_REQUEST("Invalid chunk size");
635
+ eof:
636
+ return 0;
637
+ }
478
638
 
479
- len = new_len;
639
+ int read_body_chunk_with_chunked_encoding(struct parser_state *state, VALUE *body, int chunk_size, int buffered_only) {
640
+ int len = RSTRING_LEN(state->parser->buffer);
641
+ int pos = state->parser->pos;
642
+ int left = chunk_size;
643
+
644
+ if (pos < len) {
645
+ int available = len - pos;
646
+ if (available > left) available = left;
647
+ if (*body != Qnil)
648
+ str_append_from_buffer(*body, RSTRING_PTR(state->parser->buffer) + pos, available);
649
+ else
650
+ *body = rb_str_new(RSTRING_PTR(state->parser->buffer) + pos, available);
651
+ state->parser->pos += available;
652
+ state->parser->current_request_rx += available;
653
+ left -= available;
654
+ }
655
+ if (buffered_only) return 1;
656
+
657
+ while (left) {
658
+ int maxlen = left <= MAX_BODY_READ_LENGTH ? left : MAX_BODY_READ_LENGTH;
659
+
660
+ VALUE tmp_buf = parser_io_read(state->parser, INT2NUM(maxlen), Qnil, NUM_buffer_start);
661
+ if (tmp_buf == Qnil) goto eof;
662
+ if (*body != Qnil)
663
+ rb_str_append(*body, tmp_buf);
664
+ else
665
+ *body = tmp_buf;
666
+ int read_bytes = RSTRING_LEN(tmp_buf);
667
+ state->parser->current_request_rx += read_bytes;
480
668
  left -= read_bytes;
669
+ RB_GC_GUARD(tmp_buf);
481
670
  }
671
+ return 1;
672
+ eof:
673
+ return 0;
674
+ }
675
+
676
+ static inline int parse_chunk_postfix(struct parser_state *state) {
677
+ int initial_pos = BUFFER_POS(state);
678
+ if (initial_pos == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state);
679
+ switch (BUFFER_CUR(state)) {
680
+ case '\r':
681
+ CONSUME_CRLF_NO_FILL(state);
682
+ goto done;
683
+ case '\n':
684
+ INC_BUFFER_POS_NO_FILL(state);
685
+ goto done;
686
+ default:
687
+ goto bad_request;
688
+ }
689
+ done:
690
+ state->parser->current_request_rx += BUFFER_POS(state) - initial_pos;
691
+ return 1;
692
+ bad_request:
693
+ RAISE_BAD_REQUEST("Invalid protocol");
694
+ eof:
695
+ return 0;
696
+ }
482
697
 
698
+ VALUE read_body_with_chunked_encoding(Parser_t *parser, int read_entire_body, int buffered_only) {
699
+ struct parser_state state;
700
+ state.parser = parser;
701
+ buffer_trim(&state);
702
+ INIT_PARSER_STATE(&state);
703
+ VALUE body = Qnil;
704
+
705
+ while (1) {
706
+ int chunk_size = 0;
707
+ if (BUFFER_POS(&state) == BUFFER_LEN(&state)) FILL_BUFFER_OR_GOTO_EOF(&state);
708
+ if (!parse_chunk_size(&state, &chunk_size)) goto bad_request;
709
+
710
+ if (chunk_size) {
711
+ if (!read_body_chunk_with_chunked_encoding(&state, &body, chunk_size, buffered_only)) goto bad_request;
712
+ }
713
+ else parser->request_completed = 1;
714
+
715
+ if (!parse_chunk_postfix(&state)) goto bad_request;
716
+ if (!chunk_size || !read_entire_body) goto done;
717
+ }
718
+ bad_request:
719
+ RAISE_BAD_REQUEST("Malformed request body");
720
+ eof:
721
+ RAISE_BAD_REQUEST("Incomplete request body");
722
+ done:
723
+ rb_hash_aset(parser->headers, STR_pseudo_rx, INT2NUM(state.parser->current_request_rx));
483
724
  RB_GC_GUARD(body);
484
725
  return body;
485
726
  }
486
727
 
487
- VALUE Parser_read_body(VALUE self, VALUE headers) {
488
- VALUE content_length = rb_hash_aref(headers, STR_content_length);
489
- if (content_length != Qnil)
490
- return read_body_with_content_length(self, parse_content_length(content_length));
728
+ static inline void detect_body_read_mode(Parser_t *parser) {
729
+ VALUE content_length = rb_hash_aref(parser->headers, STR_content_length);
730
+ if (content_length != Qnil) {
731
+ int int_content_length = str_to_int(content_length, "Invalid content length");
732
+ if (int_content_length < 0) RAISE_BAD_REQUEST("Invalid body content length");
733
+ parser->body_read_mode = parser->body_left = int_content_length;
734
+ parser->request_completed = 0;
735
+ return;
736
+ }
491
737
 
492
- // VALUE transfer_encoding = rb_hash_aref(headers, STR_transfer_encoding);
493
- // if (chunked_encoding_p(transfer_encoding)) {
494
- // return read_body_with_chunked_encoding(self);
495
- // }
738
+ VALUE transfer_encoding = rb_hash_aref(parser->headers, STR_transfer_encoding);
739
+ if (chunked_encoding_p(transfer_encoding)) {
740
+ parser->body_read_mode = BODY_READ_MODE_CHUNKED;
741
+ parser->request_completed = 0;
742
+ return;
743
+ }
744
+ parser->request_completed = 1;
496
745
 
497
- return Qnil;
498
746
  }
499
747
 
500
- #define GLOBAL_STR(v, s) v = rb_str_new_literal(s); rb_global_variable(&v)
748
+ static inline VALUE read_body(VALUE self, int read_entire_body, int buffered_only) {
749
+ Parser_t *parser;
750
+ GetParser(self, parser);
751
+
752
+ if (parser->body_read_mode == BODY_READ_MODE_UNKNOWN)
753
+ detect_body_read_mode(parser);
754
+
755
+ if (parser->body_read_mode == BODY_READ_MODE_CHUNKED)
756
+ return read_body_with_chunked_encoding(parser, read_entire_body, buffered_only);
757
+ return read_body_with_content_length(parser, read_entire_body, buffered_only);
758
+ }
759
+
760
+ VALUE Parser_read_body(VALUE self) {
761
+ return read_body(self, 1, 0);
762
+ }
763
+
764
+ VALUE Parser_read_body_chunk(VALUE self, VALUE buffered_only) {
765
+ return read_body(self, 0, buffered_only == Qtrue);
766
+ }
767
+
768
+ VALUE Parser_complete_p(VALUE self) {
769
+ Parser_t *parser;
770
+ GetParser(self, parser);
771
+
772
+ if (parser->body_read_mode == BODY_READ_MODE_UNKNOWN)
773
+ detect_body_read_mode(parser);
774
+
775
+ return parser->request_completed ? Qtrue : Qfalse;
776
+ }
501
777
 
502
778
  void Init_HTTP1_Parser() {
503
779
  VALUE mTipi;
504
780
  VALUE cHTTP1Parser;
505
781
 
506
- mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
507
-
508
782
  mTipi = rb_define_module("Tipi");
509
783
  cHTTP1Parser = rb_define_class_under(mTipi, "HTTP1Parser", rb_cObject);
510
784
  rb_define_alloc_func(cHTTP1Parser, Parser_allocate);
@@ -514,21 +788,36 @@ void Init_HTTP1_Parser() {
514
788
  // backend methods
515
789
  rb_define_method(cHTTP1Parser, "initialize", Parser_initialize, 1);
516
790
  rb_define_method(cHTTP1Parser, "parse_headers", Parser_parse_headers, 0);
517
- rb_define_method(cHTTP1Parser, "read_body", Parser_read_body, 1);
518
-
519
- ID_backend_read = rb_intern("backend_read");
520
- ID_downcase = rb_intern("downcase");
521
- ID_read = rb_intern("read");
522
- ID_readpartial = rb_intern("readpartial");
523
- ID_to_i = rb_intern("to_i");
524
-
525
- MAX_READ_LENGTH = INT2NUM(4096);
526
- BUFFER_END = INT2NUM(-1);
791
+ rb_define_method(cHTTP1Parser, "read_body", Parser_read_body, 0);
792
+ rb_define_method(cHTTP1Parser, "read_body_chunk", Parser_read_body_chunk, 1);
793
+ rb_define_method(cHTTP1Parser, "complete?", Parser_complete_p, 0);
794
+
795
+ ID_arity = rb_intern("arity");
796
+ ID_backend_read = rb_intern("backend_read");
797
+ ID_backend_recv = rb_intern("backend_recv");
798
+ ID_call = rb_intern("call");
799
+ ID_downcase = rb_intern("downcase");
800
+ ID_eq = rb_intern("==");
801
+ ID_parser_read_method = rb_intern("__parser_read_method__");
802
+ ID_read = rb_intern("read");
803
+ ID_readpartial = rb_intern("readpartial");
804
+ ID_to_i = rb_intern("to_i");
805
+
806
+ NUM_max_headers_read_length = INT2NUM(MAX_HEADERS_READ_LENGTH);
807
+ NUM_buffer_start = INT2NUM(0);
808
+ NUM_buffer_end = INT2NUM(-1);
527
809
 
528
810
  GLOBAL_STR(STR_pseudo_method, ":method");
529
811
  GLOBAL_STR(STR_pseudo_path, ":path");
530
812
  GLOBAL_STR(STR_pseudo_protocol, ":protocol");
813
+ GLOBAL_STR(STR_pseudo_rx, ":rx");
531
814
 
815
+ GLOBAL_STR(STR_chunked, "chunked");
532
816
  GLOBAL_STR(STR_content_length, "content-length");
533
817
  GLOBAL_STR(STR_transfer_encoding, "transfer-encoding");
818
+
819
+ SYM_backend_read = ID2SYM(ID_backend_read);
820
+ SYM_backend_recv = ID2SYM(ID_backend_recv);
821
+
822
+ rb_global_variable(&mTipi);
534
823
  }