require'faraday'require'set'# First saw on octokit, then copied from lostisland/faraday_middleware# and adapted for this library.## faraday_middleware/lib/faraday_middleware/response/follow_redirects.rbmoduleGithub# Public: Exception thrown when the maximum amount of requests is exceeded.classRedirectLimitReached<Faraday::ClientErrorattr_reader:responsedefinitialize(response)super"too many redirects; last one to: #{response['location']}"@response=responseendend# Public: Follow HTTP 301, 302, 303, 307, and 308 redirects.## For HTTP 301, 302, and 303, the original GET, POST, PUT, DELETE, or PATCH# request gets converted into a GET. With `:standards_compliant => true`,# however, the HTTP method after 301/302 remains unchanged. This allows you# to opt into HTTP/1.1 compliance and act unlike the major web browsers.## This middleware currently only works with synchronous requests; i.e. it# doesn't support parallelism.## If you wish to persist cookies across redirects, you could use# the faraday-cookie_jar gem:## Faraday.new(:url => url) do |faraday|# faraday.use FaradayMiddleware::FollowRedirects# faraday.use :cookie_jar# faraday.adapter Faraday.default_adapter# endclassResponse::FollowRedirects<Faraday::Middleware# HTTP methods for which 30x redirects can be followedALLOWED_METHODS=Set.new[:head,:options,:get,:post,:put,:patch,:delete]# HTTP redirect status codes that this middleware implementsREDIRECT_CODES=Set.new[301,302,303,307,308]# Keys in env hash which will get cleared between requestsENV_TO_CLEAR=Set.new[:status,:response,:response_headers]# Default value for max redirects followedFOLLOW_LIMIT=3# Regex that matches characters that need to be escaped in URLs, sans# the "%" character which we assume already represents an escaped sequence.URI_UNSAFE=/[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]/# Public: Initialize the middleware.## options - An options Hash (default: {}):# :limit - A Numeric redirect limit (default: 3)# :standards_compliant - A Boolean indicating whether to respect# the HTTP spec when following 301/302# (default: false)# :callback - A callable that will be called on redirects# with the old and new envsdefinitialize(app,options={})super(app)@options=options@convert_to_get=Set.new[303]@convert_to_get<<301<<302unlessstandards_compliant?enddefcall(env)perform_with_redirection(env,follow_limit)endprivatedefconvert_to_get?(response)![:head,:options].include?(response.env[:method])&&@convert_to_get.include?(response.status)enddefperform_with_redirection(env,follows)request_body=env[:body]response=@app.call(env)response.on_completedo|response_env|iffollow_redirect?(response_env,response)raiseRedirectLimitReached,responseiffollows.zero?new_request_env=update_env(response_env.dup,request_body,response)callback.call(response_env,new_request_env)ifcallbackresponse=perform_with_redirection(new_request_env,follows-1)endendresponseenddefupdate_env(env,request_body,response)env[:url]+=safe_escape(response['location'])ifconvert_to_get?(response)env[:method]=:getenv[:body]=nilelseenv[:body]=request_bodyendENV_TO_CLEAR.each{|key|env.deletekey}envenddeffollow_redirect?(env,response)ALLOWED_METHODS.include?env[:method]andREDIRECT_CODES.include?response.statusenddeffollow_limit@options.fetch(:limit,FOLLOW_LIMIT)enddefstandards_compliant?@options.fetch(:standards_compliant,false)enddefcallback@options[:callback]end# Internal: escapes unsafe characters from an URL which might be a path# component only or a fully qualified URI so that it can be joined onto an# URI:HTTP using the `+` operator. Doesn't escape "%" characters so to not# risk double-escaping.defsafe_escape(uri)uri=uri.split('#')[0]# we want to remove the fragment if presenturi.to_s.gsub(URI_UNSAFE){|match|'%'+match.unpack('H2'*match.bytesize).join('%').upcase}endendend