class Arachni::HTTP::Response

HTTP Response representation.

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constants

HTML_CONTENT_TYPES
HTML_IDENTIFIERS
HTML_IDENTIFIER_REGEXPS

Attributes

app_time[RW]

@return [Float]

Approximate time the web application took to process the {#request}.
code[RW]

@return [Integer]

HTTP response status code.
headers_string[RW]

@return [String]

Raw headers.
ip_address[RW]

@return [String]

IP address of the server.
message[RW]

@return [String]

HTTP response status message.
redirections[RW]

@return [Array<Response>]

Automatically followed redirections that eventually led to this response.
request[RW]

@return [Request]

HTTP {Request} which triggered this {Response}.
return_code[RW]

@return [Symbol]

`libcurl` return code.
return_message[RW]

@return [String]

`libcurl` return code.
time[RW]

@return [Float]

Time, in seconds, it took from the start until the full response was
received.
total_time[RW]

@return [Float]

Total time in seconds for the transfer, including name resolving, TCP
connect etc.

Public Class Methods

from_rpc_data( data ) click to toggle source

@param [Hash] data {#to_rpc_data} @return [Request]

# File lib/arachni/http/response.rb, line 241
def self.from_rpc_data( data )
    data['request']     = Request.from_rpc_data( data['request'] )
    data['return_code'] = data['return_code'].to_sym if data['return_code']
    new data
end
from_typhoeus( response, options = {} ) click to toggle source
# File lib/arachni/http/response.rb, line 285
def self.from_typhoeus( response, options = {} )
    redirections = response.redirections.map do |redirect|
        rurl   = URI.to_absolute( redirect.headers['Location'],
                                  response.effective_url )
        rurl ||= URI.normalize( response.effective_url )

        # Broken redirection, skip it...
        next if !rurl

        new( options.merge(
            url:           rurl,
            code:          redirect.code,
            headers:       redirect.headers
        ))
    end.compact

    return_code    = response.return_code
    return_message = response.return_message

    # A write error in this case will be because body reading was aborted
    # during our own callback in Request#set_body_reader.
    #
    # So, this is here just for consistency.
    if response.return_code == :write_error
        return_code    = :filesize_exceeded
        return_message = 'Maximum file size exceeded'
    end

    new( options.merge(
        url:            response.effective_url,
        code:           response.code,
        ip_address:     response.primary_ip,
        headers:        response.headers,
        headers_string: response.response_headers,
        body:           response.body,
        redirections:   redirections,
        time:           response.time,
        app_time:       (response.timed_out? ? response.time :
                            response.start_transfer_time - response.pretransfer_time).to_f,
        total_time:     response.total_time.to_f,
        return_code:    return_code,
        return_message: return_message
    ))
end
new( options = {} ) click to toggle source
Calls superclass method Arachni::HTTP::Message::new
# File lib/arachni/http/response.rb, line 70
def initialize( options = {} )
    super( options )

    @body ||= ''
    @code ||= 0

    # Holds the redirection responses that eventually led to this one.
    @redirections ||= []

    @time ||= 0.0
end

Public Instance Methods

==( other ) click to toggle source
# File lib/arachni/http/response.rb, line 247
def ==( other )
    hash == other.hash
end
body=( body ) click to toggle source
# File lib/arachni/http/response.rb, line 193
def body=( body )
    @body = body.to_s

    text_check = text?
    @body.recode! if text_check.nil? || text_check

    @body
end
hash() click to toggle source
# File lib/arachni/http/response.rb, line 251
def hash
    to_h.hash
end
headers_string=( string ) click to toggle source
# File lib/arachni/http/response.rb, line 124
def headers_string=( string )
    @headers_string = string.to_s.recode.freeze
end
html?() click to toggle source
# File lib/arachni/http/response.rb, line 175
def html?
    # IF we've got a Content-Type that's all we need to know.
    if (ct = headers.content_type)
        ct = ct.split( ';' ).first
        ct.strip!
        return HTML_CONTENT_TYPES.include?( ct.downcase )
    end

    # Server insists we should only only use the content-type. respect it.
    return false if headers['X-Content-Type-Options'].to_s.downcase.include?( 'nosniff' )

    # If there's a doctype then we're good to go.
    return true if body.start_with?( '<!DOCTYPE html' )

    # Last resort, sniff the content-type from several HTML tags.
    HTML_IDENTIFIER_REGEXPS.find { |regexp| body =~ regexp }
end
modified?() click to toggle source

@note Depends on the response code.

@return [Boolean]

`true` if the remote resource has been modified since the date given in
the `If-Modified-Since` request header field, `false` otherwise.
# File lib/arachni/http/response.rb, line 133
def modified?
    code != 304
end
ok?() click to toggle source

@return [Boolean]

`true` if the request was performed successfully and the response was
received in full, `false` otherwise.
# File lib/arachni/http/response.rb, line 140
def ok?
    !return_code || return_code == :ok
end
parse() click to toggle source
# File lib/arachni/http/response.rb, line 207
def parse
    Parser.new self
end
partial?() click to toggle source

@return [Boolean]

`true` if the client could not read the entire response, `false` otherwise.
# File lib/arachni/http/response.rb, line 88
def partial?
    # Streamed response which was aborted before completing.
    return_code == :partial_file ||
        return_code == :recv_error ||
        # Normal response with some data written, but without reaching
        # content-length.
        (code != 0 && timed_out?)
end
platforms() click to toggle source

@return [Platform]

Applicable platforms for the page.
# File lib/arachni/http/response.rb, line 99
def platforms
    Platform::Manager[url]
end
redirect?() click to toggle source

@return [Boolean]

`true` if the response is a `3xx` redirect **and** there is a `Location`
header field.
# File lib/arachni/http/response.rb, line 119
def redirect?
    code >= 300 && code <= 399 && !!headers.location
end
Also aliased as: redirection?
redirection?()
Alias for: redirect?
status_line() click to toggle source

@return [String]

First line of the response.
# File lib/arachni/http/response.rb, line 105
def status_line
    return if !headers_string
    @status_line ||= headers_string.lines.first.to_s.chomp.freeze
end
text?() click to toggle source

@return [Bool]

`true` if the response body is textual in nature, `false` if binary,
`nil` if could not be determined.
# File lib/arachni/http/response.rb, line 147
def text?
    return nil      if !@body
    return nil      if @is_text == :inconclusive
    return @is_text if !@is_text.nil?

    if (type = headers.content_type)
        return @is_text = true if type.start_with?( 'text/' )

        # Non "text/" nor "application/" content types will surely not be
        # text-based so bail out early.
        return @is_text = false if !type.start_with?( 'application/' )
    end

    # Last resort, more resource intensive binary detection.
    begin
        @is_text = !@body.binary?
    rescue ArgumentError
        @is_text = :inconclusive
        nil
    end
end
time=( t ) click to toggle source
# File lib/arachni/http/response.rb, line 82
def time=( t )
    @time = t.to_f
end
timed_out?() click to toggle source

@return [Boolean]

`true` if timed out, `false` otherwise.
# File lib/arachni/http/response.rb, line 171
def timed_out?
    return_code == :operation_timedout
end
to_h() click to toggle source

@return [Hash]

# File lib/arachni/http/response.rb, line 212
def to_h
    hash = {}
    instance_variables.each do |var|
        hash[var.to_s.gsub( /@/, '' ).to_sym] = instance_variable_get( var )
    end

    hash[:headers] = {}.merge( hash[:headers] )

    hash.delete( :normalize_url )
    hash.delete( :is_text )
    hash.delete( :scope )
    hash.delete( :parsed_url )
    hash.delete( :redirections )
    hash.delete( :request )
    hash.delete( :scope )

    hash
end
to_page() click to toggle source

@return [Arachni::Page]

# File lib/arachni/http/response.rb, line 203
def to_page
    Page.from_response self
end
to_rpc_data() click to toggle source

@return [Hash]

Data representing this instance that are suitable the RPC transmission.
# File lib/arachni/http/response.rb, line 233
def to_rpc_data
    data = to_h
    data[:request] = request.to_rpc_data
    data.my_stringify_keys(false)
end
to_s() click to toggle source

@return [String]

HTTP response string.
# File lib/arachni/http/response.rb, line 112
def to_s
    "#{headers_string}#{body}"
end
update_from_typhoeus( response, options = {} ) click to toggle source
# File lib/arachni/http/response.rb, line 255
def update_from_typhoeus( response, options = {} )
    return_code    = response.return_code
    return_message = response.return_message

    # A write error in this case will be because body reading was aborted
    # during our own callback in Request#set_body_reader.
    #
    # So, this is here just for consistency.
    if response.return_code == :write_error
        return_code    = :filesize_exceeded
        return_message = 'Maximum file size exceeded'
    end

    update( options.merge(
        url:            response.effective_url,
        code:           response.code,
        ip_address:     response.primary_ip,
        headers:        response.headers,
        headers_string: response.response_headers,
        body:           response.body,
        redirections:   redirections,
        time:           response.time,
        app_time:       (response.timed_out? ? response.time :
            response.start_transfer_time - response.pretransfer_time).to_f,
        total_time:     response.total_time.to_f,
        return_code:    return_code,
        return_message: return_message
    ))
end