class Arachni::HTTP::ProxyServer::Connection
Constants
- SKIP_HEADERS
Attributes
parent[R]
request[R]
Public Class Methods
new( options = {} )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 23 def initialize( options = {} ) @options = options @parent = options[:parent] @body = '' @parser = ::HTTP::Parser.new @raw_request = '' @parser.on_message_begin = proc do if @reused print_debug_level_3 "Reusing connection: #{object_id}" else print_debug_level_3 "Starting new connection: #{object_id}" end @reused = true print_debug_level_3 'Incoming request.' @parent.mark_connection_active self end @parser.on_body = proc do |chunk| print_debug_level_3 "Got #{chunk.size} bytes." @body << chunk end @parser.on_message_complete = proc do method = @parser.http_method.downcase.to_sym headers = cleanup_request_headers( @parser.headers ) print_debug_level_3 "Request received: #{@parser.http_method} #{@parser.request_url}" if headers['upgrade'] handle_upgrade( headers ) next end if method == :connect handle_connect( headers ) next end if !@parent.has_available_request_tokens? print_debug_level_3 'Waiting for a request token.' end @parent.get_request_token do |token| print_debug_level_3 "Got request token ##{token}." if closed? print_debug_level_3 'Connection closed while waiting for a request token.' @parent.return_request_token( token ) print_debug_level_3 "Returned request token ##{token}." next end Thread.new do begin @request = Arachni::HTTP::Request.new( http_opts.merge( url: sanitize_url( @parser.request_url, headers ), method: method, body: @body, headers: Arachni::HTTP::Client.headers.to_h.merge( headers ) ) ) handle_request( @request ) rescue => e close e ensure @parent.return_request_token( token ) print_debug_level_3 "Returned request token ##{token}." end end end end end
Public Instance Methods
cleanup_request_headers( headers )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 287 def cleanup_request_headers( headers ) headers = Arachni::HTTP::Headers.new( headers ) SKIP_HEADERS.each do |name| headers.delete name end headers.to_h end
cleanup_response_headers( headers )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 297 def cleanup_response_headers( headers ) SKIP_HEADERS.each do |name| headers.delete name end headers end
handle_connect( headers )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 116 def handle_connect( headers ) print_debug_level_3 'Preparing to intercept.' host = (headers['Host'] || @parser.request_url).split( ':', 2 ).first start_interceptor( host ) # This is our last HTTP message, from this point on we'll only be # tunnelling to the interceptor. @last_http = true write "HTTP/#{http_version} 200\r\n\r\n" end
handle_request( request )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 128 def handle_request( request ) print_debug_level_3 'Processing request.' if @options[:request_handler] print_debug_level_3 "-- Has special handler: #{@options[:request_handler]}" # Provisional empty, response in case the request_handler wants us to # skip performing the request. response = Response.new( url: request.url ) response.request = request # If the handler returns false then don't perform the HTTP request. if @options[:request_handler].call( request, response ) print_debug_level_3 '-- Handler approves, running...' response = request.run print_debug_level_3 "-- ...completed in #{response.time}: #{response.status_line}" else print_debug_level_3 '-- Handler did not approve, will not run.' end else print_debug_level_3 '-- Running...' response = request.run print_debug_level_3 "-- ...completed in #{response.time}: #{response.status_line}" end print_debug_level_3 'Processed request.' reactor.schedule { handle_response( response ) } end
handle_response( response )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 166 def handle_response( response ) print_debug_level_3 'Preparing response.' # Connection was rudely closed before we had a chance to respond, # don't bother proceeding. if closed? print_debug_level_3 '-- Connection closed, will not respond.' return end if @options[:response_handler] print_debug_level_3 "-- Has special handler: #{@options[:response_handler]}" @options[:response_handler].call( response.request, response ) end code = response.code if response.code == 0 code = 504 end res = "HTTP/#{http_version} #{code}\r\n" headers = cleanup_response_headers( response.headers ) headers['Content-Length'] = response.body.bytesize if response.text? && headers.content_type headers['Content-Type'] = "#{headers.content_type.split( ';' ).first}; charset=utf-8" end headers.each do |k, v| if v.is_a?( Array ) v.flatten.each do |h| res << "#{k}: #{h.gsub(/[\n\r]/, '')}\r\n" end next end res << "#{k}: #{v}\r\n" end res << "\r\n" print_debug_level_3 "Sending response for: #{@request.url}" write (res << response.body) end
handle_upgrade( headers )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 103 def handle_upgrade( headers ) print_debug_level_3 'Preparing to upgrade.' host = (headers['Host'] || @parser.request_url).split( ':', 2 ).first @tunnel = reactor.connect( host, 80, Tunnel, @options.merge( client: self ) ) # This is our last HTTP message, from this point on we'll only be # tunnelling to the origin server. @last_http = true @tunnel.write @raw_request end
http_opts( options = {} )
click to toggle source
@param [Hash] options
Merges the given HTTP options with some default ones.
# File lib/arachni/http/proxy_server/connection.rb, line 319 def http_opts( options = {} ) options.merge( performer: self, # Don't follow redirects, the client should handle this. follow_location: false, # Set the HTTP request timeout. timeout: @options[:timeout], # Update the framework-wide cookie-jar with the transmitted cookies. update_cookies: true, # We perform the request in blocking mode, parallelism is up to the # proxy client. mode: :sync, # Don't limit the response size when using the proxy. response_max_size: -1 ) end
http_version()
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 162 def http_version @parser.http_version.join('.') end
on_close( reason = nil )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 215 def on_close( reason = nil ) print_debug_level_3 "Closed because: [#{reason.class}] #{reason}" @parent.mark_connection_inactive self if @ssl_interceptor @ssl_interceptor.close( reason ) @ssl_interceptor = nil end if @tunnel @tunnel.close_without_callback @tunnel = nil end end
on_flush()
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 231 def on_flush if !@tunnel || @last_http if @last_http print_debug_level_3 'Last response sent, switching to tunnel.' elsif @request print_debug_level_3 "Response sent for: #{@request.url}" end @last_http = false end @body = '' @raw_request = '' @request = nil @parser.reset! @parent.mark_connection_inactive self end
on_read( data )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 256 def on_read( data ) # We need this in case we need to establish a tunnel for an "Upgrade". @raw_request << data if @tunnel @tunnel.write( data ) return end # ap data @parser << data rescue ::HTTP::Parser::Error => e close e end
sanitize_url( str, headers )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 304 def sanitize_url( str, headers ) uri = Arachni::URI( str ) return uri.to_s if uri.absolute? host, port = *headers['Host'].split( ':', 2 ) uri.scheme = self.is_a?( SSLInterceptor ) ? 'https' : 'http' uri.host = host uri.port = port ? port.to_i : nil uri.to_s end
start_interceptor( origin_host )
click to toggle source
# File lib/arachni/http/proxy_server/connection.rb, line 271 def start_interceptor( origin_host ) @interceptor_port = Utilities.available_port print_debug_level_3 "Starting interceptor on port: #{@interceptor_port}" @ssl_interceptor = reactor.listen( @options[:address], @interceptor_port, SSLInterceptor, @options.merge( origin_host: origin_host ) ) @tunnel = reactor.connect( @options[:address], @interceptor_port, Tunnel, @options.merge( client: self ) ) end
write( data )
click to toggle source
Calls superclass method
# File lib/arachni/http/proxy_server/connection.rb, line 251 def write( data ) return if closed? super data end