puma 6.0.2 → 6.4.2

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.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +213 -7
  3. data/LICENSE +0 -0
  4. data/README.md +59 -13
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +12 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +1 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +3 -6
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +5 -1
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +96 -9
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +14 -11
  42. data/lib/puma/cli.rb +5 -1
  43. data/lib/puma/client.rb +77 -16
  44. data/lib/puma/cluster/worker.rb +5 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +71 -10
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +6 -4
  49. data/lib/puma/const.rb +58 -9
  50. data/lib/puma/control_cli.rb +12 -5
  51. data/lib/puma/detect.rb +5 -4
  52. data/lib/puma/dsl.rb +157 -7
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +0 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +9 -22
  60. data/lib/puma/log_writer.rb +14 -4
  61. data/lib/puma/minissl/context_builder.rb +3 -0
  62. data/lib/puma/minissl.rb +22 -0
  63. data/lib/puma/null_io.rb +16 -2
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +0 -0
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +2 -2
  68. data/lib/puma/rack/urlmap.rb +1 -1
  69. data/lib/puma/rack_default.rb +18 -3
  70. data/lib/puma/reactor.rb +16 -7
  71. data/lib/puma/request.rb +91 -64
  72. data/lib/puma/runner.rb +13 -2
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +91 -27
  75. data/lib/puma/single.rb +2 -0
  76. data/lib/puma/state_file.rb +2 -2
  77. data/lib/puma/thread_pool.rb +41 -3
  78. data/lib/puma/util.rb +0 -0
  79. data/lib/puma.rb +0 -0
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +2 -2
  82. data/tools/trickletest.rb +0 -0
  83. metadata +5 -4
  84. data/lib/puma/systemd.rb +0 -47
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
36
36
  rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
37
37
  }
38
38
 
39
+ NORETURN(void raise_param_error(const char* caller, const char *param));
40
+
41
+ void raise_param_error(const char* caller, const char *param) {
42
+ rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
43
+ }
44
+
39
45
  void engine_free(void *ptr) {
40
46
  ms_conn *conn = ptr;
41
47
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
@@ -185,6 +191,18 @@ static int engine_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
185
191
  return preverify_ok;
186
192
  }
187
193
 
194
+ static int password_callback(char *buf, int size, int rwflag, void *userdata) {
195
+ const char *password = (const char *) userdata;
196
+ size_t len = strlen(password);
197
+
198
+ if (len > (size_t) size) {
199
+ return 0;
200
+ }
201
+
202
+ memcpy(buf, password, len);
203
+ return (int) len;
204
+ }
205
+
188
206
  static VALUE
189
207
  sslctx_alloc(VALUE klass) {
190
208
  SSL_CTX *ctx;
@@ -212,10 +230,12 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
212
230
  SSL_CTX* ctx;
213
231
  int ssl_options;
214
232
  VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
215
- verification_flags, session_id_bytes, cert_pem, key_pem;
233
+ verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
216
234
  BIO *bio;
217
- X509 *x509;
235
+ X509 *x509 = NULL;
218
236
  EVP_PKEY *pkey;
237
+ pem_password_cb *password_cb = NULL;
238
+ const char *password = NULL;
219
239
  #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
220
240
  int min;
221
241
  #endif
@@ -235,6 +255,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
235
255
 
236
256
  key = rb_funcall(mini_ssl_ctx, rb_intern_const("key"), 0);
237
257
 
258
+ key_password_command = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password_command"), 0);
259
+
238
260
  cert = rb_funcall(mini_ssl_ctx, rb_intern_const("cert"), 0);
239
261
 
240
262
  ca = rb_funcall(mini_ssl_ctx, rb_intern_const("ca"), 0);
@@ -261,6 +283,18 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
261
283
  }
262
284
  }
263
285
 
286
+ if (!NIL_P(key_password_command)) {
287
+ key_password = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password"), 0);
288
+
289
+ if (!NIL_P(key_password)) {
290
+ StringValue(key_password);
291
+ password_cb = password_callback;
292
+ password = RSTRING_PTR(key_password);
293
+ SSL_CTX_set_default_passwd_cb(ctx, password_cb);
294
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
295
+ }
296
+ }
297
+
264
298
  if (!NIL_P(key)) {
265
299
  StringValue(key);
266
300
 
@@ -270,22 +304,71 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
270
304
  }
