puma 5.6.1 → 5.6.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +55 -3
- data/ext/puma_http11/extconf.rb +8 -3
- data/ext/puma_http11/mini_ssl.c +28 -10
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +10 -12
- data/lib/puma/app/status.rb +3 -0
- data/lib/puma/binder.rb +1 -1
- data/lib/puma/client.rb +54 -11
- data/lib/puma/const.rb +6 -4
- data/lib/puma/control_cli.rb +18 -12
- data/lib/puma/dsl.rb +6 -2
- data/lib/puma/launcher.rb +11 -0
- data/lib/puma/minissl.rb +9 -4
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/request.rb +15 -6
- data/lib/puma/server.rb +5 -0
- data/lib/puma/state_file.rb +1 -0
- data/lib/puma/util.rb +12 -3
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7be1244aa7c9d74f0021e1763e05b7220ceb3630b41ef7cf2205b71d5f5cf494
|
4
|
+
data.tar.gz: 802a80a1437d272cfbd101be2fa5370860fd3745b17996661db5eaa47b98b0e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f99a9be986d9c7d617b7dbbdae9072e183b7dc957df74f353b110223f1194350a4d87614869aaaae133c737c2ddb12633989850adee14c97e088db830a6e5754
|
7
|
+
data.tar.gz: 684325223794be8efc7adf39c7eba75bb326bd3646a15aca28ebe92e83b5c836706ca37aeb6c5ec14bb4c9fcdb74ceb8c3b6d146ec862fb5008337c5430b66cc
|
data/History.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
## 5.6.5 / 2022-08-23
|
2
|
+
|
3
|
+
* Bugfixes
|
4
|
+
* NullIO#closed should return false ([#2883])
|
5
|
+
* Puma::ControlCLI - allow refork command to be sent as a request ([#2868], [#2866])
|
6
|
+
* [jruby] Fix TLS verification hang ([#2890], [#2729])
|
7
|
+
* extconf.rb - don't use pkg_config('openssl') if '--with-openssl-dir' is used ([#2885], [#2839])
|
8
|
+
* MiniSSL - detect SSL_CTX_set_dh_auto ([#2864], [#2863])
|
9
|
+
* Fix rack.after_reply exceptions breaking connections ([#2861], [#2856])
|
10
|
+
* Escape SSL cert and filenames ([#2855])
|
11
|
+
* Fail hard if SSL certs or keys are invalid ([#2848])
|
12
|
+
* Fail hard if SSL certs or keys cannot be read by user ([#2847])
|
13
|
+
* Fix build with Opaque DH in LibreSSL 3.5. ([#2838])
|
14
|
+
* Pre-existing socket file removed when TERM is issued after USR2 (if puma is running in cluster mode) ([#2817])
|
15
|
+
* Fix Puma::StateFile#load incompatibility ([#2810])
|
16
|
+
|
17
|
+
## 5.6.4 / 2022-03-30
|
18
|
+
|
19
|
+
* Security
|
20
|
+
* Close several HTTP Request Smuggling exploits (CVE-2022-24790)
|
21
|
+
|
22
|
+
## 5.6.2 / 2022-02-11
|
23
|
+
|
24
|
+
* Bugfix/Security
|
25
|
+
* Response body will always be `close`d. (GHSA-rmj8-8hhh-gv5h, related to [#2809])
|
26
|
+
|
1
27
|
## 5.6.1 / 2022-01-26
|
2
28
|
|
3
29
|
* Bugfixes
|
@@ -1835,6 +1861,32 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1835
1861
|
* Bugfixes
|
1836
1862
|
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
1837
1863
|
|
1864
|
+
[#2883]:https://github.com/puma/puma/pull/2883 "PR by @MSP-Greg, merged 2022-06-02"
|
1865
|
+
[#2868]:https://github.com/puma/puma/pull/2868 "PR by @MSP-Greg, merged 2022-06-02"
|
1866
|
+
[#2866]:https://github.com/puma/puma/issues/2866 "Issue by @slondr, closed 2022-06-02"
|
1867
|
+
[#2888]:https://github.com/puma/puma/pull/2888 "PR by @MSP-Greg, merged 2022-06-01"
|
1868
|
+
[#2890]:https://github.com/puma/puma/pull/2890 "PR by @kares, merged 2022-06-01"
|
1869
|
+
[#2729]:https://github.com/puma/puma/issues/2729 "Issue by @kares, closed 2022-06-01"
|
1870
|
+
[#2885]:https://github.com/puma/puma/pull/2885 "PR by @MSP-Greg, merged 2022-05-30"
|
1871
|
+
[#2839]:https://github.com/puma/puma/issues/2839 "Issue by @wlipa, closed 2022-05-30"
|
1872
|
+
[#2882]:https://github.com/puma/puma/pull/2882 "PR by @MSP-Greg, merged 2022-05-19"
|
1873
|
+
[#2864]:https://github.com/puma/puma/pull/2864 "PR by @MSP-Greg, merged 2022-04-26"
|
1874
|
+
[#2863]:https://github.com/puma/puma/issues/2863 "Issue by @eradman, closed 2022-04-26"
|
1875
|
+
[#2861]:https://github.com/puma/puma/pull/2861 "PR by @BlakeWilliams, merged 2022-04-17"
|
1876
|
+
[#2856]:https://github.com/puma/puma/issues/2856 "Issue by @nateberkopec, closed 2022-04-17"
|
1877
|
+
[#2855]:https://github.com/puma/puma/pull/2855 "PR by @stanhu, merged 2022-04-09"
|
1878
|
+
[#2848]:https://github.com/puma/puma/pull/2848 "PR by @stanhu, merged 2022-04-02"
|
1879
|
+
[#2847]:https://github.com/puma/puma/pull/2847 "PR by @stanhu, merged 2022-04-02"
|
1880
|
+
[#2838]:https://github.com/puma/puma/pull/2838 "PR by @epsilon-0, merged 2022-03-03"
|
1881
|
+
[#2817]:https://github.com/puma/puma/pull/2817 "PR by @khustochka, merged 2022-02-20"
|
1882
|
+
[#2810]:https://github.com/puma/puma/pull/2810 "PR by @kzkn, merged 2022-01-27"
|
1883
|
+
[#2899]:https://github.com/puma/puma/pull/2899 "PR by @kares, merged 2022-07-04"
|
1884
|
+
[#2891]:https://github.com/puma/puma/pull/2891 "PR by @gingerlime, merged 2022-06-02"
|
1885
|
+
[#2886]:https://github.com/puma/puma/pull/2886 "PR by @kares, merged 2022-05-30"
|
1886
|
+
[#2884]:https://github.com/puma/puma/pull/2884 "PR by @kares, merged 2022-05-30"
|
1887
|
+
[#2875]:https://github.com/puma/puma/pull/2875 "PR by @ylecuyer, merged 2022-05-19"
|
1888
|
+
[#2840]:https://github.com/puma/puma/pull/2840 "PR by @LukaszMaslej, merged 2022-04-13"
|
1889
|
+
[#2849]:https://github.com/puma/puma/pull/2849 "PR by @kares, merged 2022-04-09"
|
1838
1890
|
[#2809]:https://github.com/puma/puma/pull/2809 "PR by @dentarg, merged 2022-01-26"
|
1839
1891
|
[#2764]:https://github.com/puma/puma/pull/2764 "PR by @dentarg, merged 2022-01-18"
|
1840
1892
|
[#2708]:https://github.com/puma/puma/issues/2708 "Issue by @erikaxel, closed 2022-01-18"
|
@@ -1920,7 +1972,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1920
1972
|
[#2519]:https://github.com/puma/puma/pull/2519 "PR by @MSP-Greg, merged 2021-01-26"
|
1921
1973
|
[#2522]:https://github.com/puma/puma/pull/2522 "PR by @jcmfernandes, merged 2021-01-12"
|
1922
1974
|
[#2490]:https://github.com/puma/puma/pull/2490 "PR by @Bonias, merged 2020-12-07"
|
1923
|
-
[#2486]:https://github.com/puma/puma/pull/2486 "PR by @
|
1975
|
+
[#2486]:https://github.com/puma/puma/pull/2486 "PR by @karloscodes, merged 2020-12-02"
|
1924
1976
|
[#2535]:https://github.com/puma/puma/pull/2535 "PR by @MSP-Greg, merged 2021-01-27"
|
1925
1977
|
[#2529]:https://github.com/puma/puma/pull/2529 "PR by @MSP-Greg, merged 2021-01-24"
|
1926
1978
|
[#2533]:https://github.com/puma/puma/pull/2533 "PR by @MSP-Greg, merged 2021-01-24"
|
@@ -1930,7 +1982,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1930
1982
|
[#2521]:https://github.com/puma/puma/pull/2521 "PR by @ojab, merged 2021-01-04"
|
1931
1983
|
[#2531]:https://github.com/puma/puma/pull/2531 "PR by @wjordan, merged 2021-01-19"
|
1932
1984
|
[#2510]:https://github.com/puma/puma/pull/2510 "PR by @micke, merged 2020-12-10"
|
1933
|
-
[#2472]:https://github.com/puma/puma/pull/2472 "PR by @
|
1985
|
+
[#2472]:https://github.com/puma/puma/pull/2472 "PR by @karloscodes, merged 2020-11-02"
|
1934
1986
|
[#2438]:https://github.com/puma/puma/pull/2438 "PR by @ekohl, merged 2020-10-26"
|
1935
1987
|
[#2406]:https://github.com/puma/puma/pull/2406 "PR by @fdel15, merged 2020-10-19"
|
1936
1988
|
[#2449]:https://github.com/puma/puma/pull/2449 "PR by @MSP-Greg, merged 2020-10-28"
|
@@ -2357,7 +2409,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
2357
2409
|
[#709]:https://github.com/puma/puma/pull/709 "PR by @lian, merged 2015-06-10"
|
2358
2410
|
[#711]:https://github.com/puma/puma/pull/711 "PR by @julik, merged 2015-06-10"
|
2359
2411
|
[#712]:https://github.com/puma/puma/pull/712 "PR by @chewi, merged 2015-07-14"
|
2360
|
-
[#715]:https://github.com/puma/puma/pull/715 "PR by @
|
2412
|
+
[#715]:https://github.com/puma/puma/pull/715 "PR by @raymondmars, merged 2015-07-14"
|
2361
2413
|
[#725]:https://github.com/puma/puma/pull/725 "PR by @rwz, merged 2015-07-14"
|
2362
2414
|
[#726]:https://github.com/puma/puma/pull/726 "PR by @jshafton, merged 2015-07-14"
|
2363
2415
|
[#729]:https://github.com/puma/puma/pull/729 "PR by @allaire, merged 2015-07-14"
|
data/ext/puma_http11/extconf.rb
CHANGED
@@ -9,9 +9,11 @@ if $mingw && RUBY_VERSION >= '2.4'
|
|
9
9
|
end
|
10
10
|
|
11
11
|
unless ENV["DISABLE_SSL"]
|
12
|
-
|
12
|
+
# don't use pkg_config('openssl') if '--with-openssl-dir' is used
|
13
|
+
has_openssl_dir = dir_config('openssl').any?
|
14
|
+
found_pkg_config = !has_openssl_dir && pkg_config('openssl')
|
13
15
|
|
14
|
-
found_ssl = if (!$mingw || RUBY_VERSION >= '2.4') &&
|
16
|
+
found_ssl = if (!$mingw || RUBY_VERSION >= '2.4') && found_pkg_config
|
15
17
|
puts 'using OpenSSL pkgconfig (openssl.pc)'
|
16
18
|
true
|
17
19
|
elsif %w'crypto libeay32'.find {|crypto| have_library(crypto, 'BIO_read')} &&
|
@@ -35,7 +37,10 @@ unless ENV["DISABLE_SSL"]
|
|
35
37
|
have_func "X509_STORE_up_ref"
|
36
38
|
have_func "SSL_CTX_set_ecdh_auto(NULL, 0)" , "openssl/ssl.h"
|
37
39
|
|
38
|
-
# below
|
40
|
+
# below exists in 1.1.0 and later, but isn't documented until 3.0.0
|
41
|
+
have_func "SSL_CTX_set_dh_auto(NULL, 0)" , "openssl/ssl.h"
|
42
|
+
|
43
|
+
# below is yes for 3.0.0 & later
|
39
44
|
have_func "SSL_get1_peer_certificate" , "openssl/ssl.h"
|
40
45
|
|
41
46
|
# Random.bytes available in Ruby 2.5 and later, Random::DEFAULT deprecated in 3.0
|
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -30,6 +30,12 @@ typedef struct {
|
|
30
30
|
|
31
31
|
VALUE eError;
|
32
32
|
|
33
|
+
NORETURN(void raise_file_error(const char* caller, const char *filename));
|
34
|
+
|
35
|
+
void raise_file_error(const char* caller, const char *filename) {
|
36
|
+
rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
|
37
|
+
}
|
38
|
+
|
33
39
|
void engine_free(void *ptr) {
|
34
40
|
ms_conn *conn = ptr;
|
35
41
|
ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
|
@@ -49,7 +55,7 @@ const rb_data_type_t engine_data_type = {
|
|
49
55
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
|
50
56
|
};
|
51
57
|
|
52
|
-
#ifndef
|
58
|
+
#ifndef HAVE_SSL_CTX_SET_DH_AUTO
|
53
59
|
DH *get_dh2048(void) {
|
54
60
|
/* `openssl dhparam -C 2048`
|
55
61
|
* -----BEGIN DH PARAMETERS-----
|
@@ -92,13 +98,13 @@ DH *get_dh2048(void) {
|
|
92
98
|
static unsigned char dh2048_g[] = { 0x02 };
|
93
99
|
|
94
100
|
DH *dh;
|
95
|
-
#if !(OPENSSL_VERSION_NUMBER < 0x10100005L
|
101
|
+
#if !(OPENSSL_VERSION_NUMBER < 0x10100005L)
|
96
102
|
BIGNUM *p, *g;
|
97
103
|
#endif
|
98
104
|
|
99
105
|
dh = DH_new();
|
100
106
|
|
101
|
-
#if OPENSSL_VERSION_NUMBER < 0x10100005L
|
107
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100005L
|
102
108
|
dh->p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL);
|
103
109
|
dh->g = BN_bin2bn(dh2048_g, sizeof(dh2048_g), NULL);
|
104
110
|
|
@@ -211,7 +217,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
211
217
|
int ssl_options;
|
212
218
|
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
|
213
219
|
verification_flags, session_id_bytes, cert_pem, key_pem;
|
214
|
-
#ifndef
|
220
|
+
#ifndef HAVE_SSL_CTX_SET_DH_AUTO
|
215
221
|
DH *dh;
|
216
222
|
#endif
|
217
223
|
BIO *bio;
|
@@ -244,12 +250,18 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
244
250
|
|
245
251
|
if (!NIL_P(cert)) {
|
246
252
|
StringValue(cert);
|
247
|
-
|
253
|
+
|
254
|
+
if (SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert)) != 1) {
|
255
|
+
raise_file_error("SSL_CTX_use_certificate_chain_file", RSTRING_PTR(cert));
|
256
|
+
}
|
248
257
|
}
|
249
258
|
|
250
259
|
if (!NIL_P(key)) {
|
251
260
|
StringValue(key);
|
252
|
-
|
261
|
+
|
262
|
+
if (SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM) != 1) {
|
263
|
+
raise_file_error("SSL_CTX_use_PrivateKey_file", RSTRING_PTR(key));
|
264
|
+
}
|
253
265
|
}
|
254
266
|
|
255
267
|
if (!NIL_P(cert_pem)) {
|
@@ -257,7 +269,9 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
257
269
|
BIO_puts(bio, RSTRING_PTR(cert_pem));
|
258
270
|
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
259
271
|
|
260
|
-
SSL_CTX_use_certificate(ctx, x509)
|
272
|
+
if (SSL_CTX_use_certificate(ctx, x509) != 1) {
|
273
|
+
raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
|
274
|
+
}
|
261
275
|
}
|
262
276
|
|
263
277
|
if (!NIL_P(key_pem)) {
|
@@ -265,7 +279,9 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
265
279
|
BIO_puts(bio, RSTRING_PTR(key_pem));
|
266
280
|
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
267
281
|
|
268
|
-
SSL_CTX_use_PrivateKey(ctx, pkey)
|
282
|
+
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
|
283
|
+
raise_file_error("SSL_CTX_use_PrivateKey", RSTRING_PTR(key_pem));
|
284
|
+
}
|
269
285
|
}
|
270
286
|
|
271
287
|
verification_flags = rb_funcall(mini_ssl_ctx, rb_intern_const("verification_flags"), 0);
|
@@ -278,7 +294,9 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
278
294
|
|
279
295
|
if (!NIL_P(ca)) {
|
280
296
|
StringValue(ca);
|
281
|
-
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL)
|
297
|
+
if (SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL) != 1) {
|
298
|
+
raise_file_error("SSL_CTX_load_verify_locations", RSTRING_PTR(ca));
|
299
|
+
}
|
282
300
|
}
|
283
301
|
|
284
302
|
ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
|
@@ -355,7 +373,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
355
373
|
|
356
374
|
// printf("\ninitialize end security_level %d\n", SSL_CTX_get_security_level(ctx));
|
357
375
|
|
358
|
-
#ifdef
|
376
|
+
#ifdef HAVE_SSL_CTX_SET_DH_AUTO
|
359
377
|
// https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_dh_auto.html
|
360
378
|
SSL_CTX_set_dh_auto(ctx, 1);
|
361
379
|
#else
|
@@ -279,14 +279,6 @@ public class MiniSSL extends RubyObject {
|
|
279
279
|
}
|
280
280
|
}
|
281
281
|
|
282
|
-
// after each op, run any delegated tasks if needed
|
283
|
-
if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
284
|
-
Runnable runnable;
|
285
|
-
while ((runnable = engine.getDelegatedTask()) != null) {
|
286
|
-
runnable.run();
|
287
|
-
}
|
288
|
-
}
|
289
|
-
|
290
282
|
return res;
|
291
283
|
}
|
292
284
|
|
@@ -304,11 +296,12 @@ public class MiniSSL extends RubyObject {
|
|
304
296
|
|
305
297
|
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
306
298
|
boolean done = false;
|
307
|
-
SSLEngineResult res = null;
|
308
299
|
while (!done) {
|
300
|
+
SSLEngineResult res;
|
309
301
|
switch (handshakeStatus) {
|
310
302
|
case NEED_WRAP:
|
311
303
|
res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
304
|
+
handshakeStatus = res.getHandshakeStatus();
|
312
305
|
break;
|
313
306
|
case NEED_UNWRAP:
|
314
307
|
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
@@ -316,13 +309,18 @@ public class MiniSSL extends RubyObject {
|
|
316
309
|
// need more data before we can shake more hands
|
317
310
|
done = true;
|
318
311
|
}
|
312
|
+
handshakeStatus = res.getHandshakeStatus();
|
313
|
+
break;
|
314
|
+
case NEED_TASK:
|
315
|
+
Runnable runnable;
|
316
|
+
while ((runnable = engine.getDelegatedTask()) != null) {
|
317
|
+
runnable.run();
|
318
|
+
}
|
319
|
+
handshakeStatus = engine.getHandshakeStatus();
|
319
320
|
break;
|
320
321
|
default:
|
321
322
|
done = true;
|
322
323
|
}
|
323
|
-
if (!done) {
|
324
|
-
handshakeStatus = res.getHandshakeStatus();
|
325
|
-
}
|
326
324
|
}
|
327
325
|
|
328
326
|
if (inboundNetData.hasRemaining()) {
|
data/lib/puma/app/status.rb
CHANGED
data/lib/puma/binder.rb
CHANGED
@@ -189,7 +189,7 @@ module Puma
|
|
189
189
|
end
|
190
190
|
|
191
191
|
if fd = @inherited_fds.delete(str)
|
192
|
-
@unix_paths << path unless abstract
|
192
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
193
193
|
io = inherit_unix_listener path, fd
|
194
194
|
logger.log "* Inherited #{str}"
|
195
195
|
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
data/lib/puma/client.rb
CHANGED
@@ -23,6 +23,8 @@ module Puma
|
|
23
23
|
|
24
24
|
class ConnectionError < RuntimeError; end
|
25
25
|
|
26
|
+
class HttpParserError501 < IOError; end
|
27
|
+
|
26
28
|
# An instance of this class represents a unique request from a client.
|
27
29
|
# For example, this could be a web request from a browser or from CURL.
|
28
30
|
#
|
@@ -35,7 +37,21 @@ module Puma
|
|
35
37
|
# Instances of this class are responsible for knowing if
|
36
38
|
# the header and body are fully buffered via the `try_to_finish` method.
|
37
39
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
|
+
#
|
38
41
|
class Client
|
42
|
+
|
43
|
+
# this tests all values but the last, which must be chunked
|
44
|
+
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
45
|
+
|
46
|
+
# chunked body validation
|
47
|
+
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
48
|
+
CHUNK_VALID_ENDING = "\r\n".freeze
|
49
|
+
|
50
|
+
# Content-Length header value validation
|
51
|
+
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
52
|
+
|
53
|
+
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
54
|
+
|
39
55
|
# The object used for a request with no body. All requests with
|
40
56
|
# no body share this one object since it has no state.
|
41
57
|
EmptyBody = NullIO.new
|
@@ -302,16 +318,27 @@ module Puma
|
|
302
318
|
body = @parser.body
|
303
319
|
|
304
320
|
te = @env[TRANSFER_ENCODING2]
|
305
|
-
|
306
321
|
if te
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
322
|
+
te_lwr = te.downcase
|
323
|
+
if te.include? ','
|
324
|
+
te_ary = te_lwr.split ','
|
325
|
+
te_count = te_ary.count CHUNKED
|
326
|
+
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
327
|
+
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
328
|
+
@env.delete TRANSFER_ENCODING2
|
329
|
+
return setup_chunked_body body
|
330
|
+
elsif te_count >= 1
|
331
|
+
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
332
|
+
elsif !te_valid
|
333
|
+
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
312
334
|
end
|
313
|
-
elsif
|
314
|
-
|
335
|
+
elsif te_lwr == CHUNKED
|
336
|
+
@env.delete TRANSFER_ENCODING2
|
337
|
+
return setup_chunked_body body
|
338
|
+
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
339
|
+
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
340
|
+
else
|
341
|
+
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
315
342
|
end
|
316
343
|
end
|
317
344
|
|
@@ -319,7 +346,12 @@ module Puma
|
|
319
346
|
|
320
347
|
cl = @env[CONTENT_LENGTH]
|
321
348
|
|
322
|
-
|
349
|
+
if cl
|
350
|
+
# cannot contain characters that are not \d
|
351
|
+
if cl =~ CONTENT_LENGTH_VALUE_INVALID
|
352
|
+
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
353
|
+
end
|
354
|
+
else
|
323
355
|
@buffer = body.empty? ? nil : body
|
324
356
|
@body = EmptyBody
|
325
357
|
set_ready
|
@@ -478,7 +510,13 @@ module Puma
|
|
478
510
|
while !io.eof?
|
479
511
|
line = io.gets
|
480
512
|
if line.end_with?("\r\n")
|
481
|
-
|
513
|
+
# Puma doesn't process chunk extensions, but should parse if they're
|
514
|
+
# present, which is the reason for the semicolon regex
|
515
|
+
chunk_hex = line.strip[/\A[^;]+/]
|
516
|
+
if chunk_hex =~ CHUNK_SIZE_INVALID
|
517
|
+
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
518
|
+
end
|
519
|
+
len = chunk_hex.to_i(16)
|
482
520
|
if len == 0
|
483
521
|
@in_last_chunk = true
|
484
522
|
@body.rewind
|
@@ -509,7 +547,12 @@ module Puma
|
|
509
547
|
|
510
548
|
case
|
511
549
|
when got == len
|
512
|
-
|
550
|
+
# proper chunked segment must end with "\r\n"
|
551
|
+
if part.end_with? CHUNK_VALID_ENDING
|
552
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
553
|
+
else
|
554
|
+
raise HttpParserError, "Chunk size mismatch"
|
555
|
+
end
|
513
556
|
when got <= len - 2
|
514
557
|
write_chunk(part)
|
515
558
|
@partial_part_left = len - part.size
|
data/lib/puma/const.rb
CHANGED
@@ -76,7 +76,7 @@ module Puma
|
|
76
76
|
508 => 'Loop Detected',
|
77
77
|
510 => 'Not Extended',
|
78
78
|
511 => 'Network Authentication Required'
|
79
|
-
}
|
79
|
+
}.freeze
|
80
80
|
|
81
81
|
# For some HTTP status codes the client only expects headers.
|
82
82
|
#
|
@@ -85,7 +85,7 @@ module Puma
|
|
85
85
|
204 => true,
|
86
86
|
205 => true,
|
87
87
|
304 => true
|
88
|
-
}
|
88
|
+
}.freeze
|
89
89
|
|
90
90
|
# Frequently used constants when constructing requests or responses. Many times
|
91
91
|
# the constant just refers to a string with the same contents. Using these constants
|
@@ -100,7 +100,7 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "5.6.
|
103
|
+
PUMA_VERSION = VERSION = "5.6.5".freeze
|
104
104
|
CODE_NAME = "Birdie's Version".freeze
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
@@ -145,9 +145,11 @@ module Puma
|
|
145
145
|
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
146
146
|
# Indicate that there was an internal error, obviously.
|
147
147
|
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
148
|
+
# Incorrect or invalid header value
|
149
|
+
501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
|
148
150
|
# A common header for indicating the server is too busy. Not used yet.
|
149
151
|
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
150
|
-
}
|
152
|
+
}.freeze
|
151
153
|
|
152
154
|
# The basic max request size we'll try to read.
|
153
155
|
CHUNK_SIZE = 16 * 1024
|
data/lib/puma/control_cli.rb
CHANGED
@@ -17,26 +17,30 @@ module Puma
|
|
17
17
|
CMD_PATH_SIG_MAP = {
|
18
18
|
'gc' => nil,
|
19
19
|
'gc-stats' => nil,
|
20
|
-
'halt'
|
21
|
-
'
|
22
|
-
'
|
20
|
+
'halt' => 'SIGQUIT',
|
21
|
+
'info' => 'SIGINFO',
|
22
|
+
'phased-restart' => 'SIGUSR1',
|
23
|
+
'refork' => 'SIGURG',
|
23
24
|
'reload-worker-directory' => nil,
|
24
|
-
'
|
25
|
+
'reopen-log' => 'SIGHUP',
|
26
|
+
'restart' => 'SIGUSR2',
|
25
27
|
'start' => nil,
|
26
28
|
'stats' => nil,
|
27
29
|
'status' => '',
|
28
|
-
'stop'
|
29
|
-
'thread-backtraces' => nil
|
30
|
+
'stop' => 'SIGTERM',
|
31
|
+
'thread-backtraces' => nil,
|
32
|
+
'worker-count-down' => 'SIGTTOU',
|
33
|
+
'worker-count-up' => 'SIGTTIN'
|
30
34
|
}.freeze
|
31
35
|
|
32
36
|
# @deprecated 6.0.0
|
33
37
|
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
34
38
|
|
35
39
|
# commands that cannot be used in a request
|
36
|
-
NO_REQ_COMMANDS = %w
|
40
|
+
NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
|
37
41
|
|
38
42
|
# @version 5.0.0
|
39
|
-
PRINTABLE_COMMANDS = %w
|
43
|
+
PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
|
40
44
|
|
41
45
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
42
46
|
@state = nil
|
@@ -185,8 +189,6 @@ module Puma
|
|
185
189
|
|
186
190
|
if @command == 'status'
|
187
191
|
message 'Puma is started'
|
188
|
-
elsif NO_REQ_COMMANDS.include? @command
|
189
|
-
raise "Invalid request command: #{@command}"
|
190
192
|
else
|
191
193
|
url = "/#{@command}"
|
192
194
|
|
@@ -242,7 +244,11 @@ module Puma
|
|
242
244
|
@stdout.flush unless @stdout.sync
|
243
245
|
return
|
244
246
|
elsif sig.start_with? 'SIG'
|
245
|
-
|
247
|
+
if Signal.list.key? sig.sub(/\ASIG/, '')
|
248
|
+
Process.kill sig, @pid
|
249
|
+
else
|
250
|
+
raise "Signal '#{sig}' not available'"
|
251
|
+
end
|
246
252
|
elsif @command == 'status'
|
247
253
|
begin
|
248
254
|
Process.kill 0, @pid
|
@@ -268,7 +274,7 @@ module Puma
|
|
268
274
|
return start if @command == 'start'
|
269
275
|
prepare_configuration
|
270
276
|
|
271
|
-
if Puma.windows? || @control_url
|
277
|
+
if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
|
272
278
|
send_request
|
273
279
|
else
|
274
280
|
send_signal
|
data/lib/puma/dsl.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'puma/const'
|
4
|
+
require 'puma/util'
|
4
5
|
|
5
6
|
module Puma
|
6
7
|
# The methods that are available for use inside the configuration file.
|
@@ -46,7 +47,7 @@ module Puma
|
|
46
47
|
else ''
|
47
48
|
end
|
48
49
|
|
49
|
-
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
50
|
+
ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
|
50
51
|
|
51
52
|
backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
|
52
53
|
|
@@ -65,7 +66,10 @@ module Puma
|
|
65
66
|
v_flags = (ary = opts[:verification_flags]) ?
|
66
67
|
"&verification_flags=#{Array(ary).join ','}" : nil
|
67
68
|
|
68
|
-
|
69
|
+
cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(opts[:cert])}" : nil
|
70
|
+
key_flags = (cert = opts[:key]) ? "&key=#{Puma::Util.escape(opts[:key])}" : nil
|
71
|
+
|
72
|
+
"ssl://#{host}:#{port}?#{cert_flags}#{key_flags}" \
|
69
73
|
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
|
70
74
|
end
|
71
75
|
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -159,6 +159,17 @@ module Puma
|
|
159
159
|
true
|
160
160
|
end
|
161
161
|
|
162
|
+
# Begin a refork if supported
|
163
|
+
def refork
|
164
|
+
if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
|
165
|
+
@runner.fork_worker!
|
166
|
+
true
|
167
|
+
else
|
168
|
+
log "* refork called but not available."
|
169
|
+
false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
162
173
|
# Run the server. This blocks until the server is stopped
|
163
174
|
def run
|
164
175
|
previous_env =
|
data/lib/puma/minissl.rb
CHANGED
@@ -214,6 +214,11 @@ module Puma
|
|
214
214
|
@cert_pem = nil
|
215
215
|
end
|
216
216
|
|
217
|
+
def check_file(file, desc)
|
218
|
+
raise ArgumentError, "#{desc} file '#{file}' does not exist" unless File.exist? file
|
219
|
+
raise ArgumentError, "#{desc} file '#{file}' is not readable" unless File.readable? file
|
220
|
+
end
|
221
|
+
|
217
222
|
if IS_JRUBY
|
218
223
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
219
224
|
attr_reader :keystore
|
@@ -221,7 +226,7 @@ module Puma
|
|
221
226
|
attr_accessor :ssl_cipher_list
|
222
227
|
|
223
228
|
def keystore=(keystore)
|
224
|
-
|
229
|
+
check_file keystore, 'Keystore'
|
225
230
|
@keystore = keystore
|
226
231
|
end
|
227
232
|
|
@@ -240,17 +245,17 @@ module Puma
|
|
240
245
|
attr_accessor :verification_flags
|
241
246
|
|
242
247
|
def key=(key)
|
243
|
-
|
248
|
+
check_file key, 'Key'
|
244
249
|
@key = key
|
245
250
|
end
|
246
251
|
|
247
252
|
def cert=(cert)
|
248
|
-
|
253
|
+
check_file cert, 'Cert'
|
249
254
|
@cert = cert
|
250
255
|
end
|
251
256
|
|
252
257
|
def ca=(ca)
|
253
|
-
|
258
|
+
check_file ca, 'ca'
|
254
259
|
@ca = ca
|
255
260
|
end
|
256
261
|
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/request.rb
CHANGED
@@ -167,13 +167,22 @@ module Puma
|
|
167
167
|
end
|
168
168
|
|
169
169
|
ensure
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
170
|
+
begin
|
171
|
+
uncork_socket io
|
172
|
+
|
173
|
+
body.close
|
174
|
+
client.tempfile.unlink if client.tempfile
|
175
|
+
ensure
|
176
|
+
# Whatever happens, we MUST call `close` on the response body.
|
177
|
+
# Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
|
178
|
+
res_body.close if res_body.respond_to? :close
|
179
|
+
end
|
175
180
|
|
176
|
-
|
181
|
+
begin
|
182
|
+
after_reply.each { |o| o.call }
|
183
|
+
rescue StandardError => e
|
184
|
+
@log_writer.debug_error e
|
185
|
+
end
|
177
186
|
end
|
178
187
|
|
179
188
|
res_info[:keep_alive]
|
data/lib/puma/server.rb
CHANGED
@@ -39,6 +39,7 @@ module Puma
|
|
39
39
|
attr_reader :events
|
40
40
|
attr_reader :min_threads, :max_threads # for #stats
|
41
41
|
attr_reader :requests_count # @version 5.0.0
|
42
|
+
attr_reader :log_writer # to help with backports
|
42
43
|
|
43
44
|
# @todo the following may be deprecated in the future
|
44
45
|
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
@@ -73,6 +74,7 @@ module Puma
|
|
73
74
|
def initialize(app, events=Events.stdio, options={})
|
74
75
|
@app = app
|
75
76
|
@events = events
|
77
|
+
@log_writer = events
|
76
78
|
|
77
79
|
@check, @notify = nil
|
78
80
|
@status = :stop
|
@@ -515,6 +517,9 @@ module Puma
|
|
515
517
|
when HttpParserError
|
516
518
|
client.write_error(400)
|
517
519
|
@events.parse_error e, client
|
520
|
+
when HttpParserError501
|
521
|
+
client.write_error(501)
|
522
|
+
@events.parse_error e, client
|
518
523
|
else
|
519
524
|
client.write_error(500)
|
520
525
|
@events.unknown_error e, nil, "Read"
|
data/lib/puma/state_file.rb
CHANGED
data/lib/puma/util.rb
CHANGED
@@ -17,18 +17,27 @@ module Puma
|
|
17
17
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# Escapes and unescapes a URI escaped string with
|
21
|
+
# +encoding+. +encoding+ will be the target encoding of the string
|
22
|
+
# returned, and it defaults to UTF-8
|
22
23
|
if defined?(::Encoding)
|
24
|
+
def escape(s, encoding = Encoding::UTF_8)
|
25
|
+
URI.encode_www_form_component(s, encoding)
|
26
|
+
end
|
27
|
+
|
23
28
|
def unescape(s, encoding = Encoding::UTF_8)
|
24
29
|
URI.decode_www_form_component(s, encoding)
|
25
30
|
end
|
26
31
|
else
|
32
|
+
def escape(s, encoding = nil)
|
33
|
+
URI.encode_www_form_component(s, encoding)
|
34
|
+
end
|
35
|
+
|
27
36
|
def unescape(s, encoding = nil)
|
28
37
|
URI.decode_www_form_component(s, encoding)
|
29
38
|
end
|
30
39
|
end
|
31
|
-
module_function :unescape
|
40
|
+
module_function :unescape, :escape
|
32
41
|
|
33
42
|
# @version 5.0.0
|
34
43
|
def nakayoshi_gc(events)
|