faraday_middleware_safeyaml 0.12.pre.safeyaml
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +20 -0
- data/README.md +58 -0
- data/lib/faraday_middleware.rb +48 -0
- data/lib/faraday_middleware/addressable_patch.rb +20 -0
- data/lib/faraday_middleware/backwards_compatibility.rb +15 -0
- data/lib/faraday_middleware/gzip.rb +64 -0
- data/lib/faraday_middleware/instrumentation.rb +30 -0
- data/lib/faraday_middleware/rack_compatible.rb +86 -0
- data/lib/faraday_middleware/request/encode_json.rb +53 -0
- data/lib/faraday_middleware/request/method_override.rb +51 -0
- data/lib/faraday_middleware/request/oauth.rb +87 -0
- data/lib/faraday_middleware/request/oauth2.rb +85 -0
- data/lib/faraday_middleware/response/caching.rb +102 -0
- data/lib/faraday_middleware/response/chunked.rb +29 -0
- data/lib/faraday_middleware/response/follow_redirects.rb +134 -0
- data/lib/faraday_middleware/response/mashify.rb +37 -0
- data/lib/faraday_middleware/response/parse_dates.rb +39 -0
- data/lib/faraday_middleware/response/parse_json.rb +50 -0
- data/lib/faraday_middleware/response/parse_marshal.rb +13 -0
- data/lib/faraday_middleware/response/parse_xml.rb +15 -0
- data/lib/faraday_middleware/response/parse_yaml.rb +38 -0
- data/lib/faraday_middleware/response/rashify.rb +15 -0
- data/lib/faraday_middleware/response_middleware.rb +112 -0
- data/lib/faraday_middleware/version.rb +3 -0
- metadata +105 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: Uses the simple_oauth library to sign requests according the
|
6
|
+
# OAuth protocol.
|
7
|
+
#
|
8
|
+
# The options for this middleware are forwarded to SimpleOAuth::Header:
|
9
|
+
# :consumer_key, :consumer_secret, :token, :token_secret. All these
|
10
|
+
# parameters are optional.
|
11
|
+
#
|
12
|
+
# The signature is added to the "Authorization" HTTP request header. If the
|
13
|
+
# value for this header already exists, it is not overriden.
|
14
|
+
#
|
15
|
+
# If no Content-Type header is specified, this middleware assumes that
|
16
|
+
# request body parameters should be included while signing the request.
|
17
|
+
# Otherwise, it only includes them if the Content-Type is
|
18
|
+
# "application/x-www-form-urlencoded", as per OAuth 1.0.
|
19
|
+
#
|
20
|
+
# For better performance while signing requests, this middleware should be
|
21
|
+
# positioned before UrlEncoded middleware on the stack, but after any other
|
22
|
+
# body-encoding middleware (such as EncodeJson).
|
23
|
+
class OAuth < Faraday::Middleware
|
24
|
+
dependency 'simple_oauth'
|
25
|
+
|
26
|
+
AUTH_HEADER = 'Authorization'.freeze
|
27
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
28
|
+
TYPE_URLENCODED = 'application/x-www-form-urlencoded'.freeze
|
29
|
+
|
30
|
+
extend Forwardable
|
31
|
+
parser_method = :parse_nested_query
|
32
|
+
parser_module = ::Faraday::Utils.respond_to?(parser_method) ? 'Faraday::Utils' : 'Rack::Utils'
|
33
|
+
def_delegator parser_module, parser_method
|
34
|
+
|
35
|
+
def initialize(app, options)
|
36
|
+
super(app)
|
37
|
+
@options = options
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
env[:request_headers][AUTH_HEADER] ||= oauth_header(env).to_s if sign_request?(env)
|
42
|
+
@app.call(env)
|
43
|
+
end
|
44
|
+
|
45
|
+
def oauth_header(env)
|
46
|
+
SimpleOAuth::Header.new env[:method],
|
47
|
+
env[:url].to_s,
|
48
|
+
signature_params(body_params(env)),
|
49
|
+
oauth_options(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sign_request?(env)
|
53
|
+
!!env[:request].fetch(:oauth, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def oauth_options(env)
|
57
|
+
if extra = env[:request][:oauth] and extra.is_a? Hash and !extra.empty?
|
58
|
+
@options.merge extra
|
59
|
+
else
|
60
|
+
@options
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def body_params(env)
|
65
|
+
if include_body_params?(env)
|
66
|
+
if env[:body].respond_to?(:to_str)
|
67
|
+
parse_nested_query env[:body]
|
68
|
+
else
|
69
|
+
env[:body]
|
70
|
+
end
|
71
|
+
end || {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def include_body_params?(env)
|
75
|
+
# see RFC 5849, section 3.4.1.3.1 for details
|
76
|
+
!(type = env[:request_headers][CONTENT_TYPE]) or type == TYPE_URLENCODED
|
77
|
+
end
|
78
|
+
|
79
|
+
def signature_params(params)
|
80
|
+
params.empty? ? params :
|
81
|
+
params.reject {|k,v| v.respond_to?(:content_type) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# deprecated alias
|
87
|
+
Faraday::Request::OAuth = FaradayMiddleware::OAuth
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: A simple middleware that adds an access token to each request.
|
6
|
+
#
|
7
|
+
# By default, the token is added as both "access_token" query parameter and the
|
8
|
+
# "Authorization" HTTP request header. It can alternatively be added exclusively
|
9
|
+
# as a bearer token "Authorization" header by specifying a "token_type" option
|
10
|
+
# of "bearer". However, an explicit "access_token" parameter or "Authorization"
|
11
|
+
# header for the current request are not overriden.
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# # configure default token:
|
16
|
+
# OAuth2.new(app, 'abc123')
|
17
|
+
#
|
18
|
+
# # configure query parameter name:
|
19
|
+
# OAuth2.new(app, 'abc123', :param_name => 'my_oauth_token')
|
20
|
+
#
|
21
|
+
# # use bearer token authorization header only
|
22
|
+
# OAuth2.new(app, 'abc123', :token_type => 'bearer')
|
23
|
+
#
|
24
|
+
# # default token value is optional:
|
25
|
+
# OAuth2.new(app, :param_name => 'my_oauth_token')
|
26
|
+
class OAuth2 < Faraday::Middleware
|
27
|
+
|
28
|
+
PARAM_NAME = 'access_token'.freeze
|
29
|
+
TOKEN_TYPE = 'param'.freeze
|
30
|
+
AUTH_HEADER = 'Authorization'.freeze
|
31
|
+
|
32
|
+
attr_reader :param_name, :token_type
|
33
|
+
|
34
|
+
extend Forwardable
|
35
|
+
def_delegators :'Faraday::Utils', :parse_query, :build_query
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
params = { param_name => @token }.update query_params(env[:url])
|
39
|
+
token = params[param_name]
|
40
|
+
|
41
|
+
if token.respond_to?(:empty?) && !token.empty?
|
42
|
+
case @token_type.downcase
|
43
|
+
when 'param'
|
44
|
+
env[:url].query = build_query params
|
45
|
+
env[:request_headers][AUTH_HEADER] ||= %(Token token="#{token}")
|
46
|
+
when 'bearer'
|
47
|
+
env[:request_headers][AUTH_HEADER] ||= %(Bearer #{token})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@app.call env
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(app, token = nil, options = {})
|
55
|
+
super(app)
|
56
|
+
options, token = token, nil if token.is_a? Hash
|
57
|
+
@token = token && token.to_s
|
58
|
+
@param_name = options.fetch(:param_name, PARAM_NAME).to_s
|
59
|
+
@token_type = options.fetch(:token_type, TOKEN_TYPE).to_s
|
60
|
+
|
61
|
+
if @token_type == 'param' && @param_name.empty?
|
62
|
+
raise ArgumentError, ":param_name can't be blank"
|
63
|
+
end
|
64
|
+
|
65
|
+
if options[:token_type].nil?
|
66
|
+
warn "\nWarning: FaradayMiddleware::OAuth2 initialized with default "\
|
67
|
+
"token_type - token will be added as both a query string parameter "\
|
68
|
+
"and an Authorization header. In the next major release, tokens will "\
|
69
|
+
"be added exclusively as an Authorization header by default. Please "\
|
70
|
+
"visit https://github.com/lostisland/faraday_middleware/wiki for more information."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def query_params(url)
|
75
|
+
if url.query.nil? or url.query.empty?
|
76
|
+
{}
|
77
|
+
else
|
78
|
+
parse_query url.query
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# deprecated alias
|
85
|
+
Faraday::Request::OAuth2 = FaradayMiddleware::OAuth2
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
# fixes normalizing query strings:
|
4
|
+
require 'faraday_middleware/addressable_patch' if defined? ::Addressable::URI
|
5
|
+
|
6
|
+
module FaradayMiddleware
|
7
|
+
# Public: Caches GET responses and pulls subsequent ones from the cache.
|
8
|
+
class Caching < Faraday::Middleware
|
9
|
+
attr_reader :cache
|
10
|
+
# Internal: List of status codes that can be cached:
|
11
|
+
# * 200 - 'OK'
|
12
|
+
# * 203 - 'Non-Authoritative Information'
|
13
|
+
# * 300 - 'Multiple Choices'
|
14
|
+
# * 301 - 'Moved Permanently'
|
15
|
+
# * 302 - 'Found'
|
16
|
+
# * 404 - 'Not Found'
|
17
|
+
# * 410 - 'Gone'
|
18
|
+
CACHEABLE_STATUS_CODES = [200, 203, 300, 301, 302, 404, 410]
|
19
|
+
|
20
|
+
extend Forwardable
|
21
|
+
def_delegators :'Faraday::Utils', :parse_query, :build_query
|
22
|
+
|
23
|
+
# Public: initialize the middleware.
|
24
|
+
#
|
25
|
+
# cache - An object that responds to read, write and fetch (default: nil).
|
26
|
+
# options - An options Hash (default: {}):
|
27
|
+
# :ignore_params - String name or Array names of query params
|
28
|
+
# that should be ignored when forming the cache
|
29
|
+
# key (default: []).
|
30
|
+
#
|
31
|
+
# Yields if no cache is given. The block should return a cache object.
|
32
|
+
def initialize(app, cache = nil, options = {})
|
33
|
+
super(app)
|
34
|
+
options, cache = cache, nil if cache.is_a? Hash and block_given?
|
35
|
+
@cache = cache || yield
|
36
|
+
@options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
if :get == env[:method]
|
41
|
+
if env[:parallel_manager]
|
42
|
+
# callback mode
|
43
|
+
cache_on_complete(env)
|
44
|
+
else
|
45
|
+
# synchronous mode
|
46
|
+
key = cache_key(env)
|
47
|
+
unless response = cache.read(key) and response
|
48
|
+
response = @app.call(env)
|
49
|
+
|
50
|
+
if CACHEABLE_STATUS_CODES.include?(response.status)
|
51
|
+
cache.write(key, response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
finalize_response(response, env)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
@app.call(env)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def cache_key(env)
|
62
|
+
url = env[:url].dup
|
63
|
+
if url.query && params_to_ignore.any?
|
64
|
+
params = parse_query url.query
|
65
|
+
params.reject! {|k,| params_to_ignore.include? k }
|
66
|
+
url.query = params.any? ? build_query(params) : nil
|
67
|
+
end
|
68
|
+
url.normalize!
|
69
|
+
url.request_uri
|
70
|
+
end
|
71
|
+
|
72
|
+
def params_to_ignore
|
73
|
+
@params_to_ignore ||= Array(@options[:ignore_params]).map { |p| p.to_s }
|
74
|
+
end
|
75
|
+
|
76
|
+
def cache_on_complete(env)
|
77
|
+
key = cache_key(env)
|
78
|
+
if cached_response = cache.read(key)
|
79
|
+
finalize_response(cached_response, env)
|
80
|
+
else
|
81
|
+
# response.status is nil at this point, any checks need to be done inside on_complete block
|
82
|
+
@app.call(env).on_complete do |response_env|
|
83
|
+
if CACHEABLE_STATUS_CODES.include?(response_env.status)
|
84
|
+
cache.write(key, response_env.response)
|
85
|
+
end
|
86
|
+
response_env
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def finalize_response(response, env)
|
92
|
+
response = response.dup if response.frozen?
|
93
|
+
env[:response] = response
|
94
|
+
unless env[:response_headers]
|
95
|
+
env.update response.env
|
96
|
+
# FIXME: omg hax
|
97
|
+
response.instance_variable_set('@env', env)
|
98
|
+
end
|
99
|
+
response
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'faraday_middleware/response_middleware'
|
2
|
+
|
3
|
+
module FaradayMiddleware
|
4
|
+
# Public: Parse a Transfer-Encoding: Chunked response to just the original data
|
5
|
+
class Chunked < FaradayMiddleware::ResponseMiddleware
|
6
|
+
TRANSFER_ENCODING = 'transfer-encoding'.freeze
|
7
|
+
|
8
|
+
define_parser do |raw_body|
|
9
|
+
decoded_body = []
|
10
|
+
until raw_body.empty?
|
11
|
+
chunk_len, raw_body = raw_body.split("\r\n", 2)
|
12
|
+
chunk_len = chunk_len.split(';',2).first.hex
|
13
|
+
break if chunk_len == 0
|
14
|
+
decoded_body << raw_body[0, chunk_len]
|
15
|
+
# The 2 is to strip the extra CRLF at the end of the chunk
|
16
|
+
raw_body = raw_body[chunk_len + 2, raw_body.length - chunk_len - 2]
|
17
|
+
end
|
18
|
+
decoded_body.join('')
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_response?(env)
|
22
|
+
super and chunked_encoding?(env[:response_headers])
|
23
|
+
end
|
24
|
+
|
25
|
+
def chunked_encoding?(headers)
|
26
|
+
encoding = headers[TRANSFER_ENCODING] and encoding.split(',').include?('chunked')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: Exception thrown when the maximum amount of requests is exceeded.
|
6
|
+
class RedirectLimitReached < Faraday::Error::ClientError
|
7
|
+
attr_reader :response
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
super "too many redirects; last one to: #{response['location']}"
|
11
|
+
@response = response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Follow HTTP 301, 302, 303, 307, and 308 redirects.
|
16
|
+
#
|
17
|
+
# For HTTP 301, 302, and 303, the original GET, POST, PUT, DELETE, or PATCH
|
18
|
+
# request gets converted into a GET. With `:standards_compliant => true`,
|
19
|
+
# however, the HTTP method after 301/302 remains unchanged. This allows you
|
20
|
+
# to opt into HTTP/1.1 compliance and act unlike the major web browsers.
|
21
|
+
#
|
22
|
+
# This middleware currently only works with synchronous requests; i.e. it
|
23
|
+
# doesn't support parallelism.
|
24
|
+
#
|
25
|
+
# If you wish to persist cookies across redirects, you could use
|
26
|
+
# the faraday-cookie_jar gem:
|
27
|
+
#
|
28
|
+
# Faraday.new(:url => url) do |faraday|
|
29
|
+
# faraday.use FaradayMiddleware::FollowRedirects
|
30
|
+
# faraday.use :cookie_jar
|
31
|
+
# faraday.adapter Faraday.default_adapter
|
32
|
+
# end
|
33
|
+
class FollowRedirects < Faraday::Middleware
|
34
|
+
# HTTP methods for which 30x redirects can be followed
|
35
|
+
ALLOWED_METHODS = Set.new [:head, :options, :get, :post, :put, :patch, :delete]
|
36
|
+
# HTTP redirect status codes that this middleware implements
|
37
|
+
REDIRECT_CODES = Set.new [301, 302, 303, 307, 308]
|
38
|
+
# Keys in env hash which will get cleared between requests
|
39
|
+
ENV_TO_CLEAR = Set.new [:status, :response, :response_headers]
|
40
|
+
|
41
|
+
# Default value for max redirects followed
|
42
|
+
FOLLOW_LIMIT = 3
|
43
|
+
|
44
|
+
# Regex that matches characters that need to be escaped in URLs, sans
|
45
|
+
# the "%" character which we assume already represents an escaped sequence.
|
46
|
+
URI_UNSAFE = /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]/
|
47
|
+
|
48
|
+
# Public: Initialize the middleware.
|
49
|
+
#
|
50
|
+
# options - An options Hash (default: {}):
|
51
|
+
# :limit - A Numeric redirect limit (default: 3)
|
52
|
+
# :standards_compliant - A Boolean indicating whether to respect
|
53
|
+
# the HTTP spec when following 301/302
|
54
|
+
# (default: false)
|
55
|
+
# :callback - A callable that will be called on redirects
|
56
|
+
# with the old and new envs
|
57
|
+
def initialize(app, options = {})
|
58
|
+
super(app)
|
59
|
+
@options = options
|
60
|
+
|
61
|
+
@convert_to_get = Set.new [303]
|
62
|
+
@convert_to_get << 301 << 302 unless standards_compliant?
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(env)
|
66
|
+
perform_with_redirection(env, follow_limit)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def convert_to_get?(response)
|
72
|
+
![:head, :options].include?(response.env[:method]) &&
|
73
|
+
@convert_to_get.include?(response.status)
|
74
|
+
end
|
75
|
+
|
76
|
+
def perform_with_redirection(env, follows)
|
77
|
+
request_body = env[:body]
|
78
|
+
response = @app.call(env)
|
79
|
+
|
80
|
+
response.on_complete do |response_env|
|
81
|
+
if follow_redirect?(response_env, response)
|
82
|
+
raise RedirectLimitReached, response if follows.zero?
|
83
|
+
new_request_env = update_env(response_env.dup, request_body, response)
|
84
|
+
callback.call(response_env, new_request_env) if callback
|
85
|
+
response = perform_with_redirection(new_request_env, follows - 1)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
response
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_env(env, request_body, response)
|
92
|
+
env[:url] += safe_escape(response['location'] || '')
|
93
|
+
|
94
|
+
if convert_to_get?(response)
|
95
|
+
env[:method] = :get
|
96
|
+
env[:body] = nil
|
97
|
+
else
|
98
|
+
env[:body] = request_body
|
99
|
+
end
|
100
|
+
|
101
|
+
ENV_TO_CLEAR.each {|key| env.delete key }
|
102
|
+
|
103
|
+
env
|
104
|
+
end
|
105
|
+
|
106
|
+
def follow_redirect?(env, response)
|
107
|
+
ALLOWED_METHODS.include? env[:method] and
|
108
|
+
REDIRECT_CODES.include? response.status
|
109
|
+
end
|
110
|
+
|
111
|
+
def follow_limit
|
112
|
+
@options.fetch(:limit, FOLLOW_LIMIT)
|
113
|
+
end
|
114
|
+
|
115
|
+
def standards_compliant?
|
116
|
+
@options.fetch(:standards_compliant, false)
|
117
|
+
end
|
118
|
+
|
119
|
+
def callback
|
120
|
+
@options[:callback]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: escapes unsafe characters from an URL which might be a path
|
124
|
+
# component only or a fully qualified URI so that it can be joined onto an
|
125
|
+
# URI:HTTP using the `+` operator. Doesn't escape "%" characters so to not
|
126
|
+
# risk double-escaping.
|
127
|
+
def safe_escape(uri)
|
128
|
+
uri = uri.split('#')[0] # we want to remove the fragment if present
|
129
|
+
uri.to_s.gsub(URI_UNSAFE) { |match|
|
130
|
+
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module FaradayMiddleware
|
4
|
+
# Public: Converts parsed response bodies to a Hashie::Mash if they were of
|
5
|
+
# Hash or Array type.
|
6
|
+
class Mashify < Faraday::Response::Middleware
|
7
|
+
attr_accessor :mash_class
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :mash_class
|
11
|
+
end
|
12
|
+
|
13
|
+
dependency do
|
14
|
+
require 'hashie/mash'
|
15
|
+
self.mash_class = ::Hashie::Mash
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(app = nil, options = {})
|
19
|
+
super(app)
|
20
|
+
self.mash_class = options[:mash_class] || self.class.mash_class
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(body)
|
24
|
+
case body
|
25
|
+
when Hash
|
26
|
+
mash_class.new(body)
|
27
|
+
when Array
|
28
|
+
body.map { |item| parse(item) }
|
29
|
+
else
|
30
|
+
body
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# deprecated alias
|
37
|
+
Faraday::Response::Mashify = FaradayMiddleware::Mashify
|