class HTTP::Redirector

Constants

REDIRECT_CODES

HTTP status codes which indicate redirects

SEE_OTHER_ALLOWED_VERBS

Verbs which will remain unchanged upon See Other response.

STRICT_SENSITIVE_CODES

Codes which which should raise StateError in strict mode if original request was any of {UNSAFE_VERBS}

UNSAFE_VERBS

Insecure http verbs, which should trigger StateError in strict mode upon {STRICT_SENSITIVE_CODES}

Attributes

max_hops[R]

@!attribute [r] max_hops

Returns maximum allowed hops.
@return [Fixnum]
strict[R]

@!attribute [r] strict

Returns redirector policy.
@return [Boolean]

Public Class Methods

new(opts = {}) click to toggle source

@param [Hash] opts @option opts [Boolean] :strict (true) redirector hops policy @option opts [#to_i] :max_hops (5) maximum allowed amount of hops

# File lib/http/redirector.rb, line 42
def initialize(opts = {})
  @strict      = opts.fetch(:strict, true)
  @max_hops    = opts.fetch(:max_hops, 5).to_i
  @on_redirect = opts.fetch(:on_redirect, nil)
end

Public Instance Methods

perform(request, response) { |request| ... } click to toggle source

Follows redirects until non-redirect response found

# File lib/http/redirector.rb, line 49
def perform(request, response)
  @request  = request
  @response = response
  @visited  = []
  collect_cookies_from_request
  collect_cookies_from_response

  while REDIRECT_CODES.include? @response.status.code
    @visited << "#{@request.verb} #{@request.uri}"

    raise TooManyRedirectsError if too_many_hops?
    raise EndlessRedirectError  if endless_loop?

    @response.flush

    # XXX(ixti): using `Array#inject` to return `nil` if no Location header.
    @request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
    unless cookie_jar.empty?
      @request.headers.set(Headers::COOKIE, cookie_jar.cookies.map { |c| "#{c.name}=#{c.value}" }.join("; "))
    end
    @on_redirect.call @response, @request if @on_redirect.respond_to?(:call)
    @response = yield @request
    collect_cookies_from_response
  end

  @response
end

Private Instance Methods

collect_cookies_from_request() click to toggle source
# File lib/http/redirector.rb, line 86
def collect_cookies_from_request
  request_cookie_header = @request.headers["Cookie"]
  cookies =
    if request_cookie_header
      HTTP::Cookie.cookie_value_to_hash(request_cookie_header)
    else
      {}
    end

  cookies.each do |key, value|
    cookie_jar.add(HTTP::Cookie.new(key, value, :path => @request.uri.path, :domain => @request.host))
  end
end
collect_cookies_from_response() click to toggle source

Carry cookies from one response to the next. Carrying cookies to the next response ends up carrying them to the next request as well.

Note that this isn’t part of the IETF standard, but all major browsers support setting cookies on redirect: blog.dubbelboer.com/2012/11/25/302-cookie.html

# File lib/http/redirector.rb, line 105
def collect_cookies_from_response
  # Overwrite previous cookies
  @response.cookies.each do |cookie|
    if cookie.value == ""
      cookie_jar.delete(cookie)
    else
      cookie_jar.add(cookie)
    end
  end

  # I wish we could just do @response.cookes = cookie_jar
  cookie_jar.each do |cookie|
    @response.cookies.add(cookie)
  end
end
endless_loop?() click to toggle source

Check if we got into an endless loop @return [Boolean]

# File lib/http/redirector.rb, line 129
def endless_loop?
  2 <= @visited.count(@visited.last)
end
redirect_to(uri) click to toggle source

Redirect policy for follow @return [Request]

# File lib/http/redirector.rb, line 135
def redirect_to(uri)
  raise StateError, "no Location header in redirect" unless uri

  verb = @request.verb
  code = @response.status.code

  if UNSAFE_VERBS.include?(verb) && STRICT_SENSITIVE_CODES.include?(code)
    raise StateError, "can't follow #{@response.status} redirect" if @strict

    verb = :get
  end

  verb = :get if !SEE_OTHER_ALLOWED_VERBS.include?(verb) && 303 == code

  @request.redirect(uri, verb)
end
too_many_hops?() click to toggle source

Check if we reached max amount of redirect hops @return [Boolean]

# File lib/http/redirector.rb, line 123
def too_many_hops?
  1 <= @max_hops && @max_hops < @visited.count
end