Class: Arachni::HTTP::Request

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

Overview

HTTP Request representation.

Author:

Defined Under Namespace

Classes: Scope

Constant Summary collapse

REDIRECT_LIMIT =

Default redirect limit, RFC says 5 max.

5
MODES =

Supported modes of operation.

[
    # Asynchronous (non-blocking) (Default)
    :async,

    # Synchronous (blocking)
    :sync
]

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

Constructor Details

#initialize(options = {}) ⇒ Request

Returns a new instance of Request.

Parameters:

  • options (Hash) (defaults to: {})

    Request options.

Options Hash (options):

  • :url (String)

    URL.

  • :parameters (Hash) — default: {}

    Request parameters.

  • :body (String) — default: {}

    Request body.

  • :train (Bool) — default: false

    Force Arachni to analyze the response looking for new elements.

  • :mode (Symbol) — default: :async

    Mode in which to perform the request:

    • ‘:async` – Asynchronous (non-blocking) (Default).

    • ‘:sync` – Synchronous (blocking).

  • :headers (Hash) — default: {}

    Extra HTTP request headers.

  • :cookies (Hash) — default: {}

    Cookies for the request.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/arachni/http/request.rb', line 126

def initialize( options = {} )
    options[:method] ||= :get

    super( options )

    @train           = false if @train.nil?
    @update_cookies  = false if @update_cookies.nil?
    @follow_location = false if @follow_location.nil?
    @max_redirects   = (Options.http.request_redirect_limit || REDIRECT_LIMIT)
    @on_complete     = []

    @timeout       ||= Options.http.request_timeout
    @mode          ||= :async
    @parameters    ||= {}
    @cookies       ||= {}
end

Instance Attribute Details

#cookiesHash

Returns Cookies set for this request.

Returns:

  • (Hash)

    Cookies set for this request.



62
63
64
# File 'lib/arachni/http/request.rb', line 62

def cookies
  @cookies
end

#effective_bodyString

Note:

Available only via completed Arachni::HTTP::Response#request.

Returns Transmitted HTTP request body.

Returns:

  • (String)

    Transmitted HTTP request body.



80
81
82
# File 'lib/arachni/http/request.rb', line 80

def effective_body
  @effective_body
end

#follow_locationBool

Returns Follow ‘Location` headers.

Returns:

  • (Bool)

    Follow ‘Location` headers.



44
45
46
# File 'lib/arachni/http/request.rb', line 44

def follow_location
  @follow_location
end

#headers_stringString

Note:

Available only via completed Arachni::HTTP::Response#request.

Returns Transmitted HTTP request headers.

Returns:

  • (String)

    Transmitted HTTP request headers.



74
75
76
# File 'lib/arachni/http/request.rb', line 74

def headers_string
  @headers_string
end

#high_priorityBool

Returns:

  • (Bool)


98
99
100
# File 'lib/arachni/http/request.rb', line 98

def high_priority
  @high_priority
end

#idInteger

