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