File chunked-request-handling.patch of Package rubygem-puma.15815
commit b01169b5a98bc703b0e10a66f10ad57f3b8ad4cd
Author: Evan Phoenix <evan@phx.io>
Date: Sun Jul 24 22:02:23 2016 -0700
Implement chunked request handling. Fixes #620
(cherry picked from commit 5ec232d1d47283195275acccf8d53010b22416f2)
Packager's note: this is needed for CVE-2020-11076.patch to apply.
diff --git a/lib/puma/client.rb b/lib/puma/client.rb
index ac2609a3a79e..c02b7baf90ba 100644
--- a/lib/puma/client.rb
+++ b/lib/puma/client.rb
@@ -7,6 +7,7 @@ class IO
end
require 'puma/detect'
+require 'tempfile'
if Puma::IS_JRUBY
# We have to work around some OpenSSL buffer/io-readiness bugs
@@ -113,9 +114,116 @@ module Puma
# no body share this one object since it has no state.
EmptyBody = NullIO.new
+ def setup_chunked_body(body)
+ @chunked_body = true
+ @partial_part_left = 0
+ @prev_chunk = ""
+
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
+ @body.binmode
+ @tempfile = @body
+
+ return decode_chunk(body)
+ end
+
+ def decode_chunk(chunk)
+ if @partial_part_left > 0
+ if @partial_part_left <= chunk.size
+ @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
+ chunk = chunk[@partial_part_left..-1]
+ else
+ @body << chunk
+ @partial_part_left -= chunk.size
+ return false
+ end
+ end
+
+ if @prev_chunk.empty?
+ io = StringIO.new(chunk)
+ else
+ io = StringIO.new(@prev_chunk+chunk)
+ @prev_chunk = ""
+ end
+
+ while !io.eof?
+ line = io.gets
+ if line.end_with?("\r\n")
+ len = line.strip.to_i(16)
+ if len == 0
+ @body.rewind
+ @buffer = nil
+ @requests_served += 1
+ @ready = true
+ return true
+ end
+
+ len += 2
+
+ part = io.read(len)
+
+ unless part
+ @partial_part_left = len
+ next
+ end
+
+ got = part.size
+
+ case
+ when got == len
+ @body << part[0..-3] # to skip the ending \r\n
+ when got <= len - 2
+ @body << part
+ @partial_part_left = len - part.size
+ when got == len - 1 # edge where we get just \r but not \n
+ @body << part[0..-2]
+ @partial_part_left = len - part.size
+ end
+ else
+ @prev_chunk = line
+ return false
+ end
+ end
+
+ return false
+ end
+
+ def read_chunked_body
+ while true
+ begin
+ chunk = @io.read_nonblock(4096)
+ rescue Errno::EAGAIN
+ return false
+ rescue SystemCallError, IOError
+ raise ConnectionError, "Connection error detected during read"
+ end
+
+ # No chunk means a closed socket
+ unless chunk
+ @body.close
+ @buffer = nil
+ @requests_served += 1
+ @ready = true
+ raise EOFError
+ end
+
+ return true if decode_chunk(chunk)
+ end
+ end
+
def setup_body
@in_data_phase = true
+ @read_header = false
+
body = @parser.body
+
+ te = @env[TRANSFER_ENCODING2]
+
+ if te == CHUNKED
+ return setup_chunked_body(body)
+ end
+
+ @chunked_body = false
+
cl = @env[CONTENT_LENGTH]
unless cl
@@ -150,8 +258,6 @@ module Puma
@body_remain = remain
- @read_header = false
-
return false
end
@@ -242,6 +348,10 @@ module Puma
end
def read_body
+ if @chunked_body
+ return read_chunked_body
+ end
+
# Read an odd sized chunk so we can read even sized ones
# after this
remain = @body_remain
diff --git a/lib/puma/const.rb b/lib/puma/const.rb
index d074ad23b925..7bf0efc2b4e0 100644
--- a/lib/puma/const.rb
+++ b/lib/puma/const.rb
@@ -228,6 +228,7 @@ module Puma
CONTENT_LENGTH2 = "content-length".freeze
CONTENT_LENGTH_S = "Content-Length: ".freeze
TRANSFER_ENCODING = "transfer-encoding".freeze
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
CONNECTION_CLOSE = "Connection: close\r\n".freeze
CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
@@ -235,6 +236,8 @@ module Puma
TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
CLOSE_CHUNKED = "0\r\n\r\n".freeze
+ CHUNKED = "chunked".freeze
+
COLON = ": ".freeze
NEWLINE = "\n".freeze