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:

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



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



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

def app_time
  @app_time
end

#codeInteger



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

def code
  @code
end

#headers_stringString



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

def headers_string
  @headers_string
end

#ip_addressString



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

def ip_address
  @ip_address
end

#messageString



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

def message
  @message
end

#redirectionsArray<Response>



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

def redirections
  @redirections
end

#requestRequest



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

def request
  @request
end

#return_codeSymbol



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

def return_code
  @return_code
end

#return_messageString



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

def return_message
  @return_message
end

#timeFloat



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

def time
  @time
end

#total_timeFloat



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



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



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.



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

def modified?
    code != 304
end

#ok?Boolean



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



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



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

def platforms
    Platform::Manager[url]
end

#redirect?Boolean Also known as: redirection?



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

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

#status_lineString



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



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



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

def timed_out?
    return_code == :operation_timedout
end

#to_hHash



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



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

def to_page
    Page.from_response self
end

#to_rpc_dataHash



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



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