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