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.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/arachni/http/request.rb', line 122

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   = (Arachni::Options.http.request_redirect_limit || REDIRECT_LIMIT)
    @on_complete     = []

    @timeout       ||= Arachni::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

#root_redirect_idObject



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

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:



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/arachni/http/request.rb', line 428

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.



451
452
453
454
455
456
457
458
459
# File 'lib/arachni/http/request.rb', line 451

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



389
390
391
# File 'lib/arachni/http/request.rb', line 389

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.



168
169
170
# File 'lib/arachni/http/request.rb', line 168

def asynchronous?
    mode == :async
end

#blocking?Boolean

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

Returns:

  • (Boolean)

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



174
175
176
# File 'lib/arachni/http/request.rb', line 174

def blocking?
    mode == :sync
end

#body_parametersObject



216
217
218
219
# File 'lib/arachni/http/request.rb', line 216

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

#clear_callbacksObject

Clears #on_complete callbacks.



238
239
240
# File 'lib/arachni/http/request.rb', line 238

def clear_callbacks
    @on_complete.clear
end

#effective_cookiesObject



209
210
211
212
213
214
# File 'lib/arachni/http/request.rb', line 209

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

#follow_location?Bool

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

Returns:

  • (Bool)

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



244
245
246
# File 'lib/arachni/http/request.rb', line 244

def follow_location?
    !!@follow_location
end

#handle_response(response, typhoeus_response = nil) ⇒ Object



285
286
287
288
289
290
291
292
293
# File 'lib/arachni/http/request.rb', line 285

def handle_response( response, typhoeus_response = nil )
    if typhoeus_response
        fill_in_data_from_typhoeus_response typhoeus_response
    end

    response.request = self
    @on_complete.each { |b| b.call response }
    response
end

#hashObject



393
394
395
# File 'lib/arachni/http/request.rb', line 393

def hash
    to_h.hash
end

#high_priority?Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/arachni/http/request.rb', line 139

def high_priority?
    !!@high_priority
end

#marshal_dumpObject



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

def marshal_dump
    callbacks = @on_complete.dup
    performer = @performer ? @performer.dup : nil

    @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.dup if performer
end

#marshal_load(h) ⇒ Object



414
415
416
# File 'lib/arachni/http/request.rb', line 414

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

#method(*args) ⇒ Symbol

Returns HTTP method.

Returns:

  • (Symbol)

    HTTP method.



180
181
182
183
# File 'lib/arachni/http/request.rb', line 180

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.



194
195
196
# File 'lib/arachni/http/request.rb', line 194

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.



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

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

#runResponse

Note:

Will call #on_complete callbacks.

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

Returns:



278
279
280
281
282
283
# File 'lib/arachni/http/request.rb', line 278

def run
    response = to_typhoeus.run
    fill_in_data_from_typhoeus_response response

    Response.from_typhoeus( response ).tap { |r| r.request = self }
end

#to_hObject



377
378
379
380
381
382
383
384
385
386
387
# File 'lib/arachni/http/request.rb', line 377

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.



420
421
422
# File 'lib/arachni/http/request.rb', line 420

def to_rpc_data
    marshal_dump
end

#to_sString

Returns HTTP request string.

Returns:

  • (String)

    HTTP request string.



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

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`.



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
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
# File 'lib/arachni/http/request.rb', line 297

def to_typhoeus
    headers['Cookie'] = effective_cookies.
        map { |k, v| "#{Cookie.encode( k )}=#{Cookie.encode( v )}" }.
        join( ';' )

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

    headers.delete( 'Cookie' ) if headers['Cookie'].empty?
    headers.each { |k, v| headers[k] = Header.encode( v ) if v }

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

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

    options = {
        method:          method,
        headers:         headers,
        body:            body,
        params:          Arachni::Utilities.uri_parse_query( url ).
                             merge( parameters || {} ),
        userpwd:         userpwd,
        followlocation:  follow_location?,
        maxredirs:       @max_redirects,
        ssl_verifypeer:  false,
        ssl_verifyhost:  0,
        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
    options[:httpauth]   = :auto   if userpwd

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

        if proxy_user_password
            options[:proxyuserpwd] = proxy_user_password
        end

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

        if Arachni::Options.http.proxy_username && Arachni::Options.http.proxy_password
            options[:proxyuserpwd] =
                "#{Arachni::Options.http.proxy_username}:#{Arachni::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|
            handle_response Response.from_typhoeus( typhoeus_response ), typhoeus_response
        end
    end

    r
end

#trainObject

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



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

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:



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

def train?
    @train
end

#update_cookiesObject

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



269
270
271
# File 'lib/arachni/http/request.rb', line 269

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:



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

def update_cookies?
    @update_cookies
end