class EventMachine::Protocols::HttpClient
def receive_data data
def receive_data data while data and data.length > 0 case @read_state when :base # Perform any per-request initialization here and don't consume any data. @data = "" @headers = [] @content_length = nil # not zero @content = "" @status = nil @chunked = false @chunk_length = nil @read_state = :header @connection_close = nil when :header ary = data.split( /\r?\n/m, 2 ) if ary.length == 2 data = ary.last if ary.first == "" if (@content_length and @content_length > 0) || @chunked || @connection_close @read_state = :content else dispatch_response @read_state = :base end else @headers << ary.first if @headers.length == 1 parse_response_line elsif ary.first =~ /\Acontent-length:\s*/i # Only take the FIRST content-length header that appears, # which we can distinguish because @content_length is nil. # TODO, it's actually a fatal error if there is more than one # content-length header, because the caller is presumptively # a bad guy. (There is an exploit that depends on multiple # content-length headers.) @content_length ||= $'.to_i elsif ary.first =~ /\Aconnection:\s*close/i @connection_close = true elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i @chunked = true end end else @data << data data = "" end when :content if @chunked && @chunk_length bytes_needed = @chunk_length - @chunk_read new_data = data[0, bytes_needed] @chunk_read += new_data.length @content += new_data data = data[bytes_needed..-1] || "" if @chunk_length == @chunk_read && data[0,2] == "\r\n" @chunk_length = nil data = data[2..-1] end elsif @chunked if (m = data.match(/\A(\S*)\r\n/m)) data = data[m[0].length..-1] @chunk_length = m[1].to_i(16) @chunk_read = 0 if @chunk_length == 0 dispatch_response @read_state = :base end end elsif @content_length # If there was no content-length header, we have to wait until the connection # closes. Everything we get until that point is content. # TODO: Must impose a content-size limit, and also must implement chunking. # Also, must support either temporary files for large content, or calling # a content-consumer block supplied by the user. bytes_needed = @content_length - @content.length @content += data[0, bytes_needed] data = data[bytes_needed..-1] || "" if @content_length == @content.length dispatch_response @read_state = :base end else @content << data data = "" end end end end