tipi 0.38 → 0.42

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +5 -1
  3. data/.gitignore +5 -0
  4. data/CHANGELOG.md +34 -0
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +58 -16
  7. data/Rakefile +7 -3
  8. data/TODO.md +77 -1
  9. data/benchmarks/bm_http1_parser.rb +61 -0
  10. data/bin/benchmark +37 -0
  11. data/bin/h1pd +6 -0
  12. data/bin/tipi +3 -21
  13. data/df/sample_agent.rb +1 -1
  14. data/df/server.rb +16 -47
  15. data/df/server_utils.rb +178 -0
  16. data/examples/full_service.rb +13 -0
  17. data/examples/http1_parser.rb +55 -0
  18. data/examples/http_server.rb +15 -3
  19. data/examples/http_server_forked.rb +5 -1
  20. data/examples/http_server_routes.rb +29 -0
  21. data/examples/http_server_static.rb +26 -0
  22. data/examples/http_server_throttled.rb +3 -2
  23. data/examples/https_server.rb +6 -4
  24. data/examples/https_wss_server.rb +2 -1
  25. data/examples/rack_server.rb +5 -0
  26. data/examples/rack_server_https.rb +1 -1
  27. data/examples/rack_server_https_forked.rb +4 -3
  28. data/examples/routing_server.rb +5 -4
  29. data/examples/servername_cb.rb +37 -0
  30. data/examples/websocket_demo.rb +2 -8
  31. data/examples/ws_page.html +2 -2
  32. data/ext/tipi/extconf.rb +13 -0
  33. data/ext/tipi/http1_parser.c +823 -0
  34. data/ext/tipi/http1_parser.h +18 -0
  35. data/ext/tipi/tipi_ext.c +5 -0
  36. data/lib/tipi.rb +89 -1
  37. data/lib/tipi/acme.rb +308 -0
  38. data/lib/tipi/cli.rb +30 -0
  39. data/lib/tipi/digital_fabric/agent.rb +22 -17
  40. data/lib/tipi/digital_fabric/agent_proxy.rb +95 -40
  41. data/lib/tipi/digital_fabric/executive.rb +6 -2
  42. data/lib/tipi/digital_fabric/protocol.rb +87 -15
  43. data/lib/tipi/digital_fabric/request_adapter.rb +6 -10
  44. data/lib/tipi/digital_fabric/service.rb +77 -51
  45. data/lib/tipi/http1_adapter.rb +116 -117
  46. data/lib/tipi/http2_adapter.rb +56 -10
  47. data/lib/tipi/http2_stream.rb +106 -53
  48. data/lib/tipi/rack_adapter.rb +2 -53
  49. data/lib/tipi/response_extensions.rb +17 -0
  50. data/lib/tipi/version.rb +1 -1
  51. data/security/http1.rb +12 -0
  52. data/test/helper.rb +60 -11
  53. data/test/test_http1_parser.rb +586 -0
  54. data/test/test_http_server.rb +0 -27
  55. data/test/test_request.rb +1 -28
  56. data/tipi.gemspec +11 -5
  57. metadata +96 -22
  58. data/e +0 -0
@@ -12,17 +12,18 @@ puts "pid: #{Process.pid}"
12
12
  puts 'Listening on port 4411...'
13
13
 
14
14
  app = Tipi.route do |r|
15
- r.root do
15
+
16
+ r.on_root do
16
17
  r.redirect '/hello'
17
18
  end
18
19
  r.on 'hello' do
19
- r.get 'world' do
20
+ r.on_get 'world' do
20
21
  r.respond 'Hello world'
21
22
  end
22
- r.get do
23
+ r.on_get do
23
24
  r.respond 'Hello'
24
25
  end
25
- r.post do
26
+ r.on_post do
26
27
  puts 'Someone said Hello'
27
28
  r.redirect '/'
28
29
  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
@@ -1,13 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/inline'
4
-
5
- gemfile do
6
- source 'https://rubygems.org'
7
- gem 'polyphony', '~> 0.44'
8
- gem 'tipi', '~> 0.31'
9
- end
10
-
3
+ require 'bundler/setup'
4
+ require 'tipi'
11
5
  require 'tipi/websocket'
