puma 7.2.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -383,41 +383,36 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
383
383
  }
384
384
 
385
385
  MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
386
- int consumedTo;
387
- do {
388
- consumedTo = inboundNetData.position();
389
- doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
390
-
391
- HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
392
- boolean done = false;
393
- while (!done) {
394
- SSLEngineResult res;
395
- switch (handshakeStatus) {
396
- case NEED_WRAP:
397
- res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
398
- handshakeStatus = res.getHandshakeStatus();
399
- break;
400
- case NEED_UNWRAP:
401
- res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
402
- if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
403
- // need more data before we can shake more hands
404
- done = true;
405
- }
406
- handshakeStatus = res.getHandshakeStatus();
407
- break;
408
- case NEED_TASK:
409
- Runnable runnable;
410
- while ((runnable = engine.getDelegatedTask()) != null) {
411
- runnable.run();
412
- }
413
- handshakeStatus = engine.getHandshakeStatus();
414
- break;
415
- default:
386
+ doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
387
+
388
+ HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
389
+ boolean done = false;
390
+ while (!done) {
391
+ SSLEngineResult res;
392
+ switch (handshakeStatus) {
393
+ case NEED_WRAP:
394
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
395
+ handshakeStatus = res.getHandshakeStatus();
396
+ break;
397
+ case NEED_UNWRAP:
398
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
399
+ if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
400
+ // need more data before we can shake more hands
416
401
  done = true;
417
- }
402
+ }
403
+ handshakeStatus = res.getHandshakeStatus();
404
+ break;
405
+ case NEED_TASK:
406
+ Runnable runnable;
407
+ while ((runnable = engine.getDelegatedTask()) != null) {
408
+ runnable.run();
409
+ }
410
+ handshakeStatus = engine.getHandshakeStatus();
411
+ break;
412
+ default:
413
+ done = true;
418
414
  }
419
- } while (inboundAppData.position() == 0 && !closed &&
420
- inboundNetData.hasRemaining() && inboundNetData.position() > consumedTo);
415
+ }
421
416
 
