#– 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 #–
# 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
# 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
# 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
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
# 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
# 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
# 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
# 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