Returns Auto-incremented ID for this request (set by Client#request).

Returns:

  • (Integer)

    Auto-incremented ID for this request (set by Client#request).



32
33
34
# File 'lib/arachni/http/request.rb', line 32

def id
  @id
end

#max_redirectsInteger

Returns Maximum number of redirects to follow.

Returns:

  • (Integer)

    Maximum number of redirects to follow.

See Also:



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

def max_redirects
  @max_redirects
end

#modeSymbol

Returns Mode of operation for the request.

Returns:

  • (Symbol)

    Mode of operation for the request.

See Also:



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

def mode
  @mode
end

#parametersHash

Returns Request parameters.

Returns:

  • (Hash)

    Request parameters.



36
37
38
# File 'lib/arachni/http/request.rb', line 36

def parameters
  @parameters
end

#passwordString

Returns HTTP password.

Returns:



58
59
60
# File 'lib/arachni/http/request.rb', line 58

def password
  @password
end

#performerObject

Entity which performed the request – mostly used to track which response was a result of which submitted element.



84
85
86
# File 'lib/arachni/http/request.rb', line 84

def performer
  @performer
end

#proxyString

Returns ‘host:port`.

Returns:



88
89
90
# File 'lib/arachni/http/request.rb', line 88

def proxy
  @proxy
end

#proxy_typeString

Returns:



95
96
97
# File 'lib/arachni/http/request.rb', line 95

def proxy_type
  @proxy_type
end

#proxy_user_passwordString

Returns ‘user:password`.

Returns:

  • (String)

    ‘user:password`



92
93
94
# File 'lib/arachni/http/request.rb', line 92

def proxy_user_password
  @proxy_user_password
end

#response_max_sizeInteger

Returns Maximum HTTP response size to accept, in bytes.

Returns:

  • (Integer)

    Maximum HTTP response size to accept, in bytes.



102
103
104
# File 'lib/arachni/http/request.rb', line 102

def response_max_size
  @response_max_size
end

#root_redirect_idObject



105
106
107
# File 'lib/arachni/http/request.rb', line 105

def root_redirect_id
  @root_redirect_id
end

#timeoutInteger

Returns Timeout in milliseconds.

Returns:

  • (Integer)

    Timeout in milliseconds.



40
41
42
# File 'lib/arachni/http/request.rb', line 40

def timeout
  @timeout
end

#usernameString

Returns HTTP username.

Returns:



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

def username
  @username
end

Class Method Details

.from_rpc_data(data) ⇒ Request

Parameters:

Returns:



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/arachni/http/request.rb', line 451

def from_rpc_data( data )
    instance = allocate
    data.each do |name, value|

        value = case name
                    when 'method', 'mode'
                        value.to_sym

                    else
                        value
                end

        instance.instance_variable_set( "@#{name}", value )
    end
    instance
end

.parse_body(body) ⇒ Hash

Parses an HTTP request body generated by submitting a form.

Parameters:

Returns:

  • (Hash)

    Parameters.



474
475
476
477
478
479
480
481
482
# File 'lib/arachni/http/request.rb', line 474

def parse_body( body )
    return {} if !body

    body.to_s.split( '&' ).inject( {} ) do |h, pair|
        name, value = pair.split( '=', 2 )
        h[Form.decode( name.to_s )] = Form.decode( value )
        h
    end
end

Instance Method Details

#==(other) ⇒ Object



412
413
414
# File 'lib/arachni/http/request.rb', line 412

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

#asynchronous?Boolean

Returns ‘true` if #mode is `:async`, `false` otherwise.

Returns:

  • (Boolean)

    ‘true` if #mode is `:async`, `false` otherwise.



172
173
174
# File 'lib/arachni/http/request.rb', line 172

def asynchronous?
    mode == :async
end

#blocking?Boolean

Returns ‘true` if #mode is `:sync`, `false` otherwise.

Returns:

  • (Boolean)

    ‘true` if #mode is `:sync`, `false` otherwise.



178
179
180
# File 'lib/arachni/http/request.rb', line 178

def blocking?
    mode == :sync
end

#body_parametersObject



224
225
226
227
# File 'lib/arachni/http/request.rb', line 224

def body_parameters
    return {} if method != :post
    parameters.any? ? parameters : self.class.parse_body( body )
end

#clear_callbacksObject

Clears #on_complete callbacks.



258
259
260
# File 'lib/arachni/http/request.rb', line 258

def clear_callbacks
    @on_complete.clear
end

#effective_cookiesObject



213
214
215
216
217
218
# File 'lib/arachni/http/request.rb', line 213

def effective_cookies
    Cookie.from_string( url, headers['Cookie'] || '' ).inject({}) do |h, cookie|
        h[cookie.name] = cookie.value
        h
    end.merge( cookies )
end

#effective_parametersObject



220
221
222
# File 'lib/arachni/http/request.rb', line 220

def effective_parameters
    Utilities.uri_parse_query( url ).merge( parameters || {} )
end

#follow_location?Bool

Returns ‘true` if redirects should be followed, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if redirects should be followed, `false` otherwise.



264
265
266
# File 'lib/arachni/http/request.rb', line 264

def follow_location?
    !!@follow_location
end

#handle_response(response) ⇒ Object



302
303
304
305
306
# File 'lib/arachni/http/request.rb', line 302

def handle_response( response )
    response.request = self
    @on_complete.each { |b| b.call response }
    response
end

#hashObject



416
417
418
# File 'lib/arachni/http/request.rb', line 416

def hash
    to_h.hash
end

#high_priority?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/arachni/http/request.rb', line 143

def high_priority?
    !!@high_priority
end

#inspectObject



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/arachni/http/request.rb', line 235

def inspect
    s = "#<#{self.class} "
    s << "@id=#{id} "
    s << "@mode=#{mode} "
    s << "@method=#{method} "
    s << "@url=#{url.inspect} "
    s << "@parameters=#{parameters.inspect} "
    s << "@high_priority=#{high_priority} "
    s << "@performer=#{performer.inspect}"
    s << '>'
end

#marshal_dumpObject



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/arachni/http/request.rb', line 420

def marshal_dump
    callbacks = @on_complete.dup
    performer = @performer

    @performer   = nil
    @on_complete = []

    instance_variables.inject( {} ) do |h, iv|
        next h if iv == :@scope
        h[iv.to_s.gsub('@','')] = instance_variable_get( iv )
        h
    end
ensure
    @on_complete = callbacks
    @performer   = performer
end

#marshal_load(h) ⇒ Object



437
438
439
# File 'lib/arachni/http/request.rb', line 437

def marshal_load( h )
    h.each { |k, v| instance_variable_set( "@#{k}", v ) }
end

#method(*args) ⇒ Symbol

Returns HTTP method.

Returns:

  • (Symbol)

    HTTP method.



184
185
186
187
# File 'lib/arachni/http/request.rb', line 184

def method( *args )
    return super( *args ) if args.any? # Preserve Object#method.
    @method
end

#method=(verb) ⇒ Symbol

Note:

Method will be normalized to a lower-case symbol.

Sets the request HTTP method.

Parameters:

  • verb (#to_s)

    HTTP method.

Returns:

  • (Symbol)

    HTTP method.



198
199
200
# File 'lib/arachni/http/request.rb', line 198

def method=( verb )
    @method = verb.to_s.downcase.to_sym
end

#on_complete(&block) ⇒ Object

Note:

Can be invoked multiple times.

Parameters:

  • block (Block)

    Callback to be passed the response.



251
252
253
254
255
# File 'lib/arachni/http/request.rb', line 251

def on_complete( &block )
    fail 'Block is missing.' if !block_given?
    @on_complete << block
    self
end

#prepare_headersObject



485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/arachni/http/request.rb', line 485

def prepare_headers
    headers['Cookie'] = effective_cookies.
        map { |k, v| "#{Cookie.encode( k )}=#{Cookie.encode( v )}" }.
        join( ';' )
    headers.delete( 'Cookie' ) if headers['Cookie'].empty?

    headers['User-Agent'] ||= Options.http.user_agent
    headers['Accept']     ||= 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    headers['From']       ||= Options.authorized_by if Options.authorized_by

    headers.each { |k, v| headers[k] = Header.encode( v ) if v }
end

#runResponse

Note:

Will call #on_complete callbacks.

Performs the Arachni::HTTP::Request without going through Client.

Returns:



298
299
300
# File 'lib/arachni/http/request.rb', line 298

def run
    client_run.tap { |r| r.request = self }
end

#to_hObject



400
401
402
403
404
405
406
407
408
409
410
# File 'lib/arachni/http/request.rb', line 400

def to_h
    {
        url:            url,
        parameters:     parameters,
        headers:        headers,
        headers_string: headers_string,
        effective_body: effective_body,
        body:           body,
        method:         method
    }
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.



443
444
445
# File 'lib/arachni/http/request.rb', line 443

def to_rpc_data
    marshal_dump
end

#to_sString

Returns HTTP request string.

Returns:

  • (String)

    HTTP request string.



231
232
233
# File 'lib/arachni/http/request.rb', line 231

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

#to_typhoeusTyphoeus::Response

Returns ‘self` converted to a `Typhoeus::Request`.

Returns:

  • (Typhoeus::Response)

    ‘self` converted to a `Typhoeus::Request`.



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/arachni/http/request.rb', line 310

def to_typhoeus
    prepare_headers

    if (userpwd = (@username || Options.http.authentication_username))
        if (passwd = (@password || Options.http.authentication_password))
            userpwd += ":#{passwd}"
        end
    end

    max_size = @response_max_size || Options.http.response_max_size
    # Weird I know, for some reason 0 gets ignored.
    max_size = 1   if max_size == 0
    max_size = nil if max_size < 0

    options = {
        method:          method,
        headers:         headers,
        body:            body,
        params:          effective_parameters,
        userpwd:         userpwd,
        followlocation:  follow_location?,
        maxredirs:       @max_redirects,

        ssl_verifypeer:  !!Options.http.ssl_verify_peer,
        ssl_verifyhost:  Options.http.ssl_verify_host ? 2 : 0,
        sslcert:         Options.http.ssl_certificate_filepath,
        sslcerttype:     Options.http.ssl_certificate_type,
        sslkey:          Options.http.ssl_key_filepath,
        sslkeytype:      Options.http.ssl_key_type,
        sslkeypasswd:    Options.http.ssl_key_password,
        cainfo:          Options.http.ssl_ca_filepath,
        capath:          Options.http.ssl_ca_directory,
        sslversion:      Options.http.ssl_version,

        accept_encoding: 'gzip, deflate',
        nosignal:        true,
        maxfilesize:     max_size,

        # Don't keep the socket alive if this is a blocking request because
        # it's going to be performed by an one-off Hydra.
        forbid_reuse:    blocking?,
        verbose:         true
    }

    options[:timeout_ms] = timeout if timeout

    # This will allow GSS-Negotiate to work out of the box but shouldn't
    # have any adverse effects.
    if !options[:userpwd] && !parsed_url.user
        options[:userpwd]  = ':'
        options[:httpauth] = :gssnegotiate
    else
        options[:httpauth] = :auto
    end

    if proxy
        options.merge!(
            proxy:     proxy,
            proxytype: (proxy_type || :http).to_sym
        )

        if proxy_user_password
            options[:proxyuserpwd] = proxy_user_password
        end

    elsif Options.http.proxy_host && Options.http.proxy_port
        options.merge!(
            proxy:     "#{Options.http.proxy_host}:#{Options.http.proxy_port}",
            proxytype: (Options.http.proxy_type || :http).to_sym
        )

        if Options.http.proxy_username && Options.http.proxy_password
            options[:proxyuserpwd] =
                "#{Options.http.proxy_username}:#{Options.http.proxy_password}"
        end
    end

    curl = parsed_url.query ? url.gsub( "?#{parsed_url.query}", '' ) : url
    r = Typhoeus::Request.new( curl, options )

    if @on_complete.any?
        r.on_complete do |typhoeus_response|
            fill_in_data_from_typhoeus_response typhoeus_response
            handle_response Response.from_typhoeus( typhoeus_response )
        end
    end

    r
end

#trainObject

Flags that the response should be analyzed by the Trainer for new elements.



277
278
279
# File 'lib/arachni/http/request.rb', line 277

def train
    @train = true
end

#train?Bool

Returns ‘true` if the Arachni::HTTP::Response should be analyzed by the Trainer for new elements, `false` otherwise.

Returns:



271
272
273
# File 'lib/arachni/http/request.rb', line 271

def train?
    @train
end

#update_cookiesObject

Flags that the CookieJar should be updated with the Arachni::HTTP::Response cookies.



289
290
291
# File 'lib/arachni/http/request.rb', line 289

def update_cookies
    @update_cookies = true
end

#update_cookies?Bool

Returns ‘true` if the CookieJar should be updated with the Arachni::HTTP::Response cookies, `false` otherwise.

Returns:



284
285
286
# File 'lib/arachni/http/request.rb', line 284

def update_cookies?
    @update_cookies
end