module EventMachine

#– Copyright (C)2008 Ilya Grigorik

Includes portion originally Copyright (C)2007 Tony Arcieri Includes portion originally Copyright (C)2005 Zed Shaw You can redistribute this under the terms of the Ruby license See file LICENSE for details #–

Constants

OriginalHttpRequest

Public Instance Methods

parse_chunk_header() click to toggle source
# File lib/em-http/client.rb, line 769
def parse_chunk_header
  return false unless parse_header(@chunk_header)

  @bytes_remaining = @chunk_header.chunk_size
  @chunk_header = HttpChunkHeader.new

  @state = @bytes_remaining > 0 ? :chunk_body : :response_footer
  true
end
parse_header(header) click to toggle source
# File lib/em-http/client.rb, line 550
def parse_header(header)
  return false if @data.empty?

  begin
    @parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
  rescue EventMachine::HttpClientParserError
    @state = :invalid
    on_error "invalid HTTP format, parsing fails"
  end

  return false unless @parser.finished?

  # Clear parsed data from the buffer
  @data.read(@parser_nbytes)
  @parser.reset
  @parser_nbytes = 0

  true
end
parse_response_header() click to toggle source
# File lib/em-http/client.rb, line 570
def parse_response_header
  return false unless parse_header(@response_header)

  # invoke headers callback after full parse if one
  # is specified by the user
  @headers.call(@response_header) if @headers

  unless @response_header.http_status and @response_header.http_reason
    @state = :invalid
    on_error "no HTTP response"
    return false
  end

  if @state == :connect_http_proxy
    # when a successfull tunnel is established, the proxy responds with a
    # 200 response code. from here, the tunnel is transparent.
    if @response_header.http_status.to_i == 200
      @response_header = HttpResponseHeader.new
      connection_completed
      return true
    else
      @state = :invalid
      on_error "proxy not accessible"
      return false
    end
  end

  # correct location header - some servers will incorrectly give a relative URI
  if @response_header.location
    begin
      location = Addressable::URI.parse(@response_header.location)

      if location.relative?
        location = @uri.join(location)
        @response_header[LOCATION] = location.to_s
      else
        # if redirect is to an absolute url, check for correct URI structure
        raise if location.host.nil?
      end

      # store last url on any sign of redirect
      @last_effective_url = location

    rescue
      on_error "Location header format error"
      return false
    end
  end

  # Fire callbacks immediately after recieving header requests
  # if the request method is HEAD. In case of a redirect, terminate
  # current connection and reinitialize the process.
  if @method == "HEAD"
    @state = :finished
    close_connection
    return false
  end

  if websocket?
    if @response_header.status == 101
      @state = :websocket
      succeed
    else
      fail "websocket handshake failed"
    end

  elsif @response_header.chunked_encoding?
    @state = :chunk_header
  elsif @response_header.content_length
    @state = :body
    @bytes_remaining = @response_header.content_length
  else
    @state = :body
    @bytes_remaining = nil
  end

  if decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
    begin
      @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
    rescue HttpDecoders::DecoderError
      on_error "Content-decoder error"
    end
  end

  if ''.respond_to?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(response_header[CONTENT_TYPE])
    @content_charset = Encoding.find $1
  end

  true
end
parse_socks_response() click to toggle source

parses socks 5 server responses as specified on www.faqs.org/rfcs/rfc1928.html

# File lib/em-http/client.rb, line 678
def parse_socks_response
  if @socks_state == :method_negotiation
    return false unless has_bytes? 2

    _, method = @data.read(2).unpack('CC')

    if socks_methods.include?(method)
      if method == 0
        @socks_state = :connecting

        return send_socks_connect_request

      elsif method == 2
        @socks_state = :authenticating

        credentials = @options[:proxy][:authorization]
        if credentials.size < 2
          @state = :invalid
          on_error "username and password are not supplied"
          return false
        end

        username, password = credentials

        send_data [5, username.length, username, password.length, password].pack('CCA*CA*')
      end

    else
      @state = :invalid
      on_error "proxy did not accept method"
      return false
    end

  elsif @socks_state == :authenticating
    return false unless has_bytes? 2

    _, status_code = @data.read(2).unpack('CC')

    if status_code == 0
      # success
      @socks_state = :connecting

      return send_socks_connect_request

    else
      # error
      @state = :invalid
      on_error "access denied by proxy"
      return false
    end

  elsif @socks_state == :connecting
    return false unless has_bytes? 10

    _, response_code, _, address_type, _, _ = @data.read(10).unpack('CCCCNn')

    if response_code == 0
      # success
      @socks_state = :connected
      @state = :proxy_connected

      @response_header = HttpResponseHeader.new

      # connection_completed will invoke actions to
      # start sending all http data transparently
      # over the socks connection
      connection_completed

    else
      # error
      @state = :invalid

      error_messages = {
        1 => "general socks server failure",
        2 => "connection not allowed by ruleset",
        3 => "network unreachable",
        4 => "host unreachable",
        5 => "connection refused",
        6 => "TTL expired",
        7 => "command not supported",
        8 => "address type not supported"
      }
      error_message = error_messages[response_code] || "unknown error (code: #{response_code})"
      on_error "socks5 connect error: #{error_message}"
      return false
    end
  end

  true
end
process_body() click to toggle source
# File lib/em-http/client.rb, line 825
def process_body
  if @bytes_remaining.nil?
    on_body_data @data.read
    return false
  end

  if @bytes_remaining.zero?
    @state = :finished
    on_request_complete
    return false
  end

  if @data.size < @bytes_remaining
    @bytes_remaining -= @data.size
    on_body_data @data.read
    return false
  end

  on_body_data @data.read(@bytes_remaining)
  @bytes_remaining = 0

  # If Keep-Alive is enabled, the server may be pushing more data to us
  # after the first request is complete. Hence, finish first request, and
  # reset state.
  if @response_header.keep_alive?
    @data.clear # hard reset, TODO: add support for keep-alive connections!
    @state = :finished
    on_request_complete

  else
    if @data.empty?
      @state = :finished
      on_request_complete
    else
      @state = :invalid
      on_error "garbage at end of body"
    end
  end

  false
end
process_chunk_body() click to toggle source
# File lib/em-http/client.rb, line 779
def process_chunk_body
  if @data.size < @bytes_remaining
    @bytes_remaining -= @data.size
    on_body_data @data.read
    return false
  end

  on_body_data @data.read(@bytes_remaining)
  @bytes_remaining = 0

  @state = :chunk_footer
  true
end
process_websocket() click to toggle source
# File lib/em-http/client.rb, line 867
def process_websocket
  return false if @data.empty?

  # slice the message out of the buffer and pass in
  # for processing, and buffer data otherwise
  buffer = @data.read
  while msg = buffer.slice!(/\000([^\377]*)\377/)
    msg.gsub!(/\A\x00|\xff\z/, '')
    @stream.call(msg)
  end

  # store remainder if message boundary has not yet
  # been received
  @data << buffer if not buffer.empty?

  false
end
send_socks_connect_request() click to toggle source
# File lib/em-http/client.rb, line 661
def send_socks_connect_request
  # TO-DO: Implement address types for IPv6 and Domain
  begin
    ip_address = Socket.gethostbyname(@uri.host).last
    send_data [5, 1, 0, 1, ip_address, @uri.port].flatten.pack('CCCCA4n')

  rescue
    @state = :invalid
    on_error "could not resolve host", true
    return false
  end

  true
end