422
417
  if (inboundNetData.hasRemaining()) {
423
418
  inboundNetData.compact();
@@ -487,19 +482,14 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
487
482
 
488
483
  @JRubyMethod
489
484
  public IRubyObject shutdown() {
490
- // The peer may be speaking plain HTTP if the handshake did not finish.
491
- if (closed || !handshake || engine.isInboundDone() && engine.isOutboundDone()) {
492
- return getRuntime().getTrue();
493
- }
494
-
495
- // Generate close_notify for MiniSSL::Socket#close to send.
496
- engine.closeOutbound();
497
- try {
498
- doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
499
- } catch (SSLException e) {
485
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
486
+ if (engine.isOutboundDone()) {
487
+ engine.closeOutbound();
488
+ }
500
489
  return getRuntime().getTrue();
490
+ } else {
491
+ return getRuntime().getFalse();
501
492
  }
502
- return getRuntime().getFalse();
503
493
  }
504
494
 
505
495
  private static RubyClass getSSLError(Ruby runtime) {
@@ -385,8 +385,7 @@ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE st
385
385
  long dlen = 0;
386
386
 
387
387
  from = FIX2INT(start);
388
- dptr = RSTRING_PTR(data);
389
- dlen = (long)RSTRING_LEN(data);
388
+ RSTRING_GETMEM(data, dptr, dlen);
390
389
 
391
390
  if(from >= dlen) {
392
391
  rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
@@ -399,7 +398,7 @@ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE st
399
398
  if(puma_parser_has_error(http)) {
400
399
  rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
401
400
  } else {
402
- return SIZET2NUM(puma_parser_nread(http));
401
+ return INT2FIX(puma_parser_nread(http));
403
402
  }
404
403
  }
405
404
  }
@@ -445,7 +444,7 @@ static VALUE HttpParser_nread(VALUE self)
445
444
  {
446
445
  puma_parser *http = HttpParser_unwrap(self);
447
446
 
448
- return SIZET2NUM(http->nread);
447
+ return INT2FIX(http->nread);
449
448
  }
450
449
 
451
450
  /**
data/lib/puma/cli.rb CHANGED
@@ -148,7 +148,7 @@ module Puma
148
148
 
149
149
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
150
150
  "Use -b for more advanced options" do |arg|
151
- user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
151
+ user_config.port arg, Configuration.default_tcp_host
152
152
  end
153
153
 
154
154
  o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
data/lib/puma/client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'detect'
4
4
  require_relative 'io_buffer'
5
+ require_relative 'client_env'
5
6
  require 'tempfile'
6
7
 
7
8
  if Puma::IS_JRUBY
@@ -20,8 +21,8 @@ module Puma
20
21
  #———————————————————————— DO NOT USE — this class is for internal use only ———
21
22
 
22
23
 
23
- # An instance of this class represents a unique request from a client.
24
- # For example, this could be a web request from a browser or from CURL.
24
+ # An instance of this class wraps a connection/socket.
25
+ # For example, this could be an http request from a browser or from CURL.
25
26
  #
26
27
  # An instance of `Puma::Client` can be used as if it were an IO object
27
28
  # by the reactor. The reactor is expected to call `#to_io`
@@ -29,12 +30,18 @@ module Puma
29
30
  # `IO::try_convert` (which may call `#to_io`) when a new socket is
30
31
  # registered.
31
32
  #
32
- # Instances of this class are responsible for knowing if
33
- # the header and body are fully buffered via the `try_to_finish` method.
33
+ # Instances of this class are responsible for knowing if the request line,
34
+ # headers and body are fully buffered and verified via the `try_to_finish` method.
35
+ # All verification of each request is done in the `Client` object.
34
36
  # They can be used to "time out" a response via the `timeout_at` reader.
35
37
  #
38
+ # Most of the code for env processing and verification is contained
39
+ # in `Puma::ClientEnv`, which is included.
40
+ #
36
41
  class Client # :nodoc:
37
42
 
43
+ include ClientEnv
44
+
38
45
  # this tests all values but the last, which must be chunked
39
46
  ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
40
47
 
@@ -47,8 +54,6 @@ module Puma
47
54
  # chunk header.
48
55
  MAX_CHUNK_HEADER_SIZE = 4096
49
56
 
50
- SHUTDOWN_READ_GRACE = 1
51
-
52
57
  # The maximum amount of excess data the client sends
53
58
  # using chunk size extensions before we abort the connection.
54
59
  MAX_CHUNK_EXCESS = 16 * 1024
@@ -67,7 +72,13 @@ module Puma
67
72
  # no body share this one object since it has no state.
68
73
  EmptyBody = NullIO.new
69
74
 
70
- include Puma::Const
75
+ attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
76
+ :tempfile, :io_buffer, :http_content_length_limit_exceeded,
77
+ :requests_served, :error_status_code
78
+
79
+ attr_writer :peerip, :http_content_length_limit, :supported_http_methods
80
+
81
+ attr_accessor :remote_addr_header, :listener, :env_set_http_version
71
82
 
72
83
  def initialize(io, env=nil)
73
84
  @io = io
@@ -93,9 +104,8 @@ module Puma
93
104
  @hijacked = false
94
105
 
95
106
  @http_content_length_limit = nil
96
- @http_content_length_limit_exceeded = false
97
-
98
- @reactor_error = nil
107
+ @http_content_length_limit_exceeded = nil
108
+ @error_status_code = nil
99
109
 
100
110
  @peerip = nil
101
111
  @peer_family = nil
@@ -104,8 +114,6 @@ module Puma
104
114
  @expect_proxy_proto = false
105
115
 
106
116
  @body_remain = 0
107
- @bytes_read = 0
108
- @pending_write = nil
109
117
 
110
118
  @in_last_chunk = false
111
119
 
@@ -113,15 +121,6 @@ module Puma
113
121
  @read_buffer = String.new # rubocop: disable Performance/UnfreezeString
114
122
  end
115
123
 
116
- attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
117
- :tempfile, :io_buffer, :http_content_length_limit_exceeded,
118
- :requests_served, :bytes_read
119
-
120
- attr_writer :peerip, :http_content_length_limit
121
-
122
- attr_accessor :remote_addr_header, :listener,
123
- :reactor_error
124
-
125
124
  # Remove in Puma 7?
126
125
  def closed?
127
126
  @to_io.closed?
@@ -164,14 +163,15 @@ module Puma
164
163
  @parser.reset
165
164
  @io_buffer.reset
166
165
  @read_header = true
167
- @read_proxy = !!@expect_proxy_proto && @requests_served.zero?
166
+ @read_proxy = !!@expect_proxy_proto
168
167
  @env = @proto_env.dup
169
168
  @parsed_bytes = 0
170
169
  @ready = false
171
170
  @body_remain = 0
172
171
  @peerip = nil if @remote_addr_header
173
172
  @in_last_chunk = false
174
- @http_content_length_limit_exceeded = false
173
+ @http_content_length_limit_exceeded = nil
174
+ @error_status_code = nil
175
175
  end
176
176
 
177
177
  # only used with back-to-back requests contained in the buffer
@@ -181,12 +181,7 @@ module Puma
181
181
 
182
182
  @parsed_bytes = parser_execute
183
183
 
184
- if @parser.finished?
185
- return setup_body
186
- elsif @parsed_bytes >= MAX_HEADER
187
- raise HttpParserError,
188
- "HEADER is longer than allowed, aborting client early."
189
- end
184
+ @parser.finished? ? process_env_body : false
190
185
  end
191
186
  end
192
187
 
@@ -218,56 +213,27 @@ module Puma
218
213
  def try_to_parse_proxy_protocol
219
214
  if @read_proxy
220
215
  if @expect_proxy_proto == :v1
221
- crlf_index = @buffer.index "\r\n"
222
-
223
- unless crlf_index
224
- if "PROXY ".start_with? @buffer
225
- return false
226
- elsif @buffer.start_with? "PROXY "
227
- if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH
228
- raise ConnectionError, "PROXY protocol v1 line is too long"
216
+ if @buffer.include? "\r\n"
217
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
218
+ if md[1]
219
+ @peerip = md[1].split(" ")[0]
229
220
  end
230
- return false
221
+ @buffer = md.post_match
231
222
  end
232
-
223
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
224
+ # request, this is just HTTP from a non-PROXY client; move on
233
225
  @read_proxy = false
234
- return true
235
- end
236
-
237
- if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH
238
- raise ConnectionError, "PROXY protocol v1 line is too long"
239
- end
240
-
241
- if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
242
- if md[1]
243
- @peerip = md[1].split(" ")[0]
244
- end
245
- @buffer = md.post_match
226
+ return @buffer.size > 0
227
+ else
228
+ return false
246
229
  end
247
- # if the buffer has a \r\n but doesn't have a PROXY protocol
248
- # request, this is just HTTP from a non-PROXY client; move on
249
- @read_proxy = false
250
- return @buffer.size > 0
251
230
  end
252
231
  end
253
232
  true
254
233
  end
255
234
 
256
- def try_to_finish(&deadline)
257
- return false unless flush_pending_write
258
-
259
- if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
260
- @http_content_length_limit_exceeded = true
261
- end
262
-
263
- if @http_content_length_limit_exceeded
264
- @buffer = nil
265
- @body = EmptyBody
266
- set_ready
267
- return true
268
- end
269
-
270
- return read_body(&deadline) if in_data_phase
235
+ def try_to_finish
236
+ return read_body if in_data_phase
271
237
 
272
238
  data = nil
273
239
  begin
@@ -287,7 +253,6 @@ module Puma
287
253
  raise EOFError
288
254
  end
289
255
 
290
- @bytes_read += data.bytesize
291
256
  if @buffer
292
257
  @buffer << data
293
258
  else
@@ -298,74 +263,32 @@ module Puma
298
263
 
299
264
  @parsed_bytes = parser_execute
300
265
 
301
- if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
302
- @http_content_length_limit_exceeded = true
303
- end
304
-
305
- if @parser.finished?
306
- setup_body
307
- elsif @parsed_bytes >= MAX_HEADER
308
- raise HttpParserError,
309
- "HEADER is longer than allowed, aborting client early."
310
- else
311
- false
312
- end
266
+ @parser.finished? ? process_env_body : false
313
267
  end
314
268
 
315
- def eagerly_finish(&deadline)
269
+ def eagerly_finish
316
270
  return true if @ready
317
- while !write_pending? && @to_io.wait_readable(0)
318
- read = @bytes_read
319
- return true if try_to_finish(&deadline)
320
- check_deadline(&deadline) if deadline
321
- return false if @bytes_read == read
271
+ while @to_io.wait_readable(0) # rubocop: disable Style/WhileUntilModifier
272
+ return true if try_to_finish
322
273
  end
323
274
  false
324
275
  end
325
276
 
326
- def finish(timeout, &deadline)
277
+ def finish(timeout)
327
278
  return if @ready
328
- set_timeout timeout
329
- read = @bytes_read
330
- until try_to_finish(&deadline)
331
- if @bytes_read > read
332
- set_timeout timeout
333
- read = @bytes_read
334
- elsif self.timeout == 0
335
- timeout!
336
- end
337
- if deadline
338
- check_deadline(&deadline)
339
- limit = yield
340
- wait = [self.timeout, limit ? limit - Process.clock_gettime(Process::CLOCK_MONOTONIC) : 0.1].min
341
- wait_io(wait) if wait > 0
342
- else
343
- wait_io(self.timeout) || timeout!
344
- end
345
- end
346
- end
347
-
348
- def write_pending?
349
- !@pending_write.nil? || (@io.respond_to?(:write_pending?) && @io.write_pending?)
279
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
350
280
  end
351
281
 
352
- def take_pending_write
353
- str = @pending_write
354
- @pending_write = nil
355
- str
356
- end
357
-
358
- def wait_io(timeout)
359
- return @to_io.wait_readable(timeout) unless write_pending?
360
- @to_io.wait_writable(timeout)
361
- end
362
- private :wait_io
363
-
364
282
  # Wraps `@parser.execute` and adds meaningful error messages
365
283
  # @return [Integer] bytes of buffer read by parser
366
284
  #
367
285
  def parser_execute
368
- @parser.execute(@env, @buffer, @parsed_bytes)
286
+ ret = @parser.execute(@env, @buffer, @parsed_bytes)
287
+
288
+ if @env[REQUEST_METHOD] && @supported_http_methods != :any && !@supported_http_methods.key?(@env[REQUEST_METHOD])
289
+ raise HttpParserError501, "#{@env[REQUEST_METHOD]} method is not supported"
290
+ end
291
+ ret
369
292
  rescue => e
370
293
  @env[HTTP_CONNECTION] = 'close'
371
294
  raise e unless HttpParserError === e && e.message.include?('non-SSL')
@@ -399,6 +322,15 @@ module Puma
399
322
  end
400
323
  end
401
324
 
325
+ # processes the `env` and the request body
326
+ def process_env_body
327
+ temp = setup_body
328
+ normalize_env
329
+ req_env_post_parse
330
+ raise HttpParserError if @error_status_code
331
+ temp
332
+ end
333
+
402
334
  def timeout!
403
335
  write_error(408) if in_data_phase
404
336
  raise ConnectionError
@@ -406,51 +338,21 @@ module Puma
406
338
 
407
339
  def write_error(status_code)
408
340
  begin
409
- write_interim ERROR_RESPONSE[status_code]
341
+ @io << ERROR_RESPONSE[status_code]
410
342
  rescue StandardError
411
343
  end
412
344
  end
413
345
 
414
- def write_interim(str)
415
- if @pending_write
416
- @pending_write << str
417
- return flush_pending_write
418
- end
419
-
420
- n = @io.write_nonblock(str, exception: false)
421
- if n == :wait_writable
422
- @pending_write = +str unless @io.respond_to?(:write_pending?) && @io.write_pending?
423
- elsif n < str.bytesize
424
- @pending_write = str.byteslice(n..-1)
425
- end
426
- rescue SystemCallError, IOError
427
- raise ConnectionError, "Connection error detected during write"
428
- end
429
- private :write_interim
430
-
431
- def flush_pending_write
432
- return true unless @pending_write
433
-
434
- n = @io.write_nonblock(@pending_write, exception: false)
435
- return false if n == :wait_writable
436
-
437
- @pending_write = n == @pending_write.bytesize ? nil : @pending_write.byteslice(n..-1)
438
- @pending_write.nil?
439
- rescue SystemCallError, IOError
440
- raise ConnectionError, "Connection error detected during write"
441
- end
442
- private :flush_pending_write
443
-
444
346
  def peerip
445
347
  return @peerip if @peerip
446
348
 
447
349
  if @remote_addr_header
448
- hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
350
+ hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
449
351
  @peerip = hdr
450
352
  return hdr
451
353
  end
452
354
 
453
- @peerip ||= @io.peeraddr.last
355
+ @peerip ||= socket_peerip
454
356
  end
455
357
 
456
358
  def peer_family
@@ -485,18 +387,36 @@ module Puma
485
387
 
486
388
  private
487
389
 
390
+ IPV4_MAPPED_IPV6_PREFIX = "::ffff:"
391
+ private_constant :IPV4_MAPPED_IPV6_PREFIX
392
+
393
+ def socket_peerip
394
+ unmap_ipv6(@io.peeraddr.last)
395
+ end
396
+
397
+ # Converts IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) back to
398
+ # their IPv4 form. These addresses appear when IPv4 clients connect to
399
+ # a dual-stack IPv6 socket.
400
+ def unmap_ipv6(addr)
401
+ addr.delete_prefix(IPV4_MAPPED_IPV6_PREFIX)
402
+ end
403
+
404
+ # Checks the request `Transfer-Encoding` and/or `Content-Length` to see if
405
+ # they are valid. Raises errors if not, otherwise reads the body.
406
+ # @return [Boolean] true if the body can be completely read, false otherwise
407
+ #
488
408
  def setup_body
489
409
  @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
490
410
 
491
411
  if @env[HTTP_EXPECT] == CONTINUE
492
- # TODO allow a hook here to check the headers before
493
- # going forward
494
- write_interim HTTP_11_100
412
+ # TODO allow a hook here to check the headers before going forward
413
+ @io << HTTP_11_100
414
+ @io.flush
495
415
  end
496
416
 
497
417
  @read_header = false
498
418
 
499
- body = @parser.body
419
+ parser_body = @parser.body
500
420
 
501
421
  te = @env[TRANSFER_ENCODING2]
502
422
  if te
@@ -513,10 +433,10 @@ module Puma
513
433
  raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
514
434
  end
515
435
  @env.delete TRANSFER_ENCODING2
516
- return setup_chunked_body body
436
+ return setup_chunked_body parser_body
517
437
  elsif te_lwr == CHUNKED
518
438
  @env.delete TRANSFER_ENCODING2
519
- return setup_chunked_body body
439
+ return setup_chunked_body parser_body
520
440
  elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
521
441
  raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
522
442
  else
@@ -531,10 +451,12 @@ module Puma
531
451
  if cl
532
452
  # cannot contain characters that are not \d, or be empty
533
453
  if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
454
+ @error_status_code = 400
455
+ @env[HTTP_CONNECTION] = 'close'
534
456
  raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
535
457
  end
536
458
  else
537
- @buffer = body.empty? ? nil : body
459
+ @buffer = parser_body.empty? ? nil : parser_body
538
460
  @body = EmptyBody
539
461
  set_ready
540
462
  return true
@@ -542,23 +464,25 @@ module Puma
542
464
 
543
465
  content_length = cl.to_i
544
466
 
545
- remain = content_length - body.bytesize
467
+ raise_above_http_content_limit if @http_content_length_limit&.< content_length
468
+
469
+ remain = content_length - parser_body.bytesize
546
470
 
547
471
  if remain <= 0
548
- # Part of the body is a pipelined request OR garbage. We'll deal with that later.
472
+ # Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
549
473
  if content_length == 0
550
474
  @body = EmptyBody
551
- if body.empty?
475
+ if parser_body.empty?
552
476
  @buffer = nil
553
477
  else
554
- @buffer = body
478
+ @buffer = parser_body
555
479
  end
556
480
  elsif remain == 0
557
- @body = StringIO.new body
481
+ @body = StringIO.new parser_body
558
482
  @buffer = nil
559
483
  else
560
- @body = StringIO.new(body[0,content_length])
561
- @buffer = body[content_length..-1]
484
+ @body = StringIO.new(parser_body[0,content_length])
485
+ @buffer = parser_body[content_length..-1]
562
486
  end
563
487
  set_ready
564
488
  return true
@@ -570,21 +494,21 @@ module Puma
570
494
  @body.binmode
571
495
  @tempfile = @body
572
496
  else
573
- # The body[0,0] trick is to get an empty string in the same
574
- # encoding as body.
575
- @body = StringIO.new body[0,0]
497
+ # The parser_body[0,0] trick is to get an empty string in the same
498
+ # encoding as parser_body.
499
+ @body = StringIO.new parser_body[0,0]
576
500
  end
577
501
 
578
- @body.write body
502
+ @body.write parser_body
579
503
 
580
504
  @body_remain = remain
581
505
 
582
506
  false
583
507
  end
584
508
 
585
- def read_body(&deadline)
509
+ def read_body
586
510
  if @chunked_body
587
- return read_chunked_body(&deadline)
511
+ return read_chunked_body
588
512
  end
589
513
 
590
514
  # Read an odd sized chunk so we can read even sized ones
@@ -609,7 +533,6 @@ module Puma
609
533
  raise EOFError
610
534
  end
611
535
 
612
- @bytes_read += chunk.bytesize
613
536
  remain -= @body.write(chunk)
614
537
  end
615
538
 
@@ -624,7 +547,7 @@ module Puma
624
547
  end
625
548
  end
626
549
 
627
- def read_chunked_body(&deadline)
550
+ def read_chunked_body
628
551
  while true
629
552
  begin
630
553
  chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
@@ -642,27 +565,13 @@ module Puma
642
565
  raise EOFError
643
566
  end
644
567
 
645
- @bytes_read += chunk.bytesize
646
568
  if decode_chunk(chunk)
647
569
  @env[CONTENT_LENGTH] = @chunked_content_length.to_s
648
570
  return true
649
571
  end
650
-
651
- check_deadline(&deadline) if deadline
652
572
  end
653
573
  end
654
574
 
655
- def check_deadline
656
- limit = yield
657
- return unless limit
658
-
659
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
660
- return if now < limit
661
- return if now < limit + SHUTDOWN_READ_GRACE && @to_io.wait_readable(0)
662
-
663
- timeout!
664
- end
665
-
666
575
  def setup_chunked_body(body)
667
576
  @chunked_body = true
668
577
  @partial_part_left = 0
@@ -683,6 +592,10 @@ module Puma
683
592
 
684
593
  # @version 5.0.0
685
594
  def write_chunk(str)
595
+ if @http_content_length_limit&.< @chunked_content_length + str.bytesize
596
+ raise_above_http_content_limit
597
+ end
598
+
686
599
  @chunked_content_length += @body.write(str)
687
600
  end
688
601
 
@@ -815,8 +728,13 @@ module Puma
815
728
  @ready = true
816
729
  end
817
730
 
818
- def above_http_content_limit(value)
819
- @http_content_length_limit&.< value
731
+ def raise_above_http_content_limit
732
+ @http_content_length_limit_exceeded = true
733
+ @buffer = nil
734
+ @body = EmptyBody
735
+ @error_status_code = 413
736
+ @env[HTTP_CONNECTION] = 'close'
737
+ raise HttpParserError, "Payload Too Large"
820
738
  end
821
739
  end
822
740
  end