Class: Arachni::HTTP::Response

Inherits:
Message show all
Defined in:
lib/arachni/http/response.rb,
lib/arachni/http/response/scope.rb

Overview

HTTP Response representation.

Author:

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

Defined Under Namespace

Classes: Scope

Constant Summary collapse

HTML_CONTENT_TYPES =
Set.new(%w(text/html application/xhtml+xml))
HTML_IDENTIFIERS =
[
    '<!doctype html', '<html', '<head', '<body', '<title', '<script'
]
HTML_IDENTIFIER_REGEXPS =
HTML_IDENTIFIERS.map { |s| Regexp.new s, Regexp::IGNORECASE }

Instance Attribute Summary collapse

Attributes inherited from Message

#body, #headers, #url

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Message

#parsed_url, #scope, #update

Constructor Details

#initialize(options = {}) ⇒ Response

Returns a new instance of Response.



70
71
72
73
74
75
76
77
78
79
80
# 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

Instance Attribute Details

#app_timeFloat

Returns Approximate time the web application took to process the #request.

Returns:

  • (Float)

    Approximate time the web application took to process the #request.



68
69
70
# File 'lib/arachni/http/response.rb', line 68

def app_time
  @app_time
end

#codeInteger

Returns HTTP response status code.

Returns:

  • (Integer)

    HTTP response status code.



26
27
28
# File 'lib/arachni/http/response.rb', line 26

def code
  @code
end

#headers_stringString

Returns Raw headers.

Returns:



54
55
56
# File 'lib/arachni/http/response.rb', line 54

def headers_string
  @headers_string
end

#ip_addressString

Returns IP address of the server.

Returns:

  • (String)

    IP address of the server.



30
31
32
# File 'lib/arachni/http/response.rb', line 30

def ip_address
  @ip_address
end

#messageString

Returns HTTP response status message.

Returns:

  • (String)

    HTTP response status message.



34
35
36
# File 'lib/arachni/http/response.rb', line 34

def message
  @message
end

#redirectionsArray<Response>

Returns Automatically followed redirections that eventually led to this response.

Returns:

  • (Array<Response>)

    Automatically followed redirections that eventually led to this response.



42
43
44
# File 'lib/arachni/http/response.rb', line 42

def redirections
  @redirections
end

#requestRequest

Returns HTTP Arachni::HTTP::Request which triggered this Arachni::HTTP::Response.

Returns:



38
39
40
# File 'lib/arachni/http/response.rb', line 38

def request
  @request
end

#return_codeSymbol

Returns `libcurl` return code.

Returns:

  • (Symbol)

    `libcurl` return code.



46
47
48
# File 'lib/arachni/http/response.rb', line 46

def return_code
  @return_code
end

#return_messageString

Returns `libcurl` return code.

Returns:

  • (String)

    `libcurl` return code.



50
51
52
# File 'lib/arachni/http/response.rb', line 50

def return_message
  @return_message
end

#timeFloat

Returns Time, in seconds, it took from the start until the full response was received.

Returns:

  • (Float)

    Time, in seconds, it took from the start until the full response was received.



64
65
66
# File 'lib/arachni/http/response.rb', line 64

def time
  @time
end

#total_timeFloat

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

Returns:

  • (Float)

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



59
60
61
# File 'lib/arachni/http/response.rb', line 59

def total_time
  @total_time
end

Class Method Details

.from_rpc_data(data) ⇒ Request

Parameters:

Returns:



241
242
243
244
245
# 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 = {}) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# 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

Instance Method Details

#==(other) ⇒ Object



247
248
249
# File 'lib/arachni/http/response.rb', line 247

def ==( other )
    hash == other.hash
end

#body=(body) ⇒ Object



193
194
195
196
197
198
199
200
# 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

#hashObject



251
252
253
# File 'lib/arachni/http/response.rb', line 251

def hash
    to_h.hash
end

#html?Boolean

Returns:

  • (Boolean)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# 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?Boolean

Note:

Depends on the response code.

Returns `true` if the remote resource has been modified since the date given in the `If-Modified-Since` request header field, `false` otherwise.

Returns:

  • (Boolean)

    `true` if the remote resource has been modified since the date given in the `If-Modified-Since` request header field, `false` otherwise.



133
134
135
# File 'lib/arachni/http/response.rb', line 133

def modified?
    code != 304
end

#ok?Boolean

Returns `true` if the request was performed successfully and the response was received in full, `false` otherwise.

Returns:

  • (Boolean)

    `true` if the request was performed successfully and the response was received in full, `false` otherwise.



140
141
142
# File 'lib/arachni/http/response.rb', line 140

def ok?
    !return_code || return_code == :ok
end

#parseObject



207
208
209
# File 'lib/arachni/http/response.rb', line 207

def parse
    Parser.new self
end

#partial?Boolean

Returns `true` if the client could not read the entire response, `false` otherwise.

Returns:

  • (Boolean)

    `true` if the client could not read the entire response, `false` otherwise.



88
89
90
91
92
93
94
95
# 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

#platformsPlatform

Returns Applicable platforms for the page.

Returns:

  • (Platform)

    Applicable platforms for the page.



99
100
101
# File 'lib/arachni/http/response.rb', line 99

def platforms
    Platform::Manager[url]
end

#redirect?Boolean Also known as: redirection?

Returns `true` if the response is a `3xx` redirect *and* there is a `Location` header field.

Returns:

  • (Boolean)

    `true` if the response is a `3xx` redirect *and* there is a `Location` header field.



119
120
121
# File 'lib/arachni/http/response.rb', line 119

def redirect?
    code >= 300 && code <= 399 && !!headers.location
end

#status_lineString

Returns First line of the response.

Returns:

  • (String)

    First line of the response.



105
106
107
108
# 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?Bool

Returns `true` if the response body is textual in nature, `false` if binary, `nil` if could not be determined.

Returns:

  • (Bool)

    `true` if the response body is textual in nature, `false` if binary, `nil` if could not be determined.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# 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

#timed_out?Boolean

Returns `true` if timed out, `false` otherwise.

Returns:

  • (Boolean)

    `true` if timed out, `false` otherwise.



171
172
173
# File 'lib/arachni/http/response.rb', line 171

def timed_out?
    return_code == :operation_timedout
end

#to_hHash

Returns:



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# 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_pageArachni::Page

Returns:



203
204
205
# File 'lib/arachni/http/response.rb', line 203

def to_page
    Page.from_response self
end

#to_rpc_dataHash

Returns Data representing this instance that are suitable the RPC transmission.

Returns:

  • (Hash)

    Data representing this instance that are suitable the RPC transmission.



233
234
235
236
237
# 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_sString

Returns HTTP response string.

Returns:

  • (String)

    HTTP response string.



112
113
114
# File 'lib/arachni/http/response.rb', line 112

def to_s
    "#{headers_string}#{body}"
end

#update_from_typhoeus(response, options = {}) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# 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