raptor 0.22.3 → 0.24.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Dockerfile +10 -2
  4. data/README.md +183 -61
  5. data/docs/assets/profile_picture.png +0 -0
  6. data/docs/assets/startup_output.svg +37 -0
  7. data/docs/raptor-vs-puma.md +104 -424
  8. data/docs/talk.md +1388 -0
  9. data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
  10. data/ext/raptor_http/extconf.rb +2 -0
  11. data/ext/raptor_http/raptor_http.c +165 -234
  12. data/ext/raptor_http2/raptor_http2.c +272 -223
  13. data/ext/raptor_native/raptor_native.c +38 -20
  14. data/lib/rackup/handler/raptor.rb +14 -12
  15. data/lib/raptor/binder.rb +12 -12
  16. data/lib/raptor/cli.rb +19 -18
  17. data/lib/raptor/cluster.rb +120 -74
  18. data/lib/raptor/control_server.rb +5 -2
  19. data/lib/raptor/detached_body.rb +6 -2
  20. data/lib/raptor/http.rb +7 -6
  21. data/lib/raptor/http1.rb +281 -234
  22. data/lib/raptor/http2.rb +356 -276
  23. data/lib/raptor/plugin.rb +98 -0
  24. data/lib/raptor/reactor.rb +138 -148
  25. data/lib/raptor/reuseport_bpf.rb +62 -38
  26. data/lib/raptor/server.rb +45 -34
  27. data/lib/raptor/stats.rb +15 -3
  28. data/lib/raptor/thread_locals.rb +4 -3
  29. data/lib/raptor/version.rb +1 -1
  30. data/sig/generated/rackup/handler/raptor.rbs +2 -1
  31. data/sig/generated/raptor/binder.rbs +9 -9
  32. data/sig/generated/raptor/cli.rbs +6 -6
  33. data/sig/generated/raptor/cluster.rbs +18 -5
  34. data/sig/generated/raptor/control_server.rbs +2 -1
  35. data/sig/generated/raptor/detached_body.rbs +5 -1
  36. data/sig/generated/raptor/http.rbs +3 -5
  37. data/sig/generated/raptor/http1.rbs +119 -102
  38. data/sig/generated/raptor/http2.rbs +204 -190
  39. data/sig/generated/raptor/plugin.rbs +52 -0
  40. data/sig/generated/raptor/reactor.rbs +107 -118
  41. data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
  42. data/sig/generated/raptor/server.rbs +26 -26
  43. data/sig/generated/raptor/stats.rbs +3 -2
  44. data/sig/generated/raptor/thread_locals.rbs +4 -3
  45. metadata +6 -2
  46. data/docs/brisrails-talk.md +0 -2398
@@ -6,15 +6,24 @@
6
6
  #include "ruby.h"
7
7
  #include "ruby/encoding.h"
8
8
  #include <assert.h>
9
- #include <string.h>
10
9
  #include <ctype.h>
10
+ #include <stdint.h>
11
+ #include <string.h>
11
12
 
12
13
  #define MAX_HEADER_LENGTH (112 * 1024)
13
14
  #define MAX_URI_LENGTH (12 * 1024)
14
15
  #define MAX_FIELD_NAME 256
15
16
  #define MAX_FIELD_VALUE (80 * 1024)
16
17
 
