class Puma::Client

def decode_chunk(chunk)

def decode_chunk(chunk)
  if @partial_part_left > 0
    if @partial_part_left <= chunk.size
      if @partial_part_left > 2
        write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
      end
      chunk = chunk[@partial_part_left..-1]
      @partial_part_left = 0
    else
      if @partial_part_left > 2
        if @partial_part_left == chunk.size + 1
          # Don't include the last \r
          write_chunk(chunk[0..(@partial_part_left-3)])
        else
          # don't include the last \r\n
          write_chunk(chunk)
        end
      end
      @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?(CHUNK_VALID_ENDING)
      # Puma doesn't process chunk extensions, but should parse if they're
      # present, which is the reason for the semicolon regex
      chunk_hex = line.strip[/\A[^;]+/]
      if CHUNK_SIZE_INVALID.match? chunk_hex
        raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
      end
      len = chunk_hex.to_i(16)
      if len == 0
        @in_last_chunk = true
        @body.rewind
        rest = io.read
        if rest.bytesize < CHUNK_VALID_ENDING_SIZE
          @buffer = nil
          @partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
          return false
        else
          # if the next character is a CRLF, set buffer to everything after that CRLF
          start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
            CHUNK_VALID_ENDING_SIZE
          else # we have started a trailer section, which we do not support. skip it!
            rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
          end
          @buffer = rest[start_of_rest..-1]
          @buffer = nil if @buffer.empty?
          set_ready
          return true
        end
      end
      # Track the excess as a function of the size of the
      # header vs the size of the actual data. Excess can
      # go negative (and is expected to) when the body is
      # significant.
      # The additional of chunk_hex.size and 2 compensates
      # for a client sending 1 byte in a chunked body over
      # a long period of time, making sure that that client
      # isn't accidentally eventually punished.
      @excess_cr += (line.size - len - chunk_hex.size - 2)
      if @excess_cr >= MAX_CHUNK_EXCESS
        raise HttpParserError, "Maximum chunk excess detected"
      end
      len += 2
      part = io.read(len)
      unless part
        @partial_part_left = len
        next
      end
      got = part.size
      case
      when got == len
        # proper chunked segment must end with "\r\n"
        if part.end_with? CHUNK_VALID_ENDING
          write_chunk(part[0..-3]) # to skip the ending \r\n
        else
          raise HttpParserError, "Chunk size mismatch"
        end
      when got <= len - 2
        write_chunk(part)
        @partial_part_left = len - part.size
      when got == len - 1 # edge where we get just \r but not \n
        write_chunk(part[0..-2])
        @partial_part_left = len - part.size
      end
    else
      if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
        raise HttpParserError, "maximum size of chunk header exceeded"
      end
      @prev_chunk = line
      return false
    end
  end
  if @in_last_chunk
    set_ready
    true
  else
    false
  end
end