faraday_middleware_safeyaml 0.12.pre.safeyaml

Sign up to get free protection for your applications and to get access to all the features.
@@ -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