12
6
 
13
7
  class WebsocketClient
@@ -6,7 +6,7 @@
6
6
  <body>
7
7
  <script>
8
8
  var connect = function () {
9
- var exampleSocket = new WebSocket("wss://dev.realiteq.net/");
9
+ var exampleSocket = new WebSocket("ws://localhost:4411/");
10
10
 
11
11
  exampleSocket.onopen = function (event) {
12
12
  document.querySelector('#status').innerText = 'connected';
@@ -30,4 +30,4 @@
30
30
  <h1 id="status">disconnected</h1>
31
31
  <h1 id="msg"></h1>
32
32
  </body>
33
- </html>
33
+ </html>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'mkmf'
5
+
6
+ require_relative '../../security/http1'
7
+
8
+ $CFLAGS << " -Wno-format-security"
9
+ CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
10
+ Tipi::HTTP1_LIMITS.each { |k, v| $defs << "-D#{k.upcase}=#{v}" }
11
+
12
+ dir_config 'tipi_ext'
13
+ create_makefile 'tipi_ext'
@@ -0,0 +1,823 @@
1
+ #include "ruby.h"
2
+ #include "http1_parser.h"
3
+
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
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_eq;
22
+ ID ID_parser_read_method;
23
+ ID ID_read;
24
+ ID ID_readpartial;
25
+ ID ID_to_i;
26
+
27
+ static VALUE mPolyphony = Qnil;
28
+ static VALUE cError;
29
+
30
+ VALUE NUM_max_headers_read_length;
31
+ VALUE NUM_buffer_start;
32
+ VALUE NUM_buffer_end;
33
+
34
+ VALUE STR_pseudo_method;
35
+ VALUE STR_pseudo_path;
36
+ VALUE STR_pseudo_protocol;
37
+ VALUE STR_pseudo_rx;
38
+
39
+ VALUE STR_chunked;
40
+ VALUE STR_content_length;
41
+ VALUE STR_transfer_encoding;
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
+
53
+ typedef struct parser {
54
+ VALUE io;
55
+ VALUE buffer;
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;
64
+ } Parser_t;
65
+
66
+ VALUE cParser = Qnil;
67
+
68
+ static void Parser_mark(void *ptr) {
69
+ Parser_t *parser = ptr;
70
+ rb_gc_mark(parser->io);
71
+ rb_gc_mark(parser->buffer);
72
+ rb_gc_mark(parser->headers);
73
+ }
74
+
75
+ static void Parser_free(void *ptr) {
76
+ xfree(ptr);
77
+ }
78
+
79
+ static size_t Parser_size(const void *ptr) {
80
+ return sizeof(Parser_t);
81
+ }
82
+
83
+ static const rb_data_type_t Parser_type = {
84
+ "Parser",
85
+ {Parser_mark, Parser_free, Parser_size,},
86
+ 0, 0, 0
87
+ };
88
+
89
+ static VALUE Parser_allocate(VALUE klass) {
90
+ Parser_t *parser;
91
+
92
+ parser = ALLOC(Parser_t);
93
+ return TypedData_Wrap_Struct(klass, &Parser_type, parser);
94
+ }
95
+
96
+ #define GetParser(obj, parser) \
97
+ TypedData_Get_Struct((obj), Parser_t, &Parser_type, (parser))
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
+
115
+ VALUE Parser_initialize(VALUE self, VALUE io) {
116
+ Parser_t *parser;
117
+ GetParser(self, parser);
118
+
119
+ parser->io = io;
120
+ parser->buffer = rb_str_new_literal("");
121
+ parser->headers = Qnil;
122
+ parser->pos = 0;
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;
130
+ return self;
131
+ }
132
+
133
+ ////////////////////////////////////////////////////////////////////////////////
134
+
135
+ #define str_downcase(str) (rb_funcall((str), ID_downcase, 0))
136
+
137
+ #define FILL_BUFFER_OR_GOTO_EOF(state) { if (!fill_buffer(state)) goto eof; }
138
+
139
+ #define BUFFER_POS(state) ((state)->parser->pos)
140
+ #define BUFFER_LEN(state) ((state)->len)
141
+ #define BUFFER_CUR(state) ((state)->ptr[(state)->parser->pos])
142
+ #define BUFFER_AT(state, pos) ((state)->ptr[pos])
143
+ #define BUFFER_PTR(state, pos) ((state)->ptr + pos)
144
+ #define BUFFER_STR(state, pos, len) (rb_utf8_str_new((state)->ptr + pos, len))
145
+
146
+ #define INC_BUFFER_POS(state) { \
147
+ BUFFER_POS(state)++; \
148
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
149
+ }
150
+
151
+ #define INC_BUFFER_POS_NO_FILL(state) BUFFER_POS(state)++;
152
+
153
+ #define INC_BUFFER_POS_UTF8(state, len) { \
154
+ unsigned char c = BUFFER_CUR(state); \
155
+ if ((c & 0xf0) == 0xf0) { \
156
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 4) FILL_BUFFER_OR_GOTO_EOF(state); \
157
+ BUFFER_POS(state) += 4; \
158
+ len += 4; \
159
+ } \
160
+ else if ((c & 0xe0) == 0xe0) { \
161
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 3) FILL_BUFFER_OR_GOTO_EOF(state); \
162
+ BUFFER_POS(state) += 3; \
163
+ len += 3; \
164
+ } \
165
+ else if ((c & 0xc0) == 0xc0) { \
166
+ while (BUFFER_LEN(state) - BUFFER_POS(state) < 2) FILL_BUFFER_OR_GOTO_EOF(state); \
167
+ BUFFER_POS(state) += 2; \
168
+ len += 2; \
169
+ } \
170
+ else { \
171
+ BUFFER_POS(state)++; \
172
+ len ++; \
173
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) FILL_BUFFER_OR_GOTO_EOF(state); \
174
+ } \
175
+ }
176
+
177
+ #define INIT_PARSER_STATE(state) { \
178
+ (state)->len = RSTRING_LEN((state)->parser->buffer); \
179
+ if (BUFFER_POS(state) == BUFFER_LEN(state)) \
180
+ FILL_BUFFER_OR_GOTO_EOF(state) \
181
+ else \
182
+ (state)->ptr = RSTRING_PTR((state)->parser->buffer); \
183
+ }
184
+
185
+ #define RAISE_BAD_REQUEST(msg) rb_raise(cError, msg)
186
+
187
+ #define SET_HEADER_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
188
+ VALUE value = BUFFER_STR(state, pos, len); \
189
+ rb_hash_aset(headers, key, value); \
190
+ RB_GC_GUARD(value); \
191
+ }
192
+
193
+ #define SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, key, pos, len) { \
194
+ VALUE value = str_downcase(BUFFER_STR(state, pos, len)); \
195
+ rb_hash_aset(headers, key, value); \
196
+ RB_GC_GUARD(value); \
197
+ }
198
+
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
+ }
259
+
260
+ static inline void buffer_trim(struct parser_state *state) {
261
+ int len = RSTRING_LEN(state->parser->buffer);
262
+ int pos = state->parser->pos;
263
+ int left = len - pos;
264
+
265
+ // The buffer is trimmed only if length and position thresholds are passed,
266
+ // *and* position is past the halfway point.
267
+ if (len < BUFFER_TRIM_MIN_LEN ||
268
+ pos < BUFFER_TRIM_MIN_POS ||
269
+ left >= pos) return;
270
+
271
+ if (left > 0) {
272
+ char *ptr = RSTRING_PTR(state->parser->buffer);
273
+ memcpy(ptr, ptr + pos, left);
274
+ }
275
+ rb_str_set_len(state->parser->buffer, left);
276
+ state->parser->pos = 0;
277
+ }
278
+
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
+
286
+ ////////////////////////////////////////////////////////////////////////////////
287
+
288
+ static inline int parse_method(struct parser_state *state, VALUE headers) {
289
+ int pos = BUFFER_POS(state);
290
+ int len = 0;
291
+
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
+ }
307
+ }
308
+ done:
309
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_method, pos, len);
310
+ return 1;
311
+ bad_request:
312
+ RAISE_BAD_REQUEST("Invalid method");
313
+ eof:
314
+ return 0;
315
+ }
316
+
317
+ static int parse_request_target(struct parser_state *state, VALUE headers) {
318
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
319
+ int pos = BUFFER_POS(state);
320
+ int len = 0;
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
+ }
336
+ }
337
+ done:
338
+ SET_HEADER_VALUE_FROM_BUFFER(state, headers, STR_pseudo_path, pos, len);
339
+ return 1;
340
+ bad_request:
341
+ RAISE_BAD_REQUEST("Invalid request target");
342
+ eof:
343
+ return 0;
344
+ }
345
+
346
+ // case-insensitive compare
347
+ #define CMP_CI(state, down, up) ((BUFFER_CUR(state) == down) || (BUFFER_CUR(state) == up))
348
+
349
+ static int parse_protocol(struct parser_state *state, VALUE headers) {
350
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
351
+ int pos = BUFFER_POS(state);
352
+ int len = 0;
353
+
354
+ if (CMP_CI(state, 'H', 'h')) INC_BUFFER_POS(state) else goto bad_request;
355
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
356
+ if (CMP_CI(state, 'T', 't')) INC_BUFFER_POS(state) else goto bad_request;
357
+ if (CMP_CI(state, 'P', 'p')) INC_BUFFER_POS(state) else goto bad_request;
358
+ if (BUFFER_CUR(state) == '/') INC_BUFFER_POS(state) else goto bad_request;
359
+ if (BUFFER_CUR(state) == '1') INC_BUFFER_POS(state) else goto bad_request;
360
+ len = 6;
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
+ }
381
+ }
382
+ done:
383
+ if (len < 6 || len > 8) goto bad_request;
384
+ SET_HEADER_DOWNCASE_VALUE_FROM_BUFFER(state, headers, STR_pseudo_protocol, pos, len);
385
+ return 1;
386
+ bad_request:
387
+ RAISE_BAD_REQUEST("Invalid protocol");
388
+ eof:
389
+ return 0;
390
+ }
391
+
392
+ int parse_request_line(struct parser_state *state, VALUE headers) {
393
+ if (!parse_method(state, headers)) goto eof;
394
+ if (!parse_request_target(state, headers)) goto eof;
395
+ if (!parse_protocol(state, headers)) goto eof;
396
+
397
+ return 1;
398
+ eof:
399
+ return 0;
400
+ }
401
+
402
+ static inline int parse_header_key(struct parser_state *state, VALUE *key) {
403
+ int pos = BUFFER_POS(state);
404
+ int len = 0;
405
+
406
+ while (1) {
407
+ switch (BUFFER_CUR(state)) {
408
+ case ' ':
409
+ goto bad_request;
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
+ }
430
+ }
431
+ done:
432
+ if (len == 0) return -1;
433
+ (*key) = str_downcase(BUFFER_STR(state, pos, len));
434
+ return 1;
435
+ bad_request:
436
+ RAISE_BAD_REQUEST("Invalid header key");
437
+ eof:
438
+ return 0;
439
+ }
440
+
441
+ static inline int parse_header_value(struct parser_state *state, VALUE *value) {
442
+ while (BUFFER_CUR(state) == ' ') INC_BUFFER_POS(state);
443
+
444
+ int pos = BUFFER_POS(state);
445
+ int len = 0;
446
+
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
+ }
459
+ }
460
+ done:
461
+ if (len < 1 || len > MAX_HEADER_VALUE_LENGTH) goto bad_request;
462
+ (*value) = BUFFER_STR(state, pos, len);
463
+ return 1;
464
+ bad_request:
465
+ RAISE_BAD_REQUEST("Invalid header value");
466
+ eof:
467
+ return 0;
468
+ }
469
+
470
+ static inline int parse_header(struct parser_state *state, VALUE headers) {
471
+ VALUE key, value;
472
+
473
+ switch (parse_header_key(state, &key)) {
474
+ case -1: return -1;
475
+ case 0: goto eof;
476
+ }
477
+
478
+ if (!parse_header_value(state, &value)) goto eof;
479
+
480
+ VALUE existing = rb_hash_aref(headers, key);
481
+ if (existing != Qnil) {
482
+ if (TYPE(existing) != T_ARRAY) {
483
+ existing = rb_ary_new3(2, existing, value);
484
+ rb_hash_aset(headers, key, existing);
485
+ }
486
+ else
487
+ rb_ary_push(existing, value);
488
+ }
489
+ else
490
+ rb_hash_aset(headers, key, value);
491
+
492
+ RB_GC_GUARD(existing);
493
+ RB_GC_GUARD(key);
494
+ RB_GC_GUARD(value);
495
+ return 1;
496
+ eof:
497
+ return 0;
498
+ }
499
+
500
+ VALUE Parser_parse_headers(VALUE self) {
501
+ struct parser_state state;
502
+ GetParser(self, state.parser);
503
+ state.parser->headers = rb_hash_new();
504
+
505
+ buffer_trim(&state);
506
+ int initial_pos = state.parser->pos;
507
+ INIT_PARSER_STATE(&state);
508
+ state.parser->current_request_rx = 0;
509
+
510
+ if (!parse_request_line(&state, state.parser->headers)) goto eof;
511
+
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++;
520
+ }
521
+ eof:
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;
531
+ }
532
+
533
+ ////////////////////////////////////////////////////////////////////////////////
534
+
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;
539
+
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
+ }
549
+
550
+ return int_value;
551
+ }
552
+
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;
555
+
556
+ VALUE body = Qnil;
557
+
558
+ int len = RSTRING_LEN(parser->buffer);
559
+ int pos = parser->pos;
560
+
561
+ if (pos < len) {
562
+ int available = len - pos;
563
+ if (available > parser->body_left) available = parser->body_left;
564
+ body = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
565
+ parser->pos += available;
566
+ parser->current_request_rx += available;
567
+ parser->body_left -= available;
568
+ if (!parser->body_left) parser->request_completed = 1;
569
+ }
570
+ else {
571
+ body = Qnil;
572
+ len = 0;
573
+ }
574
+ if (buffered_only) return body;
575
+
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)
581
+ rb_str_append(body, tmp_buf);
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;
623
+ }
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
+ }
638
+
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;
668
+ left -= read_bytes;
669
+ RB_GC_GUARD(tmp_buf);
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
+ }
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));
724
+ RB_GC_GUARD(body);
725
+ return body;
726
+ }
727
+
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
+ }
737
+
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;
745
+
746
+ }
747
+
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
+ }
777
+
778
+ void Init_HTTP1_Parser() {
779
+ VALUE mTipi;
780
+ VALUE cHTTP1Parser;
781
+
782
+ mTipi = rb_define_module("Tipi");
783
+ cHTTP1Parser = rb_define_class_under(mTipi, "HTTP1Parser", rb_cObject);
784
+ rb_define_alloc_func(cHTTP1Parser, Parser_allocate);
785
+
786
+ cError = rb_define_class_under(cHTTP1Parser, "Error", rb_eRuntimeError);
787
+
788
+ // backend methods
789
+ rb_define_method(cHTTP1Parser, "initialize", Parser_initialize, 1);
790
+ rb_define_method(cHTTP1Parser, "parse_headers", Parser_parse_headers, 0);
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);
809
+
810
+ GLOBAL_STR(STR_pseudo_method, ":method");
811
+ GLOBAL_STR(STR_pseudo_path, ":path");
812
+ GLOBAL_STR(STR_pseudo_protocol, ":protocol");
813
+ GLOBAL_STR(STR_pseudo_rx, ":rx");
814
+
815
+ GLOBAL_STR(STR_chunked, "chunked");
816
+ GLOBAL_STR(STR_content_length, "content-length");
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);
823
+ }