puma 6.0.2 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.
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