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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 469723b0faecde36baaac342696d0b59d73086f199a020e89d18ae33e2b181f0
4
- data.tar.gz: cf2f9bb437d6bf29c6ad1fe5a166ed4db0c9c0c0d67a239d7f07e62b8d993a6b
3
+ metadata.gz: 7be1244aa7c9d74f0021e1763e05b7220ceb3630b41ef7cf2205b71d5f5cf494
4
+ data.tar.gz: 802a80a1437d272cfbd101be2fa5370860fd3745b17996661db5eaa47b98b0e1
5
5
  SHA512:
6
- metadata.gz: b677ffb75bd299a97fc19eefa2caf8f3a80b8db0600d980ea4700fcb1fd12fd7c4079182e38bc4fc43879e668bffdc757247f4024dfd7e0d13ceb29181debbc4
7
- data.tar.gz: cd06e3f7cbecffafb7aec87831a24055f45f5e6313d8808ab797116efa9e410e01539382499465f3c23bb957aace762ba24a9deb8ecfdc1c4bf1f0cff852688d
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 @ccverak, merged 2020-12-02"
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 @ccverak, merged 2020-11-02"
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 @0RaymondJiang0, merged 2015-07-14"
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"
@@ -9,9 +9,11 @@ if $mingw && RUBY_VERSION >= '2.4'
9
9
  end
10
10
 
11
11
  unless ENV["DISABLE_SSL"]
12
- dir_config("openssl")
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') && (t = pkg_config 'openssl')
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 are yes for 3.0.0 & later, use for OpenSSL 3 detection
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
@@ -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 HAVE_SSL_GET1_PEER_CERTIFICATE
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 || defined(LIBRESSL_VERSION_NUMBER))
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 || defined(LIBRESSL_VERSION_NUMBER)
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 HAVE_SSL_GET1_PEER_CERTIFICATE
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
- SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert));
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
- SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);
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 HAVE_SSL_GET1_PEER_CERTIFICATE
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()) {
@@ -39,6 +39,9 @@ module Puma
39
39
  when 'phased-restart'
40
40
  @launcher.phased_restart ? 200 : 404
41
41
 
42
+ when 'refork'
43
+ @launcher.refork ? 200 : 404
44
+
42
45
  when 'reload-worker-directory'
43
46
  @launcher.send(:reload_worker_directory) ? 200 : 404
44
47
 
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
- if te.include?(",")
308
- te.split(",").each do |part|
309
- if CHUNKED.casecmp(part.strip) == 0
310
- return setup_chunked_body(body)
311
- end
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 CHUNKED.casecmp(te) == 0
314
- return setup_chunked_body(body)
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
- unless cl
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
- len = line.strip.to_i(16)
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
- write_chunk(part[0..-3]) # to skip the ending \r\n
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.1".freeze
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
@@ -17,26 +17,30 @@ module Puma
17
17
  CMD_PATH_SIG_MAP = {
18
18
  'gc' => nil,
19
19
  'gc-stats' => nil,
20
- 'halt' => 'SIGQUIT',
21
- 'phased-restart' => 'SIGUSR1',
22
- 'refork' => 'SIGURG',
20
+ 'halt' => 'SIGQUIT',
21
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
23
24
  'reload-worker-directory' => nil,
24
- 'restart' => 'SIGUSR2',
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
25
27
  'start' => nil,
26
28
  'stats' => nil,
27
29
  'status' => '',
28
- 'stop' => 'SIGTERM',
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{refork}.freeze
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{gc-stats stats thread-backtraces}.freeze
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
- Process.kill sig, @pid
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
- "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
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
- raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
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
- raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
248
+ check_file key, 'Key'
244
249
  @key = key
245
250
  end
246
251
 
247
252
  def cert=(cert)
248
- raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
253
+ check_file cert, 'Cert'
249
254
  @cert = cert
250
255
  end
251
256
 
252
257
  def ca=(ca)
253
- raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
258
+ check_file ca, 'ca'
254
259
  @ca = ca
255
260
  end
256
261
 
data/lib/puma/null_io.rb CHANGED
@@ -52,5 +52,10 @@ module Puma
52
52
  def flush
53
53
  self
54
54
  end
55
+
56
+ # This is used as singleton class, so can't have state.
57
+ def closed?
58
+ false
59
+ end
55
60
  end
56
61
  end
data/lib/puma/request.rb CHANGED
@@ -167,13 +167,22 @@ module Puma
167
167
  end
168
168
 
169
169
  ensure
170
- uncork_socket io
171
-
172
- body.close
173
- client.tempfile.unlink if client.tempfile
174
- res_body.close if res_body.respond_to? :close
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
- after_reply.each { |o| o.call }
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"
@@ -50,6 +50,7 @@ module Puma
50
50
  v = v.strip
51
51
  @options[k] =
52
52
  case v
53
+ when '' then nil
53
54
  when /\A\d+\z/ then v.to_i
54
55
  when /\A\d+\.\d+\z/ then v.to_f
55
56
  else v.gsub(/\A"|"\z/, '')
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
- # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
21
- # target encoding of the string returned, and it defaults to UTF-8
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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.1
4
+ version: 5.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix