puma 8.0.3-java → 8.1.0-java
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.
- checksums.yaml +4 -4
- data/History.md +115 -0
- data/README.md +20 -5
- data/docs/6.0-Upgrade.md +1 -1
- data/docs/8.0-Upgrade.md +2 -2
- data/docs/deployment.md +3 -3
- data/docs/fork_worker.md +34 -4
- data/docs/kubernetes.md +3 -3
- data/docs/plugins.md +2 -2
- data/docs/puma_support.md +78 -0
- data/docs/rails_dev_mode.md +2 -2
- data/docs/restart.md +4 -3
- data/docs/signals.md +2 -2
- data/docs/stats.md +5 -5
- data/docs/systemd.md +4 -2
- data/docs/testing_benchmarks_local_files.md +1 -1
- data/docs/testing_test_rackup_ci_files.md +2 -2
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +6 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -1
- data/ext/puma_http11/mini_ssl.c +8 -4
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +36 -36
- data/ext/puma_http11/puma_http11.c +24 -0
- data/lib/puma/binder.rb +29 -0
- data/lib/puma/cli.rb +6 -0
- data/lib/puma/client.rb +75 -29
- data/lib/puma/client_env.rb +32 -18
- data/lib/puma/cluster/worker.rb +109 -33
- data/lib/puma/cluster/worker_handle.rb +16 -0
- data/lib/puma/cluster.rb +173 -44
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +22 -3
- data/lib/puma/const.rb +11 -9
- data/lib/puma/dsl.rb +265 -91
- data/lib/puma/launcher.rb +6 -4
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/plugin/systemd.rb +1 -1
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/response.rb +13 -5
- data/lib/puma/runner.rb +5 -1
- data/lib/puma/sd_notify.rb +4 -1
- data/lib/puma/server.rb +45 -20
- data/lib/puma/thread_pool.rb +12 -1
- data/lib/puma.rb +1 -1
- metadata +3 -2
|
@@ -7,7 +7,7 @@ and by the type of object used for the body.
|
|
|
7
7
|
|
|
8
8
|
Five rackup files are located in 'test/rackup' that can be used. All have their
|
|
9
9
|
request body size (in kB) set via `Body-Conf` header or with `ENV['CI_BODY_CONF']`.
|
|
10
|
-
Additionally, the ci_select.ru file can have
|
|
10
|
+
Additionally, the ci_select.ru file can have its body type set via a starting
|
|
11
11
|
character.
|
|
12
12
|
|
|
13
13
|
* **ci_array.ru** - body is an `Array` of 1kB strings. `Content-Length` is not set.
|
|
@@ -21,7 +21,7 @@ contain 1kB items.
|
|
|
21
21
|
|
|
22
22
|
All can be delayed by a float value (seconds) specified by the `Dly` header
|
|
23
23
|
|
|
24
|
-
Note that
|
|
24
|
+
Note that the `Body-Conf` header takes precedence, and `ENV['CI_BODY_CONF']` is
|
|
25
25
|
only read on load.
|
|
26
26
|
|
|
27
27
|
## ci_select.ru
|
data/ext/puma_http11/extconf.rb
CHANGED
|
@@ -592,6 +592,10 @@ case 24:
|
|
|
592
592
|
case 60: goto st0;
|
|
593
593
|
case 62: goto st0;
|
|
594
594
|
case 63: goto tr39;
|
|
595
|
+
case 92: goto st0;
|
|
596
|
+
case 96: goto st0;
|
|
597
|
+
case 123: goto st0;
|
|
598
|
+
case 125: goto st0;
|
|
595
599
|
case 127: goto st0;
|
|
596
600
|
}
|
|
597
601
|
if ( 0 <= (*p) && (*p) <= 31 )
|
|
@@ -607,7 +611,7 @@ st25:
|
|
|
607
611
|
if ( ++p == pe )
|
|
608
612
|
goto _test_eof25;
|
|
609
613
|
case 25:
|
|
610
|
-
#line
|
|
614
|
+
#line 615 "ext/puma_http11/http11_parser.c"
|
|
611
615
|
switch( (*p) ) {
|
|
612
616
|
case 32: goto tr41;
|
|
613
617
|
case 34: goto st0;
|
|
@@ -627,7 +631,7 @@ st26:
|
|
|
627
631
|
if ( ++p == pe )
|
|
628
632
|
goto _test_eof26;
|
|
629
633
|
case 26:
|
|
630
|
-
#line
|
|
634
|
+
#line 635 "ext/puma_http11/http11_parser.c"
|
|
631
635
|
switch( (*p) ) {
|
|
632
636
|
case 32: goto tr44;
|
|
633
637
|
case 34: goto st0;
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
escape = ("%" xdigit xdigit);
|
|
18
18
|
uchar = (unreserved | escape | "%");
|
|
19
19
|
pchar = (uchar | ":" | "@" | "&" | "=" | "+" | ";");
|
|
20
|
+
# RFC 3986 disallows these in paths; browsers never send them unescaped there.
|
|
21
|
+
path_unwise = ("{" | "}" | "`" | "\\");
|
|
22
|
+
path_char = pchar -- path_unwise;
|
|
20
23
|
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
|
21
24
|
|
|
22
25
|
# elements
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
scheme = ( alpha | digit | "+" | "-" | "." )* ;
|
|
27
30
|
absolute_uri = (scheme ":" (uchar | reserved )*);
|
|
28
31
|
|
|
29
|
-
path = (
|
|
32
|
+
path = ( path_char+ ( "/" path_char* )* ) ;
|
|
30
33
|
query = ( uchar | reserved )* %query_string ;
|
|
31
34
|
param = ( pchar | "/" )* ;
|
|
32
35
|
params = ( param ( ";" param )* ) ;
|
data/ext/puma_http11/mini_ssl.c
CHANGED
|
@@ -228,7 +228,7 @@ sslctx_alloc(VALUE klass) {
|
|
|
228
228
|
VALUE
|
|
229
229
|
sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
230
230
|
SSL_CTX* ctx;
|
|
231
|
-
int ssl_options;
|
|
231
|
+
int ssl_options, private_key_result;
|
|
232
232
|
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, ssl_ciphersuites, no_tlsv1, no_tlsv1_1,
|
|
233
233
|
verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
|
|
234
234
|
BIO *bio;
|
|
@@ -292,15 +292,19 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
|
292
292
|
StringValue(key_password);
|
|
293
293
|
password_cb = password_callback;
|
|
294
294
|
password = RSTRING_PTR(key_password);
|
|
295
|
-
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
|
|
296
|
-
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
|
|
297
295
|
}
|
|
298
296
|
}
|
|
299
297
|
|
|
300
298
|
if (!NIL_P(key)) {
|
|
301
299
|
StringValue(key);
|
|
302
300
|
|
|
303
|
-
|
|
301
|
+
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
|
|
302
|
+
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
|
|
303
|
+
private_key_result = SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);
|
|
304
|
+
SSL_CTX_set_default_passwd_cb(ctx, NULL);
|
|
305
|
+
SSL_CTX_set_default_passwd_cb_userdata(ctx, NULL);
|
|
306
|
+
|
|
307
|
+
if (private_key_result != 1) {
|
|
304
308
|
raise_file_error("SSL_CTX_use_PrivateKey_file", RSTRING_PTR(key));
|
|
305
309
|
}
|
|
306
310
|
}
|
|
@@ -36,8 +36,8 @@ private static short[] init__puma_parser_key_offsets_0()
|
|
|
36
36
|
return new short [] {
|
|
37
37
|
0, 0, 8, 17, 27, 29, 30, 31, 32, 33, 34, 36,
|
|
38
38
|
39, 41, 44, 45, 61, 62, 78, 85, 91, 99, 107, 117,
|
|
39
|
-
125,
|
|
40
|
-
|
|
39
|
+
125, 138, 146, 154, 163, 172, 181, 190, 199, 208, 217, 226,
|
|
40
|
+
235, 244, 253, 262, 271, 280, 289, 298, 307, 316, 317
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -57,23 +57,23 @@ private static char[] init__puma_parser_trans_keys_0()
|
|
|
57
57
|
31, 13, 127, 0, 8, 10, 31, 32, 60, 62, 127, 0,
|
|
58
58
|
31, 34, 35, 32, 60, 62, 127, 0, 31, 34, 35, 43,
|
|
59
59
|
58, 45, 46, 48, 57, 65, 90, 97, 122, 32, 34, 35,
|
|
60
|
-
60, 62, 127, 0, 31, 32, 34, 35, 60, 62, 63,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
57, 65, 90, 32, 36, 95, 45, 46,
|
|
64
|
-
32, 36, 95, 45, 46, 48, 57, 65,
|
|
65
|
-
45, 46, 48, 57, 65, 90, 32, 36,
|
|
66
|
-
57, 65, 90, 32, 36, 95, 45, 46,
|
|
67
|
-
32, 36, 95, 45, 46, 48, 57, 65,
|
|
68
|
-
45, 46, 48, 57, 65, 90, 32, 36,
|
|
69
|
-
57, 65, 90, 32, 36, 95, 45, 46,
|
|
70
|
-
32, 36, 95, 45, 46, 48, 57, 65,
|
|
71
|
-
45, 46, 48, 57, 65, 90, 32, 36,
|
|
72
|
-
57, 65, 90, 32, 36, 95, 45, 46,
|
|
73
|
-
32, 36, 95, 45, 46, 48, 57, 65,
|
|
74
|
-
45, 46, 48, 57, 65, 90, 32, 36,
|
|
75
|
-
57, 65, 90, 32, 36, 95, 45, 46,
|
|
76
|
-
32, 0
|
|
60
|
+
60, 62, 127, 0, 31, 32, 34, 35, 60, 62, 63, 92,
|
|
61
|
+
96, 123, 125, 127, 0, 31, 32, 34, 35, 60, 62, 127,
|
|
62
|
+
0, 31, 32, 34, 35, 60, 62, 127, 0, 31, 32, 36,
|
|
63
|
+
95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46,
|
|
64
|
+
48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65,
|
|
65
|
+
90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36,
|
|
66
|
+
95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46,
|
|
67
|
+
48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65,
|
|
68
|
+
90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36,
|
|
69
|
+
95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46,
|
|
70
|
+
48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65,
|
|
71
|
+
90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36,
|
|
72
|
+
95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46,
|
|
73
|
+
48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65,
|
|
74
|
+
90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36,
|
|
75
|
+
95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46,
|
|
76
|
+
48, 57, 65, 90, 32, 0
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -85,7 +85,7 @@ private static byte[] init__puma_parser_single_lengths_0()
|
|
|
85
85
|
return new byte [] {
|
|
86
86
|
0, 2, 3, 4, 2, 1, 1, 1, 1, 1, 0, 1,
|
|
87
87
|
0, 1, 1, 4, 1, 4, 3, 2, 4, 4, 2, 6,
|
|
88
|
-
|
|
88
|
+
11, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
89
89
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0
|
|
90
90
|
};
|
|
91
91
|
}
|
|
@@ -111,8 +111,8 @@ private static short[] init__puma_parser_index_offsets_0()
|
|
|
111
111
|
return new short [] {
|
|
112
112
|
0, 0, 6, 13, 21, 24, 26, 28, 30, 32, 34, 36,
|
|
113
113
|
39, 41, 44, 46, 57, 59, 70, 76, 81, 88, 95, 102,
|
|
114
|
-
110,
|
|
115
|
-
|
|
114
|
+
110, 123, 131, 139, 146, 153, 160, 167, 174, 181, 188, 195,
|
|
115
|
+
202, 209, 216, 223, 230, 237, 244, 251, 258, 265, 267
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -131,20 +131,20 @@ private static byte[] init__puma_parser_indicies_0()
|
|
|
131
131
|
1, 1, 1, 25, 29, 1, 1, 1, 28, 30, 1, 1,
|
|
132
132
|
1, 1, 1, 31, 32, 1, 1, 1, 1, 1, 33, 34,
|
|
133
133
|
35, 34, 34, 34, 34, 1, 8, 1, 9, 1, 1, 1,
|
|
134
|
-
1, 35, 36, 1, 38, 1, 1, 39, 1, 1,
|
|
135
|
-
1, 42, 1, 1, 1, 1, 41, 43,
|
|
136
|
-
1, 1, 44, 2, 46, 46, 46, 46,
|
|
137
|
-
47, 47, 47, 47, 1, 2, 48, 48,
|
|
138
|
-
|
|
139
|
-
50, 1, 2, 51, 51, 51, 51, 51,
|
|
140
|
-
|
|
141
|
-
54, 54, 54, 54, 54, 1, 2, 55,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
59, 59, 59, 59, 1, 2, 60, 60,
|
|
145
|
-
|
|
146
|
-
62, 1, 2, 63, 63, 63, 63, 63,
|
|
147
|
-
0
|
|
134
|
+
1, 35, 36, 1, 38, 1, 1, 39, 1, 1, 1, 1,
|
|
135
|
+
1, 1, 37, 40, 1, 42, 1, 1, 1, 1, 41, 43,
|
|
136
|
+
1, 45, 1, 1, 1, 1, 44, 2, 46, 46, 46, 46,
|
|
137
|
+
46, 1, 2, 47, 47, 47, 47, 47, 1, 2, 48, 48,
|
|
138
|
+
48, 48, 48, 1, 2, 49, 49, 49, 49, 49, 1, 2,
|
|
139
|
+
50, 50, 50, 50, 50, 1, 2, 51, 51, 51, 51, 51,
|
|
140
|
+
1, 2, 52, 52, 52, 52, 52, 1, 2, 53, 53, 53,
|
|
141
|
+
53, 53, 1, 2, 54, 54, 54, 54, 54, 1, 2, 55,
|
|
142
|
+
55, 55, 55, 55, 1, 2, 56, 56, 56, 56, 56, 1,
|
|
143
|
+
2, 57, 57, 57, 57, 57, 1, 2, 58, 58, 58, 58,
|
|
144
|
+
58, 1, 2, 59, 59, 59, 59, 59, 1, 2, 60, 60,
|
|
145
|
+
60, 60, 60, 1, 2, 61, 61, 61, 61, 61, 1, 2,
|
|
146
|
+
62, 62, 62, 62, 62, 1, 2, 63, 63, 63, 63, 63,
|
|
147
|
+
1, 2, 1, 1, 0
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
#include <ctype.h>
|
|
14
14
|
#include "http11_parser.h"
|
|
15
15
|
|
|
16
|
+
#ifdef HAVE_PRCTL
|
|
17
|
+
#include <sys/prctl.h>
|
|
18
|
+
#endif
|
|
19
|
+
|
|
16
20
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
|
17
21
|
|
|
18
22
|
static VALUE eHttpParserError;
|
|
@@ -460,6 +464,24 @@ static VALUE HttpParser_body(VALUE self) {
|
|
|
460
464
|
return http->body;
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
/**
|
|
468
|
+
* call-seq:
|
|
469
|
+
* Puma.enable_child_subreaper -> true or false
|
|
470
|
+
*
|
|
471
|
+
* Marks the calling process as a child subreaper so that orphaned descendants
|
|
472
|
+
* are reparented to it. Returns false where this is unsupported.
|
|
473
|
+
*/
|
|
474
|
+
static VALUE Puma_enable_child_subreaper(VALUE self) {
|
|
475
|
+
#ifdef HAVE_PRCTL
|
|
476
|
+
if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
|
|
477
|
+
rb_sys_fail("prctl(PR_SET_CHILD_SUBREAPER)");
|
|
478
|
+
}
|
|
479
|
+
return Qtrue;
|
|
480
|
+
#else
|
|
481
|
+
return Qfalse;
|
|
482
|
+
#endif
|
|
483
|
+
}
|
|
484
|
+
|
|
463
485
|
#ifdef HAVE_OPENSSL_BIO_H
|
|
464
486
|
void Init_mini_ssl(VALUE mod);
|
|
465
487
|
#endif
|
|
@@ -483,6 +505,8 @@ RUBY_FUNC_EXPORTED void Init_puma_http11(void)
|
|
|
483
505
|
rb_global_variable(&eHttpParserError);
|
|
484
506
|
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
|
485
507
|
|
|
508
|
+
rb_define_singleton_method(mPuma, "enable_child_subreaper", Puma_enable_child_subreaper, 0);
|
|
509
|
+
|
|
486
510
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
|
487
511
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
|
488
512
|
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
data/lib/puma/binder.rb
CHANGED
|
@@ -309,6 +309,13 @@ module Puma
|
|
|
309
309
|
end
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
+
# Removes and returns the activated socket for +url+ as <tt>[key, socket]</tt>,
|
|
313
|
+
# or +nil+ if none was activated for it.
|
|
314
|
+
def take_activated_socket(url)
|
|
315
|
+
key = activated_keys_for(url).find { |k| @activated_sockets.key?(k) }
|
|
316
|
+
[key, @activated_sockets.delete(key)] if key
|
|
317
|
+
end
|
|
318
|
+
|
|
312
319
|
def localhost_authority
|
|
313
320
|
@localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
|
|
314
321
|
end
|
|
@@ -486,6 +493,28 @@ module Puma
|
|
|
486
493
|
|
|
487
494
|
private
|
|
488
495
|
|
|
496
|
+
def activated_keys_for(url)
|
|
497
|
+
uri = URI.parse url
|
|
498
|
+
case uri.scheme
|
|
499
|
+
when "tcp", "ssl"
|
|
500
|
+
[[:tcp, uri.host, uri.port]]
|
|
501
|
+
when "unix"
|
|
502
|
+
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
|
503
|
+
return [] if path.start_with?("@")
|
|
504
|
+
|
|
505
|
+
real_path = begin
|
|
506
|
+
File.realdirpath(path)
|
|
507
|
+
rescue SystemCallError
|
|
508
|
+
path
|
|
509
|
+
end
|
|
510
|
+
[[:unix, path], [:unix, real_path]].uniq
|
|
511
|
+
else
|
|
512
|
+
[]
|
|
513
|
+
end
|
|
514
|
+
rescue URI::InvalidURIError
|
|
515
|
+
[]
|
|
516
|
+
end
|
|
517
|
+
|
|
489
518
|
# @!attribute [r] loopback_addresses
|
|
490
519
|
def loopback_addresses
|
|
491
520
|
t = Socket.ip_address_list.select do |addrinfo|
|
data/lib/puma/cli.rb
CHANGED
|
@@ -138,6 +138,12 @@ module Puma
|
|
|
138
138
|
user_config.fork_worker(*args.compact)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
+
o.on "--mold-worker=[REQUESTS]", Array,
|
|
142
|
+
"Fork new workers from a mold promoted from an existing worker. Cluster mode only",
|
|
143
|
+
"Promote a new mold at each comma-separated REQUESTS threshold (default 1000)" do |thresholds|
|
|
144
|
+
user_config.mold_worker(*thresholds)
|
|
145
|
+
end
|
|
146
|
+
|
|
141
147
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
|
142
148
|
$LOAD_PATH.unshift(*arg.split(':'))
|
|
143
149
|
end
|
data/lib/puma/client.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative 'detect'
|
|
4
4
|
require_relative 'io_buffer'
|
|
5
5
|
require_relative 'client_env'
|
|
6
|
+
require_relative 'null_io'
|
|
6
7
|
require 'tempfile'
|
|
7
8
|
|
|
8
9
|
if Puma::IS_JRUBY
|
|
@@ -18,6 +19,17 @@ module Puma
|
|
|
18
19
|
|
|
19
20
|
class HttpParserError501 < IOError; end
|
|
20
21
|
|
|
22
|
+
# Raised when a +continue_callback+ answers an <tt>Expect: 100-continue</tt>
|
|
23
|
+
# request itself, so the request body is never read.
|
|
24
|
+
class ContinueRejected < StandardError
|
|
25
|
+
attr_reader :response
|
|
26
|
+
|
|
27
|
+
def initialize(response)
|
|
28
|
+
super("Expect: 100-continue answered by continue_callback")
|
|
29
|
+
@response = response
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
21
33
|
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
|
22
34
|
|
|
23
35
|
|
|
@@ -46,12 +58,12 @@ module Puma
|
|
|
46
58
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
|
47
59
|
|
|
48
60
|
# chunked body validation
|
|
49
|
-
|
|
61
|
+
CHUNK_SIZE_VALID = /\A\h+\z/.freeze
|
|
50
62
|
CHUNK_VALID_ENDING = Const::LINE_END
|
|
51
63
|
CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
|
|
52
64
|
|
|
53
65
|
# The maximum number of bytes we'll buffer looking for a valid
|
|
54
|
-
# chunk header.
|
|
66
|
+
# chunk header, and the maximum size of a trailer section.
|
|
55
67
|
MAX_CHUNK_HEADER_SIZE = 4096
|
|
56
68
|
|
|
57
69
|
SHUTDOWN_READ_GRACE = 1
|
|
@@ -78,7 +90,8 @@ module Puma
|
|
|
78
90
|
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
79
91
|
:requests_served, :error_status_code, :bytes_read
|
|
80
92
|
|
|
81
|
-
attr_writer :peerip, :http_content_length_limit, :supported_http_methods
|
|
93
|
+
attr_writer :peerip, :http_content_length_limit, :supported_http_methods,
|
|
94
|
+
:allow_underscore_headers, :continue_callback
|
|
82
95
|
|
|
83
96
|
attr_accessor :remote_addr_header, :listener, :env_set_http_version,
|
|
84
97
|
:reactor_error
|
|
@@ -108,6 +121,8 @@ module Puma
|
|
|
108
121
|
|
|
109
122
|
@http_content_length_limit = nil
|
|
110
123
|
@http_content_length_limit_exceeded = nil
|
|
124
|
+
@allow_underscore_headers = true
|
|
125
|
+
@continue_callback = nil
|
|
111
126
|
@error_status_code = nil
|
|
112
127
|
@reactor_error = nil
|
|
113
128
|
|
|
@@ -354,6 +369,8 @@ module Puma
|
|
|
354
369
|
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
355
370
|
|
|
356
371
|
req, _ = @buffer.split "\r\n\r\n"
|
|
372
|
+
raise e if req.nil? || req.empty?
|
|
373
|
+
|
|
357
374
|
request_line, headers = req.split "\r\n", 2
|
|
358
375
|
|
|
359
376
|
# below checks for request issues and changes error message accordingly
|
|
@@ -384,6 +401,7 @@ module Puma
|
|
|
384
401
|
|
|
385
402
|
# processes the `env` and the request body
|
|
386
403
|
def process_env_body
|
|
404
|
+
validate_env
|
|
387
405
|
temp = setup_body
|
|
388
406
|
normalize_env
|
|
389
407
|
req_env_post_parse
|
|
@@ -496,10 +514,21 @@ module Puma
|
|
|
496
514
|
# @return [Boolean] true if the body can be completely read, false otherwise
|
|
497
515
|
#
|
|
498
516
|
def setup_body
|
|
517
|
+
# The parsers preserve underscores as commas to distinguish these aliases
|
|
518
|
+
# from real framing headers. Reject them before 100 Continue or body reads,
|
|
519
|
+
# regardless of allow_underscore_headers, to avoid proxy disagreements.
|
|
520
|
+
if @env.key?('HTTP_CONTENT,LENGTH') || @env.key?('HTTP_TRANSFER,ENCODING')
|
|
521
|
+
@error_status_code = 400
|
|
522
|
+
@env[HTTP_CONNECTION] = CLOSE
|
|
523
|
+
raise HttpParserError, 'Underscores are not allowed in HTTP framing headers'
|
|
524
|
+
end
|
|
525
|
+
|
|
499
526
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
500
527
|
|
|
501
528
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
502
|
-
|
|
529
|
+
if @continue_callback && (response = @continue_callback.call(@env))
|
|
530
|
+
reject_continue response
|
|
531
|
+
end
|
|
503
532
|
write_interim HTTP_11_100
|
|
504
533
|
end
|
|
505
534
|
|
|
@@ -704,6 +733,8 @@ module Puma
|
|
|
704
733
|
end
|
|
705
734
|
|
|
706
735
|
def decode_chunk(chunk)
|
|
736
|
+
return finish_last_chunk(@prev_chunk + chunk) if @in_last_chunk
|
|
737
|
+
|
|
707
738
|
if @partial_part_left > 0
|
|
708
739
|
if @partial_part_left <= chunk.size
|
|
709
740
|
if @partial_part_left > 2
|
|
@@ -739,31 +770,18 @@ module Puma
|
|
|
739
770
|
# Puma doesn't process chunk extensions, but should parse if they're
|
|
740
771
|
# present, which is the reason for the semicolon regex
|
|
741
772
|
chunk_hex = line.strip[/\A[^;]+/]
|
|
742
|
-
|
|
743
|
-
|
|
773
|
+
unless CHUNK_SIZE_VALID.match? chunk_hex
|
|
774
|
+
err_msg = chunk_hex && !chunk_hex.empty? ?
|
|
775
|
+
"Invalid chunk size: '#{chunk_hex}'" :
|
|
776
|
+
"Chunk size cannot be empty or nil"
|
|
777
|
+
raise HttpParserError, err_msg
|
|
744
778
|
end
|
|
779
|
+
|
|
745
780
|
len = chunk_hex.to_i(16)
|
|
746
781
|
if len == 0
|
|
747
782
|
@in_last_chunk = true
|
|
748
783
|
@body.rewind
|
|
749
|
-
|
|
750
|
-
if rest.bytesize < CHUNK_VALID_ENDING_SIZE
|
|
751
|
-
@buffer = nil
|
|
752
|
-
@partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
|
|
753
|
-
return false
|
|
754
|
-
else
|
|
755
|
-
# if the next character is a CRLF, set buffer to everything after that CRLF
|
|
756
|
-
start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
|
|
757
|
-
CHUNK_VALID_ENDING_SIZE
|
|
758
|
-
else # we have started a trailer section, which we do not support. skip it!
|
|
759
|
-
rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
@buffer = rest[start_of_rest..-1]
|
|
763
|
-
@buffer = nil if @buffer.empty?
|
|
764
|
-
set_ready
|
|
765
|
-
return true
|
|
766
|
-
end
|
|
784
|
+
return finish_last_chunk(io.read)
|
|
767
785
|
end
|
|
768
786
|
|
|
769
787
|
# Track the excess as a function of the size of the
|
|
@@ -816,12 +834,33 @@ module Puma
|
|
|
816
834
|
end
|
|
817
835
|
end
|
|
818
836
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
837
|
+
false
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# Consume the terminator that ends a chunked body. Per RFC 9112 §7.1.2
|
|
841
|
+
# this is either a bare CRLF or an optional trailer section ending in
|
|
842
|
+
# CRLF*2. Puma does not surface trailers, so we skip past them. Bytes
|
|
843
|
+
# past the terminator belong to the next pipelined request.
|
|
844
|
+
def finish_last_chunk(rest)
|
|
845
|
+
if rest.start_with?(CHUNK_VALID_ENDING)
|
|
846
|
+
trailer_end = CHUNK_VALID_ENDING_SIZE
|
|
847
|
+
elsif idx = rest.index(CHUNK_VALID_ENDING * 2)
|
|
848
|
+
trailer_end = idx + CHUNK_VALID_ENDING_SIZE * 2
|
|
849
|
+
elsif rest.bytesize < MAX_CHUNK_HEADER_SIZE
|
|
850
|
+
@prev_chunk = rest
|
|
851
|
+
return false
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
if trailer_end.nil? || trailer_end > MAX_CHUNK_HEADER_SIZE
|
|
855
|
+
@env[HTTP_CONNECTION] = CLOSE
|
|
856
|
+
raise HttpParserError, "maximum size of chunk trailer exceeded"
|
|
824
857
|
end
|
|
858
|
+
|
|
859
|
+
@prev_chunk = ""
|
|
860
|
+
@buffer = rest[trailer_end..-1]
|
|
861
|
+
@buffer = nil if @buffer.empty?
|
|
862
|
+
set_ready
|
|
863
|
+
true
|
|
825
864
|
end
|
|
826
865
|
|
|
827
866
|
def set_ready
|
|
@@ -832,6 +871,13 @@ module Puma
|
|
|
832
871
|
@ready = true
|
|
833
872
|
end
|
|
834
873
|
|
|
874
|
+
def reject_continue(response)
|
|
875
|
+
@buffer = nil
|
|
876
|
+
@body = EmptyBody
|
|
877
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
878
|
+
raise ContinueRejected, response
|
|
879
|
+
end
|
|
880
|
+
|
|
835
881
|
def raise_above_http_content_limit
|
|
836
882
|
@http_content_length_limit_exceeded = true
|
|
837
883
|
@buffer = nil
|
data/lib/puma/client_env.rb
CHANGED
|
@@ -12,6 +12,22 @@ module Puma
|
|
|
12
12
|
|
|
13
13
|
include Puma::Const
|
|
14
14
|
|
|
15
|
+
def validate_env
|
|
16
|
+
return unless @env[SERVER_PROTOCOL] == HTTP_11
|
|
17
|
+
|
|
18
|
+
unless @env.key? HTTP_HOST
|
|
19
|
+
@error_status_code = 400
|
|
20
|
+
@env[HTTP_CONNECTION] = CLOSE
|
|
21
|
+
raise HttpParserError, "Missing Host header"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if @env[HTTP_HOST].empty?
|
|
25
|
+
@error_status_code = 400
|
|
26
|
+
@env[HTTP_CONNECTION] = CLOSE
|
|
27
|
+
raise HttpParserError, "Invalid Host header"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
15
31
|
# Given a Hash +env+ for the request read from +client+, add
|
|
16
32
|
# and fixup keys to comply with Rack's env guidelines.
|
|
17
33
|
# @param env [Hash] see Puma::Client#env, from request
|
|
@@ -105,9 +121,10 @@ module Puma
|
|
|
105
121
|
# avoid allocation in the common case (ie there are no headers
|
|
106
122
|
# with `,` in their names), that's why it has the extra conditionals.
|
|
107
123
|
#
|
|
108
|
-
# @note If
|
|
109
|
-
#
|
|
110
|
-
#
|
|
124
|
+
# @note If underscore headers are disallowed, `,` headers are discarded.
|
|
125
|
+
# Otherwise, if a normalized version of a `,` header already exists, we
|
|
126
|
+
# ignore the `,` version. This prevents clobbering headers managed by
|
|
127
|
+
# proxies but not by clients (Like X-Forwarded-For).
|
|
111
128
|
#
|
|
112
129
|
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
113
130
|
# @version 5.0.3
|
|
@@ -115,28 +132,25 @@ module Puma
|
|
|
115
132
|
def req_env_post_parse
|
|
116
133
|
to_delete = nil
|
|
117
134
|
to_add = nil
|
|
135
|
+
underscore_headers = nil
|
|
118
136
|
|
|
119
137
|
@env.each do |k,v|
|
|
120
|
-
|
|
121
|
-
if to_delete
|
|
122
|
-
to_delete << k
|
|
123
|
-
else
|
|
124
|
-
to_delete = [k]
|
|
125
|
-
end
|
|
138
|
+
next unless k.start_with?("HTTP_") && k.include?(",")
|
|
126
139
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
next
|
|
130
|
-
end
|
|
140
|
+
(underscore_headers ||= []) << k.delete_prefix("HTTP_").tr("_,", "-_")
|
|
141
|
+
next if @allow_underscore_headers && UNMASKABLE_HEADERS.key?(k)
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
143
|
+
(to_delete ||= []) << k
|
|
144
|
+
next unless @allow_underscore_headers
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
new_k = k.tr(",", "_")
|
|
147
|
+
next if @env.key?(new_k)
|
|
148
|
+
|
|
149
|
+
(to_add ||= {})[new_k] = v
|
|
138
150
|
end
|
|
139
151
|
|
|
152
|
+
@env[PUMA_UNDERSCORE_HEADERS] = underscore_headers if underscore_headers
|
|
153
|
+
|
|
140
154
|
if to_delete # rubocop:disable Style/SafeNavigation
|
|
141
155
|
to_delete.each { |k| env.delete(k) }
|
|
142
156
|
end
|