Skip to content

Commit

Permalink
Use stateful CookieJar in Redirector
Browse files Browse the repository at this point in the history
As [mentioned](..httprb/issues/264#issuecomment-157070867) in httprb#264, CookieJar
implements cookie domain scoping rules, which is useful when issuing
redirects across domains.

Resolves: httprb#264
  • Loading branch information
Kache committed Jun 20, 2020
1 parent dbc73e3 commit fef377b
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
20 changes: 14 additions & 6 deletions lib/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
def build_request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
opts = @default_options.merge(opts)
uri = make_request_uri(uri, opts)
headers = make_request_headers(opts)
headers = make_request_headers(uri, opts)
body = make_request_body(opts, headers)

req = HTTP::Request.new(
Expand Down Expand Up @@ -147,19 +147,27 @@ def make_request_uri(uri, opts)
end

# Creates request headers with cookies (if any) merged in
def make_request_headers(opts)
def make_request_headers(uri, opts)
headers = opts.headers

# Tell the server to keep the conn open
headers[Headers::CONNECTION] = default_options.persistent? ? Connection::KEEP_ALIVE : Connection::CLOSE

cookies = opts.cookies.values
followjar = opts.follow && opts.follow[:jar]

unless cookies.empty?
cookies = opts.headers.get(Headers::COOKIE).concat(cookies).join("; ")
headers[Headers::COOKIE] = cookies
hash_cookies = opts.cookies.values
header_cookies = opts.headers.get(Headers::COOKIE)
followjar_cookies = followjar ? followjar.each(uri).map(&:cookie_value) : []

sync_to_followjar = followjar ? hash_cookies + header_cookies.join.split("; ") : []
sync_to_followjar.each do |name_val|
name, value = name_val.split("=", 2)
followjar << Cookie.new(name, value, :domain => uri, :secure => uri.https?, :path => "/")
end

cookies = header_cookies + hash_cookies + followjar_cookies
headers[Headers::COOKIE] = cookies.join("; ") unless cookies.empty?

headers
end

Expand Down
10 changes: 8 additions & 2 deletions lib/http/redirector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ class EndlessRedirectError < TooManyRedirectsError; end
# @param [Hash] opts
# @option opts [Boolean] :strict (true) redirector hops policy
# @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
# @option opts [HTTP::CookieJar] :jar (HTTP::CookieJar.new) stateful CookieJar used across hops
def initialize(opts = {}) # rubocop:disable Style/OptionHash
@strict = opts.fetch(:strict, true)
@max_hops = opts.fetch(:max_hops, 5).to_i
@jar = opts.fetch(:jar, CookieJar.new)
end

# Follows redirects until non-redirect response found
Expand All @@ -61,6 +63,7 @@ def perform(request, response)
# XXX(ixti): using `Array#inject` to return `nil` if no Location header.
@request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
@response = yield @request
@response.cookies.inject(@jar, :<<)
end

@response
Expand All @@ -77,7 +80,8 @@ def too_many_hops?
# Check if we got into an endless loop
# @return [Boolean]
def endless_loop?
2 <= @visited.count(@visited.last)
visits = @visited.count(@visited.last)
@visited.last == @visited.first ? visits > 2 : visits > 1 # allow retrying first uri once
end

# Redirect policy for follow
Expand All @@ -94,8 +98,10 @@ def redirect_to(uri)
end

verb = :get if !SEE_OTHER_ALLOWED_VERBS.include?(verb) && 303 == code
redirect_uri = @request.uri.join(uri)
cookies_raw = @jar.each(redirect_uri).map(&:cookie_value).join("; ")

@request.redirect(uri, verb)
@request.redirect(uri, verb, :cookies_raw => cookies_raw)
end
end
end
3 changes: 2 additions & 1 deletion lib/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ def initialize(opts)
end

# Returns new Request with updated uri
def redirect(uri, verb = @verb)
def redirect(uri, verb = @verb, cookies_raw: nil)
headers = self.headers.dup
headers.delete(Headers::HOST)
headers[Headers::COOKIE] = cookies_raw if cookies_raw

self.class.new(
:verb => verb,
Expand Down

0 comments on commit fef377b

Please sign in to comment.