271
305
 
272
306
  if (!NIL_P(cert_pem)) {
307
+ X509 *ca = NULL;
308
+ unsigned long err;
309
+
273
310
  bio = BIO_new(BIO_s_mem());
274
311
  BIO_puts(bio, RSTRING_PTR(cert_pem));
312
+
313
+ /**
314
+ * Much of this pulled as a simplified version of the `use_certificate_chain_file` method
315
+ * from openssl's `ssl_rsa.c` file.
316
+ */
317
+
318
+ /* first read the cert as the first item in the pem file */
275
319
  x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
320
+ if (NULL == x509) {
321
+ BIO_free_all(bio);
322
+ raise_param_error("PEM_read_bio_X509", "cert_pem");
323
+ }
276
324
 
277
- if (SSL_CTX_use_certificate(ctx, x509) != 1) {
278
- BIO_free(bio);
279
- raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
325
+ /* Add the cert to the context */
326
+ /* 1 is success - otherwise check the error codes */
327
+ if (1 != SSL_CTX_use_certificate(ctx, x509)) {
328
+ BIO_free_all(bio);
329
+ raise_param_error("SSL_CTX_use_certificate", "cert_pem");
330
+ }
331
+
332
+ X509_free(x509); /* no longer need our reference */
333
+
334
+ /* Now lets load up the rest of the certificate chain */
335
+ /* 1 is success 0 is error */
336
+ if (0 == SSL_CTX_clear_chain_certs(ctx)) {
337
+ BIO_free_all(bio);
338
+ raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
339
+ }
340
+
341
+ while (1) {
342
+ ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
343
+
344
+ if (NULL == ca) {
345
+ break;
346
+ }
347
+
348
+ if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
349
+ BIO_free_all(bio);
350
+ raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
351
+ }
352
+ /* don't free ca - its now owned by the context */
353
+ }
354
+
355
+ /* ca is NULL - so its either the end of the file or an error */
356
+ err = ERR_peek_last_error();
357
+
358
+ /* If its the end of the file - then we are done, in any case free the bio */
359
+ BIO_free_all(bio);
360
+
361
+ if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
362
+ ERR_clear_error();
363
+ } else {
364
+ raise_param_error("PEM_read_bio_X509","cert_pem");
280
365
  }
281
- X509_free(x509);
282
- BIO_free(bio);
283
366
  }
284
367
 