17
- typedef struct raptor_parser {
18
+ // Bounds each chunk-size line and the terminal chunk/trailer section, including
19
+ // CRLFs. Also limits cumulative nonterminal overhead beyond the payload size.
20
+ #define MAX_CHUNK_OVERHEAD (16 * 1024)
21
+
22
+ #define FLAG_CHUNKED 0x1
23
+ #define FLAG_HAS_BODY 0x2
24
+ #define FLAG_FINISHED 0x4
25
+
26
+ typedef struct {
18
27
  int cs;
19
28
  size_t mark;
20
29
  size_t field_start;
@@ -22,10 +31,11 @@ typedef struct raptor_parser {
22
31
  size_t query_start;
23
32
  size_t nread;
24
33
  size_t body_start;
25
- off_t content_len;
34
+ int64_t content_len;
26
35
  unsigned int flags;
27
36
  VALUE request;
28
- VALUE body;
37
+ VALUE underscored_headers;
38
+ // Scratch space for the env key: "HTTP_" prefix + field name + NUL.
29
39
  char buf[MAX_FIELD_NAME + 6];
30
40
  } raptor_parser;
31
41
 
@@ -33,33 +43,29 @@ typedef struct raptor_parser {
33
43
  #define LEN(AT, P) ((P) - buffer - parser->AT)
34
44
  #define PTR_TO(F) (buffer + parser->F)
35
45
 
36
- #define FLAG_CHUNKED 0x1
37
- #define FLAG_HAS_BODY 0x2
38
- #define FLAG_FINISHED 0x4
39
-
40
- #define MAX_CHUNK_OVERHEAD (16 * 1024)
41
-
42
46
  static VALUE eHttpParserError;
43
47
  static VALUE global_request_method;
44
48
  static VALUE global_request_uri;
45
49
  static VALUE global_query_string;
46
50
  static VALUE global_server_protocol;
47
- static VALUE global_request_path;
51
+ static VALUE global_path_info;
48
52
  static VALUE global_fragment;
49
53
  static ID id_complete;
50
54
  static ID id_incomplete;
51
55
  static ID id_malformed;
52
56
  static ID id_too_large;
53
57
 
54
- struct common_field {
58
+ typedef struct {
55
59
  const char *name;
56
60
  size_t len;
57
61
  VALUE interned;
58
- };
62
+ } common_field;
59
63
 
60
64
  #define FIELD(name) { name, sizeof(name) - 1, Qnil }
61
65
 
62
- static struct common_field common_fields[] = {
66
+ // Env keys for frequently seen headers, interned once at load time so parsing
67
+ // these headers does not need to look up the fstring table.
68
+ static common_field common_fields[] = {
63
69
  FIELD("HTTP_HOST"),
64
70
  FIELD("HTTP_USER_AGENT"),
65
71
  FIELD("HTTP_CONNECTION"),
@@ -111,7 +117,7 @@ static struct common_field common_fields[] = {
111
117
 
112
118
  #define NUM_COMMON_FIELDS (sizeof(common_fields) / sizeof(common_fields[0]))
113
119
 
114
- static VALUE raptor_http_intern_field(const char *buf, size_t len) {
120
+ static VALUE intern_field(const char *buf, size_t len) {
115
121
  for (size_t i = 0; i < NUM_COMMON_FIELDS; i++) {
116
122
  if (common_fields[i].len == len && memcmp(common_fields[i].name, buf, len) == 0) {
117
123
  return common_fields[i].interned;
@@ -120,13 +126,6 @@ static VALUE raptor_http_intern_field(const char *buf, size_t len) {
120
126
  return rb_enc_interned_str(buf, len, rb_utf8_encoding());
121
127
  }
122
128
 
123
- static inline void upcase_header_char(char *c) {
124
- if (*c >= 'a' && *c <= 'z')
125
- *c &= ~0x20;
126
- else if (*c == '-')
127
- *c = '_';
128
- }
129
-
130
129
  static int contains_chunked(const char *value, long len) {
131
130
  static const char chunked[] = "chunked";
132
131
  static const long chunked_len = sizeof(chunked) - 1;
@@ -142,11 +141,80 @@ static int contains_chunked(const char *value, long len) {
142
141
  return 0;
143
142
  }
144
143
 
144
+ // Ruby checks for non-digit characters when it validates request framing.
145
+ static int64_t parse_content_length(const char *value, long len) {
146
+ int64_t content_len = 0;
147
+ for (long i = 0; i < len && value[i] >= '0' && value[i] <= '9'; i++) {
148
+ int digit = value[i] - '0';
149
+ if (content_len > (INT64_MAX - digit) / 10)
150
+ rb_raise(eHttpParserError, "content length too large");
151
+ content_len = content_len * 10 + digit;
152
+ }
153
+ return content_len;
154
+ }
155
+
156
+ static void parse_header(raptor_parser *parser, const char *field, const char *value, size_t value_len) {
157
+ size_t field_len = parser->field_len;
158
+ if (field_len == 0 || field_len > MAX_FIELD_NAME)
159
+ rb_raise(eHttpParserError, "invalid field name length");
160
+ if (value_len > MAX_FIELD_VALUE)
161
+ rb_raise(eHttpParserError, "field value too long");
162
+
163
+ int underscored = 0;
164
+ char *normalized = parser->buf + 5;
165
+ for (size_t i = 0; i < field_len; i++) {
166
+ char c = field[i];
167
+ if (c == '_') underscored = 1;
168
+ normalized[i] = c == '-' ? '_' : (c >= 'a' && c <= 'z' ? c & ~0x20 : c);
169
+ }
170
+ int content_length = field_len == 14 && memcmp(normalized, "CONTENT_LENGTH", 14) == 0;
171
+ int content_type = field_len == 12 && memcmp(normalized, "CONTENT_TYPE", 12) == 0;
172
+ int transfer_encoding = field_len == 17 && memcmp(normalized, "TRANSFER_ENCODING", 17) == 0;
173
+ if (underscored && (content_length || transfer_encoding))
174
+ rb_raise(eHttpParserError, "invalid framing header name");
175
+
176
+ size_t key_len = field_len;
177
+ if (content_length || content_type) {
178
+ memmove(parser->buf, normalized, field_len);
179
+ } else {
180
+ memcpy(parser->buf, "HTTP_", 5);
181
+ key_len += 5;
182
+ }
183
+ VALUE key = intern_field(parser->buf, key_len);
184
+ VALUE existing = rb_hash_aref(parser->request, key);
185
+ int existing_underscored = !NIL_P(parser->underscored_headers) &&
186
+ RTEST(rb_hash_aref(parser->underscored_headers, key));
187
+ // Canonical names win in either order; true duplicates still merge.
188
+ if (underscored) {
189
+ if (!NIL_P(existing) && !existing_underscored) return;
190
+ if (NIL_P(parser->underscored_headers)) parser->underscored_headers = rb_hash_new();
191
+ rb_hash_aset(parser->underscored_headers, key, Qtrue);
192
+ } else if (existing_underscored) {
193
+ existing = Qnil;
194
+ rb_hash_delete(parser->underscored_headers, key);
195
+ }
196
+
197
+ while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t'))
198
+ value_len--;
199
+ if (content_length) {
200
+ parser->content_len = parse_content_length(value, (long)value_len);
201
+ if (parser->content_len > 0) parser->flags |= FLAG_HAS_BODY;
202
+ } else if (transfer_encoding && contains_chunked(value, (long)value_len)) {
203
+ parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
204
+ parser->content_len = 0;
205
+ }
206
+
207
+ if (!NIL_P(existing)) {
208
+ rb_str_cat(existing, ", ", 2);
209
+ rb_str_cat(existing, value, (long)value_len);
210
+ } else {
211
+ rb_hash_aset(parser->request, key, rb_str_new(value, (long)value_len));
212
+ }
213
+ }
214
+
145
215
  static const int raptor_parser_start = 1;
146
- static const int raptor_parser_first_final = 46;
147
- static const int raptor_parser_error = 0;
148
216
 
149
- size_t raptor_parser_execute(raptor_parser *parser, const char *buffer, size_t len) {
217
+ static size_t raptor_parser_execute(raptor_parser *parser, const char *buffer, size_t len) {
150
218
  const char *p, *pe;
151
219
  int cs = parser->cs;
152
220
 
@@ -257,7 +325,7 @@ tr33:
257
325
  tr37:
258
326
  {
259
327
  VALUE path = rb_str_new(PTR_TO(mark), LEN(mark,p));
260
- rb_hash_aset(parser->request, global_request_path, path);
328
+ rb_hash_aset(parser->request, global_path_info, path);
261
329
  }
262
330
  {
263
331
  if (LEN(mark, p) > MAX_URI_LENGTH)
@@ -370,122 +438,12 @@ tr18:
370
438
  tr26:
371
439
  { MARK(mark, p); }
372
440
  {
373
- if (parser->field_len == 0 || parser->field_len > MAX_FIELD_NAME)
374
- rb_raise(eHttpParserError, "invalid field name length");
375
-
376
- size_t value_len = LEN(mark, p);
377
- if (value_len > MAX_FIELD_VALUE)
378
- rb_raise(eHttpParserError, "field value too long");
379
-
380
- const char *field_ptr = PTR_TO(field_start);
381
-
382
- int needs_http_prefix = 1;
383
- if (parser->field_len == 14 && memcmp(field_ptr, "CONTENT_LENGTH", 14) == 0)
384
- needs_http_prefix = 0;
385
- else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
386
- needs_http_prefix = 0;
387
-
388
- size_t key_len;
389
- if (needs_http_prefix) {
390
- memcpy(parser->buf, "HTTP_", 5);
391
- memcpy(parser->buf + 5, field_ptr, parser->field_len);
392
- key_len = 5 + parser->field_len;
393
- parser->buf[key_len] = '\0';
394
- } else {
395
- memcpy(parser->buf, field_ptr, parser->field_len);
396
- key_len = parser->field_len;
397
- parser->buf[key_len] = '\0';
398
- }
399
-
400
- VALUE key = raptor_http_intern_field(parser->buf, key_len);
401
- VALUE value = rb_str_new(PTR_TO(mark), value_len);
402
-
403
- char *value_ptr = RSTRING_PTR(value);
404
- long value_real_len = RSTRING_LEN(value);
405
- while (value_real_len > 0 && (value_ptr[value_real_len - 1] == ' ' ||
406
- value_ptr[value_real_len - 1] == '\t'))
407
- value_real_len--;
408
- rb_str_set_len(value, value_real_len);
409
-
410
- if (!needs_http_prefix && parser->field_len == 14) {
411
- parser->content_len = strtoul(RSTRING_PTR(value), NULL, 10);
412
- if (parser->content_len > 0)
413
- parser->flags |= FLAG_HAS_BODY;
414
- } else if (needs_http_prefix && parser->field_len == 17 &&
415
- memcmp(field_ptr, "TRANSFER_ENCODING", 17) == 0) {
416
- if (contains_chunked(value_ptr, value_real_len)) {
417
- parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
418
- parser->content_len = 0;
419
- }
420
- }
421
-
422
- VALUE existing = rb_hash_aref(parser->request, key);
423
- if (!NIL_P(existing)) {
424
- rb_str_cat2(existing, ", ");
425
- rb_str_append(existing, value);
426
- } else {
427
- rb_hash_aset(parser->request, key, value);
428
- }
441
+ parse_header(parser, PTR_TO(field_start), PTR_TO(mark), LEN(mark, p));
429
442
  }
430
443
  goto st14;
431
444
  tr29:
432
445
  {
433
- if (parser->field_len == 0 || parser->field_len > MAX_FIELD_NAME)
434
- rb_raise(eHttpParserError, "invalid field name length");
435
-
436
- size_t value_len = LEN(mark, p);
437
- if (value_len > MAX_FIELD_VALUE)
438
- rb_raise(eHttpParserError, "field value too long");
439
-
440
- const char *field_ptr = PTR_TO(field_start);
441
-
442
- int needs_http_prefix = 1;
443
- if (parser->field_len == 14 && memcmp(field_ptr, "CONTENT_LENGTH", 14) == 0)
444
- needs_http_prefix = 0;
445
- else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
446
- needs_http_prefix = 0;
447
-
448
- size_t key_len;
449
- if (needs_http_prefix) {
450
- memcpy(parser->buf, "HTTP_", 5);
451
- memcpy(parser->buf + 5, field_ptr, parser->field_len);
452
- key_len = 5 + parser->field_len;
453
- parser->buf[key_len] = '\0';
454
- } else {
455
- memcpy(parser->buf, field_ptr, parser->field_len);
456
- key_len = parser->field_len;
457
- parser->buf[key_len] = '\0';
458
- }
459
-
460
- VALUE key = raptor_http_intern_field(parser->buf, key_len);
461
- VALUE value = rb_str_new(PTR_TO(mark), value_len);
462
-
463
- char *value_ptr = RSTRING_PTR(value);
464
- long value_real_len = RSTRING_LEN(value);
465
- while (value_real_len > 0 && (value_ptr[value_real_len - 1] == ' ' ||
466
- value_ptr[value_real_len - 1] == '\t'))
467
- value_real_len--;
468
- rb_str_set_len(value, value_real_len);
469
-
470
- if (!needs_http_prefix && parser->field_len == 14) {
471
- parser->content_len = strtoul(RSTRING_PTR(value), NULL, 10);
472
- if (parser->content_len > 0)
473
- parser->flags |= FLAG_HAS_BODY;
474
- } else if (needs_http_prefix && parser->field_len == 17 &&
475
- memcmp(field_ptr, "TRANSFER_ENCODING", 17) == 0) {
476
- if (contains_chunked(value_ptr, value_real_len)) {
477
- parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
478
- parser->content_len = 0;
479
- }
480
- }
481
-
482
- VALUE existing = rb_hash_aref(parser->request, key);
483
- if (!NIL_P(existing)) {
484
- rb_str_cat2(existing, ", ");
485
- rb_str_append(existing, value);
486
- } else {
487
- rb_hash_aset(parser->request, key, value);
488
- }
446
+ parse_header(parser, PTR_TO(field_start), PTR_TO(mark), LEN(mark, p));
489
447
  }
490
448
  goto st14;
491
449
  st14:
@@ -545,10 +503,8 @@ case 46:
545
503
  goto st0;
546
504
  tr21:
547
505
  { MARK(field_start, p); }
548
- { upcase_header_char((char *)p); }
549
506
  goto st17;
550
507
  tr23:
551
- { upcase_header_char((char *)p); }
552
508
  goto st17;
553
509
  st17:
554
510
  if ( ++p == pe )
@@ -591,6 +547,7 @@ st18:
591
547
  goto _test_eof18;
592
548
  case 18:
593
549
  switch( (*p) ) {
550
+ case 9: goto tr27;
594
551
  case 13: goto tr26;
595
552
  case 32: goto tr27;
596
553
  case 127: goto st0;
@@ -629,7 +586,7 @@ tr9:
629
586
  tr38:
630
587
  {
631
588
  VALUE path = rb_str_new(PTR_TO(mark), LEN(mark,p));
632
- rb_hash_aset(parser->request, global_request_path, path);
589
+ rb_hash_aset(parser->request, global_path_info, path);
633
590
  }
634
591
  {
635
592
  if (LEN(mark, p) > MAX_URI_LENGTH)
@@ -761,7 +718,7 @@ case 24:
761
718
  tr39:
762
719
  {
763
720
  VALUE path = rb_str_new(PTR_TO(mark), LEN(mark,p));
764
- rb_hash_aset(parser->request, global_request_path, path);
721
+ rb_hash_aset(parser->request, global_path_info, path);
765
722
  }
766
723
  goto st25;
767
724
  st25:
@@ -1193,31 +1150,7 @@ done:
1193
1150
  return parser->nread;
1194
1151
  }
1195
1152
 
1196
- int raptor_parser_finished(raptor_parser *parser) {
1197
- return (parser->flags & FLAG_FINISHED) != 0;
1198
- }
1199
-
1200
- int raptor_parser_has_body(raptor_parser *parser) {
1201
- return (parser->flags & FLAG_HAS_BODY) != 0;
1202
- }
1203
-
1204
- size_t raptor_parser_content_length(raptor_parser *parser) {
1205
- return parser->content_len;
1206
- }
1207
-
1208
- int raptor_parser_is_chunked(raptor_parser *parser) {
1209
- return (parser->flags & FLAG_CHUNKED) != 0;
1210
- }
1211
-
1212
- int raptor_parser_has_error(raptor_parser *parser) {
1213
- return parser->cs == raptor_parser_error;
1214
- }
1215
-
1216
- int raptor_parser_is_finished(raptor_parser *parser) {
1217
- return parser->cs >= raptor_parser_first_final;
1218
- }
1219
-
1220
- void raptor_parser_init(raptor_parser *parser) {
1153
+ static void raptor_parser_init(raptor_parser *parser) {
1221
1154
  parser->cs = raptor_parser_start;
1222
1155
  parser->mark = 0;
1223
1156
  parser->field_start = 0;
@@ -1228,19 +1161,13 @@ void raptor_parser_init(raptor_parser *parser) {
1228
1161
  parser->content_len = 0;
1229
1162
  parser->flags = 0;
1230
1163
  parser->request = Qnil;
1231
- parser->body = Qnil;
1164
+ parser->underscored_headers = Qnil;
1232
1165
  }
1233
1166
 
1234
- static VALUE cHttpParser;
1235
-
1236
1167
  static void parser_mark(void *ptr) {
1237
1168
  raptor_parser *parser = ptr;
1238
1169
  rb_gc_mark(parser->request);
1239
- rb_gc_mark(parser->body);
1240
- }
1241
-
1242
- static void parser_free(void *ptr) {
1243
- xfree(ptr);
1170
+ rb_gc_mark(parser->underscored_headers);
1244
1171
  }
1245
1172
 
1246
1173
  static size_t parser_memsize(const void *ptr) {
@@ -1248,9 +1175,13 @@ static size_t parser_memsize(const void *ptr) {
1248
1175
  }
1249
1176
 
1250
1177
  static const rb_data_type_t parser_type = {
1251
- "raptor_http_parser",
1252
- { parser_mark, parser_free, parser_memsize },
1253
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
1178
+ .wrap_struct_name = "raptor_http_parser",
1179
+ .function = {
1180
+ .dmark = parser_mark,
1181
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
1182
+ .dsize = parser_memsize,
1183
+ },
1184
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
1254
1185
  };
1255
1186
 
1256
1187
  static VALUE parser_alloc(VALUE klass) {
@@ -1282,31 +1213,25 @@ static VALUE parser_execute(VALUE self, VALUE req_hash, VALUE buffer, VALUE star
1282
1213
  static VALUE parser_finished_p(VALUE self) {
1283
1214
  raptor_parser *parser;
1284
1215
  TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1285
- return raptor_parser_finished(parser) ? Qtrue : Qfalse;
1216
+ return (parser->flags & FLAG_FINISHED) ? Qtrue : Qfalse;
1286
1217
  }
1287
1218
 
1288
1219
  static VALUE parser_has_body_p(VALUE self) {
1289
1220
  raptor_parser *parser;
1290
1221
  TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1291
- return raptor_parser_has_body(parser) ? Qtrue : Qfalse;
1222
+ return (parser->flags & FLAG_HAS_BODY) ? Qtrue : Qfalse;
1292
1223
  }
1293
1224
 
1294
1225
  static VALUE parser_chunked_p(VALUE self) {
1295
1226
  raptor_parser *parser;
1296
1227
  TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1297
- return raptor_parser_is_chunked(parser) ? Qtrue : Qfalse;
1228
+ return (parser->flags & FLAG_CHUNKED) ? Qtrue : Qfalse;
1298
1229
  }
1299
1230
 
1300
1231
  static VALUE parser_content_length(VALUE self) {
1301
1232
  raptor_parser *parser;
1302
1233
  TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1303
- return SIZET2NUM(raptor_parser_content_length(parser));
1304
- }
1305
-
1306
- static VALUE parser_nread(VALUE self) {
1307
- raptor_parser *parser;
1308
- TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1309
- return SIZET2NUM(parser->nread);
1234
+ return LL2NUM(parser->content_len);
1310
1235
  }
1311
1236
 
1312
1237
  static VALUE parser_reset(VALUE self) {
@@ -1316,12 +1241,6 @@ static VALUE parser_reset(VALUE self) {
1316
1241
  return Qnil;
1317
1242
  }
1318
1243
 
1319
- static VALUE parser_body(VALUE self) {
1320
- raptor_parser *parser;
1321
- TypedData_Get_Struct(self, raptor_parser, &parser_type, parser);
1322
- return parser->body;
1323
- }
1324
-
1325
1244
  static const char *find_crlf(const char *buf, size_t len) {
1326
1245
  if (len < 2) return NULL;
1327
1246
  const char *end = buf + len - 1;
@@ -1331,8 +1250,10 @@ static const char *find_crlf(const char *buf, size_t len) {
1331
1250
  return NULL;
1332
1251
  }
1333
1252
 
1253
+ // Decodes a chunked body into [decoded, state], appending the number of bytes
1254
+ // the body consumed when state is :complete so the caller can find any
1255
+ // pipelined request that follows it.
1334
1256
  static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
1335
- (void)self;
1336
1257
  VALUE buffer, max_size;
1337
1258
  rb_scan_args(argc, argv, "11", &buffer, &max_size);
1338
1259
  Check_Type(buffer, T_STRING);
@@ -1348,6 +1269,9 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
1348
1269
 
1349
1270
  while (offset < len) {
1350
1271
  const char *crlf = find_crlf(buf + offset, len - offset);
1272
+ size_t line_bytes = crlf ? (size_t)(crlf - (buf + offset)) + 2 : len - offset;
1273
+ if (line_bytes > MAX_CHUNK_OVERHEAD)
1274
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
1351
1275
  if (!crlf) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1352
1276
 
1353
1277
  size_t line_len = (size_t)(crlf - (buf + offset));
@@ -1377,8 +1301,11 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
1377
1301
  while (1) {
1378
1302
  if (trailer_offset >= len) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1379
1303
  const char *tcrlf = find_crlf(buf + trailer_offset, len - trailer_offset);
1304
+ size_t terminal_bytes = tcrlf ? (size_t)(tcrlf - (buf + offset)) + 2 : len - offset;
1305
+ if (terminal_bytes > MAX_CHUNK_OVERHEAD)
1306
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
1380
1307
  if (!tcrlf) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1381
- if (tcrlf == buf + trailer_offset) return rb_ary_new_from_args(2, decoded, ID2SYM(id_complete));
1308
+ if (tcrlf == buf + trailer_offset) return rb_ary_new_from_args(3, decoded, ID2SYM(id_complete), SIZET2NUM(trailer_offset + 2));
1382
1309
  trailer_offset = (size_t)(tcrlf - buf) + 2;
1383
1310
  }
1384
1311
  }
@@ -1398,12 +1325,23 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
1398
1325
  size_t bytes_to_copy = (size_t)chunk_size < available ? (size_t)chunk_size : available;
1399
1326
  rb_str_cat(decoded, buf + data_offset, (long)bytes_to_copy);
1400
1327
 
1328
+ if (available <= (size_t)chunk_size)
1329
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1330
+ if (buf[data_offset + (size_t)chunk_size] != '\r')
1331
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
1332
+ if (available == (size_t)chunk_size + 1)
1333
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1334
+ if (buf[data_offset + (size_t)chunk_size + 1] != '\n')
1335
+ return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
1336
+
1401
1337
  offset = data_offset + (size_t)chunk_size + 2;
1402
1338
  }
1403
1339
 
1404
1340
  return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
1405
1341
  }
1406
1342
 
1343
+ // One bit per byte value. Header names reject C0 controls, space, DEL and
1344
+ // separators; header values reject C0 controls other than horizontal tab.
1407
1345
  static uint8_t illegal_header_key_bitmap[32];
1408
1346
  static uint8_t illegal_header_value_bitmap[32];
1409
1347
 
@@ -1411,6 +1349,16 @@ static void bitmap_set(uint8_t *bitmap, unsigned char byte) {
1411
1349
  bitmap[byte >> 3] |= (uint8_t)(1u << (byte & 7));
1412
1350
  }
1413
1351
 
1352
+ static void init_illegal_header_bitmaps(void) {
1353
+ for (int byte = 0x00; byte <= 0x20; byte++) bitmap_set(illegal_header_key_bitmap, (unsigned char)byte);
1354
+ const char *key_specials = "()<>@,;:\\\"/[]?={}";
1355
+ for (const char *p = key_specials; *p; p++) bitmap_set(illegal_header_key_bitmap, (unsigned char)*p);
1356
+ bitmap_set(illegal_header_key_bitmap, 0x7F);
1357
+
1358
+ for (int byte = 0x00; byte <= 0x08; byte++) bitmap_set(illegal_header_value_bitmap, (unsigned char)byte);
1359
+ for (int byte = 0x0A; byte <= 0x1F; byte++) bitmap_set(illegal_header_value_bitmap, (unsigned char)byte);
1360
+ }
1361
+
1414
1362
  static int contains_illegal_byte(const uint8_t *bitmap, const char *str, size_t len) {
1415
1363
  for (size_t i = 0; i < len; i++) {
1416
1364
  unsigned char byte = (unsigned char)str[i];
@@ -1420,13 +1368,11 @@ static int contains_illegal_byte(const uint8_t *bitmap, const char *str, size_t
1420
1368
  }
1421
1369
 
1422
1370
  static VALUE parser_illegal_header_key_p(VALUE self, VALUE key) {
1423
- (void)self;
1424
1371
  Check_Type(key, T_STRING);
1425
1372
  return contains_illegal_byte(illegal_header_key_bitmap, RSTRING_PTR(key), (size_t)RSTRING_LEN(key)) ? Qtrue : Qfalse;
1426
1373
  }
1427
1374
 
1428
1375
  static VALUE parser_illegal_header_value_p(VALUE self, VALUE value) {
1429
- (void)self;
1430
1376
  Check_Type(value, T_STRING);
1431
1377
  return contains_illegal_byte(illegal_header_value_bitmap, RSTRING_PTR(value), (size_t)RSTRING_LEN(value)) ? Qtrue : Qfalse;
1432
1378
  }
@@ -1442,7 +1388,7 @@ static void emit_header_line(VALUE buffer, const char *name, long name_len,
1442
1388
  }
1443
1389
 
1444
1390
  static void emit_header(VALUE buffer, const char *name, long name_len, VALUE value) {
1445
- VALUE str = RB_TYPE_P(value, T_STRING) ? value : rb_funcall(value, rb_intern("to_s"), 0);
1391
+ VALUE str = rb_obj_as_string(value);
1446
1392
  const char *ptr = RSTRING_PTR(str);
1447
1393
  long len = RSTRING_LEN(str);
1448
1394
  if (len == 0) return;
@@ -1459,13 +1405,10 @@ static void emit_header(VALUE buffer, const char *name, long name_len, VALUE val
1459
1405
  line_start = i + 1;
1460
1406
  }
1461
1407
  }
1462
- if (line_start < len) {
1463
- emit_header_line(buffer, name, name_len, ptr + line_start, len - line_start);
1464
- }
1408
+ if (line_start < len) emit_header_line(buffer, name, name_len, ptr + line_start, len - line_start);
1465
1409
  }
1466
1410
 
1467
- static int format_header_iter(VALUE key, VALUE value, VALUE data) {
1468
- VALUE buffer = (VALUE)data;
1411
+ static int format_header_iter(VALUE key, VALUE value, VALUE buffer) {
1469
1412
  if (!RB_TYPE_P(key, T_STRING)) return ST_CONTINUE;
1470
1413
  const char *name = RSTRING_PTR(key);
1471
1414
  long name_len = RSTRING_LEN(key);
@@ -1483,7 +1426,6 @@ static int format_header_iter(VALUE key, VALUE value, VALUE data) {
1483
1426
  }
1484
1427
 
1485
1428
  static VALUE parser_format_headers(VALUE self, VALUE buffer, VALUE headers) {
1486
- (void)self;
1487
1429
  Check_Type(buffer, T_STRING);
1488
1430
  Check_Type(headers, T_HASH);
1489
1431
  rb_hash_foreach(headers, format_header_iter, buffer);
@@ -1491,7 +1433,6 @@ static VALUE parser_format_headers(VALUE self, VALUE buffer, VALUE headers) {
1491
1433
  }
1492
1434
 
1493
1435
  static VALUE parser_chunked_encode(VALUE self, VALUE buffer, VALUE chunk) {
1494
- (void)self;
1495
1436
  Check_Type(buffer, T_STRING);
1496
1437
  Check_Type(chunk, T_STRING);
1497
1438
  long chunk_len = RSTRING_LEN(chunk);
@@ -1506,36 +1447,27 @@ static VALUE parser_chunked_encode(VALUE self, VALUE buffer, VALUE chunk) {
1506
1447
  return buffer;
1507
1448
  }
1508
1449
 
1509
- static void init_illegal_header_bitmaps(void) {
1510
- for (int byte = 0x00; byte <= 0x20; byte++) bitmap_set(illegal_header_key_bitmap, (unsigned char)byte);
1511
- const char *key_specials = "()<>@,;:\\\"/[]?={}";
1512
- for (const char *p = key_specials; *p; p++) bitmap_set(illegal_header_key_bitmap, (unsigned char)*p);
1513
- bitmap_set(illegal_header_key_bitmap, 0x7F);
1514
-
1515
- for (int byte = 0x00; byte <= 0x08; byte++) bitmap_set(illegal_header_value_bitmap, (unsigned char)byte);
1516
- for (int byte = 0x0A; byte <= 0x1F; byte++) bitmap_set(illegal_header_value_bitmap, (unsigned char)byte);
1517
- }
1518
-
1519
1450
  RUBY_FUNC_EXPORTED void Init_raptor_http(void) {
1520
1451
  rb_ext_ractor_safe(true);
1521
1452
 
1522
- VALUE mRaptor = rb_define_module("Raptor");
1523
- cHttpParser = rb_define_class_under(mRaptor, "HttpParser", rb_cObject);
1524
- eHttpParserError = rb_define_class_under(mRaptor, "HttpParserError", rb_eStandardError);
1525
-
1526
1453
  rb_global_variable(&global_request_method);
1527
1454
  rb_global_variable(&global_request_uri);
1528
1455
  rb_global_variable(&global_query_string);
1529
1456
  rb_global_variable(&global_server_protocol);
1530
- rb_global_variable(&global_request_path);
1457
+ rb_global_variable(&global_path_info);
1531
1458
  rb_global_variable(&global_fragment);
1532
1459
 
1533
- global_request_method = rb_str_new2("REQUEST_METHOD");
1534
- global_request_uri = rb_str_new2("REQUEST_URI");
1535
- global_query_string = rb_str_new2("QUERY_STRING");
1536
- global_server_protocol = rb_str_new2("SERVER_PROTOCOL");
1537
- global_request_path = rb_str_new2("PATH_INFO");
1538
- global_fragment = rb_str_new2("FRAGMENT");
1460
+ global_request_method = rb_str_new_lit("REQUEST_METHOD");
1461
+ global_request_uri = rb_str_new_lit("REQUEST_URI");
1462
+ global_query_string = rb_str_new_lit("QUERY_STRING");
1463
+ global_server_protocol = rb_str_new_lit("SERVER_PROTOCOL");
1464
+ global_path_info = rb_str_new_lit("PATH_INFO");
1465
+ global_fragment = rb_str_new_lit("FRAGMENT");
1466
+
1467
+ for (size_t i = 0; i < NUM_COMMON_FIELDS; i++) {
1468
+ rb_global_variable(&common_fields[i].interned);
1469
+ common_fields[i].interned = rb_enc_interned_str(common_fields[i].name, common_fields[i].len, rb_utf8_encoding());
1470
+ }
1539
1471
 
1540
1472
  id_complete = rb_intern("complete");
1541
1473
  id_incomplete = rb_intern("incomplete");
@@ -1544,10 +1476,11 @@ RUBY_FUNC_EXPORTED void Init_raptor_http(void) {
1544
1476
 
1545
1477
  init_illegal_header_bitmaps();
1546
1478
 
1547
- for (size_t i = 0; i < NUM_COMMON_FIELDS; i++) {
1548
- common_fields[i].interned = rb_enc_interned_str(common_fields[i].name, common_fields[i].len, rb_utf8_encoding());
1549
- rb_global_variable(&common_fields[i].interned);
1550
- }
1479
+ VALUE mRaptor = rb_define_module("Raptor");
1480
+ VALUE cHttpParser = rb_define_class_under(mRaptor, "HttpParser", rb_cObject);
1481
+ eHttpParserError = rb_define_class_under(mRaptor, "HttpParserError", rb_eStandardError);
1482
+
1483
+ rb_define_const(cHttpParser, "MAX_CHUNK_OVERHEAD", INT2FIX(MAX_CHUNK_OVERHEAD));
1551
1484
 
1552
1485
  rb_define_alloc_func(cHttpParser, parser_alloc);
1553
1486
  rb_define_method(cHttpParser, "execute", parser_execute, 3);
@@ -1555,13 +1488,11 @@ RUBY_FUNC_EXPORTED void Init_raptor_http(void) {
1555
1488
  rb_define_method(cHttpParser, "has_body?", parser_has_body_p, 0);
1556
1489
  rb_define_method(cHttpParser, "chunked?", parser_chunked_p, 0);
1557
1490
  rb_define_method(cHttpParser, "content_length", parser_content_length, 0);
1558
- rb_define_method(cHttpParser, "nread", parser_nread, 0);
1559
1491
  rb_define_method(cHttpParser, "reset", parser_reset, 0);
1560
- rb_define_method(cHttpParser, "body", parser_body, 0);
1492
+
1561
1493
  rb_define_singleton_method(cHttpParser, "decode_chunked", parser_decode_chunked, -1);
1562
1494
  rb_define_singleton_method(cHttpParser, "illegal_header_key?", parser_illegal_header_key_p, 1);
1563
1495
  rb_define_singleton_method(cHttpParser, "illegal_header_value?", parser_illegal_header_value_p, 1);
1564
1496
  rb_define_singleton_method(cHttpParser, "format_headers", parser_format_headers, 2);
1565
1497
  rb_define_singleton_method(cHttpParser, "chunked_encode", parser_chunked_encode, 2);
1566
- rb_define_const(cHttpParser, "MAX_CHUNK_OVERHEAD", INT2FIX(MAX_CHUNK_OVERHEAD));
1567
1498
  }