285
368
  if (!NIL_P(key_pem)) {
286
369
  bio = BIO_new(BIO_s_mem());
287
370
  BIO_puts(bio, RSTRING_PTR(key_pem));
288
- pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
371
+ pkey = PEM_read_bio_PrivateKey(bio, NULL, password_cb, (void *) password);
289
372
 
290
373
  if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
291
374
  BIO_free(bio);
@@ -463,7 +546,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
463
546
 
464
547
  void raise_error(SSL* ssl, int result) {
465
548
  char buf[512];
466
- char msg[512];
549
+ char msg[768];
467
550
  const char* err_str;
468
551
  int err = errno;
469
552
  int mask = 4095;
@@ -721,6 +804,10 @@ void Init_mini_ssl(VALUE puma) {
721
804
 
722
805
  rb_define_method(eng, "init?", engine_init, 0);
723
806
 
807
+ /* @!attribute [r] peercert
808
+ * Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
809
+ * @return [String, nil] DER encoded cert
810
+ */
724
811
  rb_define_method(eng, "peercert", engine_peercert, 0);
725
812
 
726
813
  rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
File without changes
File without changes
File without changes
@@ -47,6 +47,7 @@ import static javax.net.ssl.SSLEngineResult.Status;
47
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
48
48
 
49
49
  public class MiniSSL extends RubyObject { // MiniSSL::Engine
50
+ private static final long serialVersionUID = -6903439483039141234L;
50
51
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
51
52
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
52
53
  return new MiniSSL(runtime, klass);
@@ -500,7 +501,7 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
500
501
  }
501
502
 
502
503
  private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
- RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ RaiseException ex = RaiseException.from(runtime, errorClass, message);
504
505
  ex.initCause(cause);
505
506
  return ex;
506
507
  }
File without changes
@@ -80,7 +80,7 @@ module Puma
80
80
 
81
81
  def authenticate(env)
82
82
  return true unless @auth_token
83
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
83
+ env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
84
84
  end
85
85
 
86
86
  def rack_response(status, body, content_type='application/json')
data/lib/puma/binder.rb CHANGED
@@ -48,7 +48,6 @@ module Puma
48
48
 
49
49
  @envs = {}
50
50
  @ios = []
51
- localhost_authority
52
51
  end
53
52
 
54
53
  attr_reader :ios
@@ -158,10 +157,10 @@ module Puma
158
157
  ios_len = @ios.length
159
158
  params = Util.parse_query uri.query
160
159
 
161
- opt = params.key?('low_latency') && params['low_latency'] != 'false'
160
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
162
161
  backlog = params.fetch('backlog', 1024).to_i
163
162
 
164
- io = add_tcp_listener uri.host, uri.port, opt, backlog
163
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
165
164
 
166
165
  @ios[ios_len..-1].each do |i|
167
166
  addr = loc_addr_str i
@@ -251,7 +250,8 @@ module Puma
251
250
  else
252
251
  ios_len = @ios.length
253
252
  backlog = params.fetch('backlog', 1024).to_i
254
- io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
253
+ low_latency = params['low_latency'] != 'false'
254
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
255
255
 
256
256
  @ios[ios_len..-1].each do |i|
257
257
  addr = loc_addr_str i
@@ -330,7 +330,7 @@ module Puma
330
330
  return
331
331
  end
332
332
 
333
- host = host[1..-2] if host and host[0..0] == '['
333
+ host = host[1..-2] if host&.start_with? '['
334
334
  tcp_server = TCPServer.new(host, port)
335
335
 
336
336
  if optimize_for_latency
@@ -364,7 +364,7 @@ module Puma
364
364
  return
365
365
  end
366
366
 
367
- host = host[1..-2] if host[0..0] == '['
367
+ host = host[1..-2] if host&.start_with? '['
368
368
  s = TCPServer.new(host, port)
369
369
  if optimize_for_latency
370
370
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@@ -450,11 +450,14 @@ module Puma
450
450
 
451
451
  def close_listeners
452
452
  @listeners.each do |l, io|
453
- io.close unless io.closed?
454
- uri = URI.parse l
455
- next unless uri.scheme == 'unix'
456
- unix_path = "#{uri.host}#{uri.path}"
457
- File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
453
+ begin
454
+ io.close unless io.closed?
455
+ uri = URI.parse l
456
+ next unless uri.scheme == 'unix'
457
+ unix_path = "#{uri.host}#{uri.path}"
458
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
459
+ rescue Errno::EBADF
460
+ end
458
461
  end
459
462
  end
460
463
 
data/lib/puma/cli.rb CHANGED
@@ -93,7 +93,7 @@ module Puma
93
93
  #
94
94
 
95
95
  def setup_options
96
- @conf = Configuration.new do |user_config, file_config|
96
+ @conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
97
97
  @parser = OptionParser.new do |o|
98
98
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
99
99
  user_config.bind arg
@@ -144,6 +144,10 @@ module Puma
144
144
  $LOAD_PATH.unshift(*arg.split(':'))
145
145
  end
146
146
 
147
+ o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
148
+ user_config.idle_timeout arg
149
+ end
150
+
147
151
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
152
  "Use -b for more advanced options" do |arg|
149
153
  user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
data/lib/puma/client.rb CHANGED
@@ -11,7 +11,6 @@ end
11
11
  require_relative 'detect'
12
12
  require_relative 'io_buffer'
13
13
  require 'tempfile'
14
- require 'forwardable'
15
14
 
16
15
  if Puma::IS_JRUBY
17
16
  # We have to work around some OpenSSL buffer/io-readiness bugs
@@ -49,7 +48,16 @@ module Puma
49
48
 
50
49
  # chunked body validation
51
50
  CHUNK_SIZE_INVALID = /[^\h]/.freeze
52
- CHUNK_VALID_ENDING = "\r\n".freeze
51
+ CHUNK_VALID_ENDING = Const::LINE_END
52
+ CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
53
+
54
+ # The maximum number of bytes we'll buffer looking for a valid
55
+ # chunk header.
56
+ MAX_CHUNK_HEADER_SIZE = 4096
57
+
58
+ # The maximum amount of excess data the client sends
59
+ # using chunk size extensions before we abort the connection.
60
+ MAX_CHUNK_EXCESS = 16 * 1024
53
61
 
54
62
  # Content-Length header value validation
55
63
  CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
@@ -61,14 +69,13 @@ module Puma
61
69
  EmptyBody = NullIO.new
62
70
 
63
71
  include Puma::Const
64
- extend Forwardable
65
72
 
66
73
  def initialize(io, env=nil)
67
74
  @io = io
68
75
  @to_io = io.to_io
69
76
  @io_buffer = IOBuffer.new
70
77
  @proto_env = env
71
- @env = env ? env.dup : nil
78
+ @env = env&.dup
72
79
 
73
80
  @parser = HttpParser.new
74
81
  @parsed_bytes = 0
@@ -86,6 +93,9 @@ module Puma
86
93
  @requests_served = 0
87
94
  @hijacked = false
88
95
 
96
+ @http_content_length_limit = nil
97
+ @http_content_length_limit_exceeded = false
98
+
89
99
  @peerip = nil
90
100
  @peer_family = nil
91
101
  @listener = nil
@@ -95,16 +105,22 @@ module Puma
95
105
  @body_remain = 0
96
106
 
97
107
  @in_last_chunk = false
108
+
109
+ # need unfrozen ASCII-8BIT, +'' is UTF-8
110
+ @read_buffer = String.new # rubocop: disable Performance/UnfreezeString
98
111
  end
99
112
 
100
113
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
101
- :tempfile, :io_buffer
114
+ :tempfile, :io_buffer, :http_content_length_limit_exceeded
102
115
 
103
- attr_writer :peerip
116
+ attr_writer :peerip, :http_content_length_limit
104
117
 
105
118
  attr_accessor :remote_addr_header, :listener
106
119
 
107
- def_delegators :@io, :closed?
120
+ # Remove in Puma 7?
121
+ def closed?
122
+ @to_io.closed?
123
+ end
108
124
 
109
125
  # Test to see if io meets a bare minimum of functioning, @to_io needs to be
110
126
  # used for MiniSSL::Socket
@@ -151,6 +167,7 @@ module Puma
151
167
  @body_remain = 0
152
168
  @peerip = nil if @remote_addr_header
153
169
  @in_last_chunk = false
170
+ @http_content_length_limit_exceeded = false
154
171
 
155
172
  if @buffer
156
173
  return false unless try_to_parse_proxy_protocol
@@ -210,6 +227,17 @@ module Puma
210
227
  end
211
228
 
212
229
  def try_to_finish
230
+ if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
231
+ @http_content_length_limit_exceeded = true
232
+ end
233
+
234
+ if @http_content_length_limit_exceeded
235
+ @buffer = nil
236
+ @body = EmptyBody
237
+ set_ready
238
+ return true
239
+ end
240
+
213
241
  return read_body if in_data_phase
214
242
 
215
243
  begin
@@ -239,6 +267,10 @@ module Puma
239
267
 
240
268
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
241
269
 
270
+ if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
271
+ @http_content_length_limit_exceeded = true
272
+ end
273
+
242
274
  if @parser.finished?
243
275
  return setup_body
244
276
  elsif @parsed_bytes >= MAX_HEADER
@@ -360,8 +392,8 @@ module Puma
360
392
  cl = @env[CONTENT_LENGTH]
361
393
 
362
394
  if cl
363
- # cannot contain characters that are not \d
364
- if CONTENT_LENGTH_VALUE_INVALID.match? cl
395
+ # cannot contain characters that are not \d, or be empty
396
+ if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
365
397
  raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
366
398
  end
367
399
  else
@@ -414,7 +446,7 @@ module Puma
414
446
  end
415
447
 
416
448
  begin
417
- chunk = @io.read_nonblock(want)
449
+ chunk = @io.read_nonblock(want, @read_buffer)
418
450
  rescue IO::WaitReadable
419
451
  return false
420
452
  rescue SystemCallError, IOError
@@ -446,7 +478,7 @@ module Puma
446
478
  def read_chunked_body
447
479
  while true
448
480
  begin
449
- chunk = @io.read_nonblock(4096)
481
+ chunk = @io.read_nonblock(4096, @read_buffer)
450
482
  rescue IO::WaitReadable
451
483
  return false
452
484
  rescue SystemCallError, IOError
@@ -472,6 +504,7 @@ module Puma
472
504
  @chunked_body = true
473
505
  @partial_part_left = 0
474
506
  @prev_chunk = ""
507
+ @excess_cr = 0
475
508
 
476
509
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
477
510
  @body.unlink
@@ -522,7 +555,7 @@ module Puma
522
555
 
523
556
  while !io.eof?
524
557
  line = io.gets
525
- if line.end_with?("\r\n")
558
+ if line.end_with?(CHUNK_VALID_ENDING)
526
559
  # Puma doesn't process chunk extensions, but should parse if they're
527
560
  # present, which is the reason for the semicolon regex
528
561
  chunk_hex = line.strip[/\A[^;]+/]
@@ -534,19 +567,39 @@ module Puma
534
567
  @in_last_chunk = true
535
568
  @body.rewind
536
569
  rest = io.read
537
- last_crlf_size = "\r\n".bytesize
538
- if rest.bytesize < last_crlf_size
570
+ if rest.bytesize < CHUNK_VALID_ENDING_SIZE
539
571
  @buffer = nil
540
- @partial_part_left = last_crlf_size - rest.bytesize
572
+ @partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
541
573
  return false
542
574
  else
543
- @buffer = rest[last_crlf_size..-1]
575
+ # if the next character is a CRLF, set buffer to everything after that CRLF
576
+ start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
577
+ CHUNK_VALID_ENDING_SIZE
578
+ else # we have started a trailer section, which we do not support. skip it!
579
+ rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
580
+ end
581
+
582
+ @buffer = rest[start_of_rest..-1]
544
583
  @buffer = nil if @buffer.empty?
545
584
  set_ready
546
585
  return true
547
586
  end
548
587
  end
549
588
 
589
+ # Track the excess as a function of the size of the
590
+ # header vs the size of the actual data. Excess can
591
+ # go negative (and is expected to) when the body is
592
+ # significant.
593
+ # The additional of chunk_hex.size and 2 compensates
594
+ # for a client sending 1 byte in a chunked body over
595
+ # a long period of time, making sure that that client
596
+ # isn't accidentally eventually punished.
597
+ @excess_cr += (line.size - len - chunk_hex.size - 2)
598
+
599
+ if @excess_cr >= MAX_CHUNK_EXCESS
600
+ raise HttpParserError, "Maximum chunk excess detected"
601
+ end
602
+
550
603
  len += 2
551
604
 
552
605
  part = io.read(len)
@@ -574,6 +627,10 @@ module Puma
574
627
  @partial_part_left = len - part.size
575
628
  end
576
629
  else
630
+ if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
631
+ raise HttpParserError, "maximum size of chunk header exceeded"
632
+ end
633
+
577
634
  @prev_chunk = line
578
635
  return false
579
636
  end
@@ -594,5 +651,9 @@ module Puma
594
651
  @requests_served += 1
595
652
  @ready = true
596
653
  end
654
+
655
+ def above_http_content_limit(value)
656
+ @http_content_length_limit&.< value
657
+ end
597
658
  end
598
659
  end
@@ -115,6 +115,11 @@ module Puma
115
115
 
116
116
  while restart_server.pop
117
117
  server_thread = server.run
118
+
119
+ if @log_writer.debug? && index == 0
120
+ debug_loaded_extensions "Loaded Extensions - worker 0:"
121
+ end
122
+
118
123
  stat_thread ||= Thread.new(@worker_write) do |io|
119
124
  Puma.set_thread_name "stat pld"
120
125
  base_payload = "p#{Process.pid}"
File without changes