grape-security 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +314 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -0
- data/README.md +1777 -0
- data/RELEASING.md +105 -0
- data/Rakefile +69 -0
- data/UPGRADING.md +124 -0
- data/grape-security.gemspec +39 -0
- data/grape.png +0 -0
- data/lib/grape.rb +99 -0
- data/lib/grape/api.rb +646 -0
- data/lib/grape/cookies.rb +39 -0
- data/lib/grape/endpoint.rb +533 -0
- data/lib/grape/error_formatter/base.rb +31 -0
- data/lib/grape/error_formatter/json.rb +15 -0
- data/lib/grape/error_formatter/txt.rb +16 -0
- data/lib/grape/error_formatter/xml.rb +15 -0
- data/lib/grape/exceptions/base.rb +66 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +10 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
- data/lib/grape/exceptions/missing_mime_type.rb +10 -0
- data/lib/grape/exceptions/missing_option.rb +10 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
- data/lib/grape/exceptions/unknown_options.rb +10 -0
- data/lib/grape/exceptions/unknown_validator.rb +10 -0
- data/lib/grape/exceptions/validation.rb +26 -0
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +31 -0
- data/lib/grape/formatter/json.rb +12 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +12 -0
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +32 -0
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +13 -0
- data/lib/grape/middleware/auth/digest.rb +13 -0
- data/lib/grape/middleware/auth/oauth2.rb +83 -0
- data/lib/grape/middleware/base.rb +62 -0
- data/lib/grape/middleware/error.rb +89 -0
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +150 -0
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner.rb +32 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +132 -0
- data/lib/grape/middleware/versioner/param.rb +42 -0
- data/lib/grape/middleware/versioner/path.rb +52 -0
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +29 -0
- data/lib/grape/parser/json.rb +11 -0
- data/lib/grape/parser/xml.rb +11 -0
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +27 -0
- data/lib/grape/util/content_types.rb +18 -0
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +120 -0
- data/lib/grape/validations.rb +322 -0
- data/lib/grape/validations/coerce.rb +63 -0
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/exactly_one_of.rb +26 -0
- data/lib/grape/validations/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/presence.rb +16 -0
- data/lib/grape/validations/regexp.rb +12 -0
- data/lib/grape/validations/values.rb +23 -0
- data/lib/grape/version.rb +3 -0
- data/spec/grape/api_spec.rb +2571 -0
- data/spec/grape/endpoint_spec.rb +784 -0
- data/spec/grape/entity_spec.rb +324 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/auth/digest_spec.rb +47 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
- data/spec/grape/middleware/base_spec.rb +58 -0
- data/spec/grape/middleware/error_spec.rb +45 -0
- data/spec/grape/middleware/exception_spec.rb +184 -0
- data/spec/grape/middleware/formatter_spec.rb +258 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +302 -0
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner/path_spec.rb +44 -0
- data/spec/grape/middleware/versioner_spec.rb +22 -0
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +132 -0
- data/spec/grape/validations/coerce_spec.rb +208 -0
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
- data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
- data/spec/grape/validations/presence_spec.rb +142 -0
- data/spec/grape/validations/regexp_spec.rb +40 -0
- data/spec/grape/validations/values_spec.rb +152 -0
- data/spec/grape/validations/zh-CN.yml +10 -0
- data/spec/grape/validations_spec.rb +994 -0
- data/spec/shared/versioning_examples.rb +121 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/basic_auth_encode_helpers.rb +3 -0
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +50 -0
- metadata +421 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
en:
|
2
|
+
grape:
|
3
|
+
errors:
|
4
|
+
format: ! '%{attribute} %{message}'
|
5
|
+
messages:
|
6
|
+
coerce: 'is invalid'
|
7
|
+
presence: 'is missing'
|
8
|
+
regexp: 'is invalid'
|
9
|
+
values: 'does not have a valid value'
|
10
|
+
missing_vendor_option:
|
11
|
+
problem: 'missing :vendor option.'
|
12
|
+
summary: 'when version using header, you must specify :vendor option. '
|
13
|
+
resolution: "eg: version 'v1', :using => :header, :vendor => 'twitter'"
|
14
|
+
missing_mime_type:
|
15
|
+
problem: 'missing mime type for %{new_format}'
|
16
|
+
resolution:
|
17
|
+
"you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES
|
18
|
+
or add your own with content_type :%{new_format}, 'application/%{new_format}'
|
19
|
+
"
|
20
|
+
invalid_with_option_for_represent:
|
21
|
+
problem: 'You must specify an entity class in the :with option.'
|
22
|
+
resolution: 'eg: represent User, :with => Entity::User'
|
23
|
+
missing_option: 'You must specify :%{option} options.'
|
24
|
+
invalid_formatter: 'cannot convert %{klass} to %{to_format}'
|
25
|
+
invalid_versioner_option:
|
26
|
+
problem: 'Unknown :using for versioner: %{strategy}'
|
27
|
+
resolution: 'available strategy for :using is :path, :header, :param'
|
28
|
+
unknown_validator: 'unknown validator: %{validator_type}'
|
29
|
+
unknown_options: 'unknown options: %{options}'
|
30
|
+
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
|
31
|
+
mutual_exclusion: 'are mutually exclusive'
|
32
|
+
exactly_one: "- exactly one parameter must be provided"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/auth/basic'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Auth
|
6
|
+
class Base < Grape::Middleware::Base
|
7
|
+
attr_reader :authenticator
|
8
|
+
|
9
|
+
def initialize(app, options = {}, &authenticator)
|
10
|
+
super(app, options)
|
11
|
+
@authenticator = authenticator
|
12
|
+
end
|
13
|
+
|
14
|
+
def base_request
|
15
|
+
raise NotImplementedError, "You must implement base_request."
|
16
|
+
end
|
17
|
+
|
18
|
+
def credentials
|
19
|
+
base_request.provided? ? base_request.credentials : [nil, nil]
|
20
|
+
end
|
21
|
+
|
22
|
+
def before
|
23
|
+
unless authenticator.call(*credentials)
|
24
|
+
throw :error, status: 401, message: "API Authorization Failed."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
module Auth
|
4
|
+
# OAuth 2.0 authorization for Grape APIs.
|
5
|
+
class OAuth2 < Grape::Middleware::Base
|
6
|
+
def default_options
|
7
|
+
{
|
8
|
+
token_class: 'AccessToken',
|
9
|
+
realm: 'OAuth API',
|
10
|
+
parameter: %w(bearer_token oauth_token access_token),
|
11
|
+
accepted_headers: %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION),
|
12
|
+
header: [/Bearer (.*)/i, /OAuth (.*)/i],
|
13
|
+
required: true
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def before
|
18
|
+
verify_token(token_parameter || token_header)
|
19
|
+
end
|
20
|
+
|
21
|
+
def request
|
22
|
+
@request ||= Grape::Request.new(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def params
|
26
|
+
@params ||= request.params
|
27
|
+
end
|
28
|
+
|
29
|
+
def token_parameter
|
30
|
+
Array(options[:parameter]).each do |p|
|
31
|
+
return params[p] if params[p]
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def token_header
|
37
|
+
return false unless authorization_header
|
38
|
+
Array(options[:header]).each do |regexp|
|
39
|
+
return $1 if authorization_header =~ regexp
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def authorization_header
|
45
|
+
options[:accepted_headers].each do |head|
|
46
|
+
return env[head] if env[head]
|
47
|
+
end
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def token_class
|
52
|
+
@klass ||= eval(options[:token_class]) # rubocop:disable Eval
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_token(token)
|
56
|
+
token = token_class.verify(token)
|
57
|
+
if token
|
58
|
+
if token.respond_to?(:expired?) && token.expired?
|
59
|
+
error_out(401, 'invalid_grant')
|
60
|
+
else
|
61
|
+
if !token.respond_to?(:permission_for?) || token.permission_for?(env)
|
62
|
+
env['api.token'] = token
|
63
|
+
else
|
64
|
+
error_out(403, 'insufficient_scope')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
elsif !!options[:required]
|
68
|
+
error_out(401, 'invalid_grant')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def error_out(status, error)
|
73
|
+
throw :error,
|
74
|
+
message: error,
|
75
|
+
status: status,
|
76
|
+
headers: {
|
77
|
+
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
class Base
|
4
|
+
attr_reader :app, :env, :options
|
5
|
+
|
6
|
+
# @param [Rack Application] app The standard argument for a Rack middleware.
|
7
|
+
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
8
|
+
def initialize(app, options = {})
|
9
|
+
@app = app
|
10
|
+
@options = default_options.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_options
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
dup.call!(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call!(env)
|
22
|
+
@env = env
|
23
|
+
before
|
24
|
+
@app_response = @app.call(@env)
|
25
|
+
after || @app_response
|
26
|
+
end
|
27
|
+
|
28
|
+
# @abstract
|
29
|
+
# Called before the application is called in the middleware lifecycle.
|
30
|
+
def before
|
31
|
+
end
|
32
|
+
|
33
|
+
# @abstract
|
34
|
+
# Called after the application is called in the middleware lifecycle.
|
35
|
+
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
36
|
+
def after
|
37
|
+
end
|
38
|
+
|
39
|
+
def response
|
40
|
+
Rack::Response.new(@app_response)
|
41
|
+
end
|
42
|
+
|
43
|
+
def content_type_for(format)
|
44
|
+
HashWithIndifferentAccess.new(content_types)[format]
|
45
|
+
end
|
46
|
+
|
47
|
+
def content_types
|
48
|
+
ContentTypes.content_types_for(options[:content_types])
|
49
|
+
end
|
50
|
+
|
51
|
+
def content_type
|
52
|
+
content_type_for(env['api.format'] || options[:format]) || 'text/html'
|
53
|
+
end
|
54
|
+
|
55
|
+
def mime_types
|
56
|
+
content_types.each_with_object({}) { |(k, v), types_without_params|
|
57
|
+
types_without_params[k] = v.split(';').first
|
58
|
+
}.invert
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Error < Base
|
6
|
+
def default_options
|
7
|
+
{
|
8
|
+
default_status: 500, # default status returned on error
|
9
|
+
default_message: "",
|
10
|
+
format: :txt,
|
11
|
+
formatters: {},
|
12
|
+
error_formatters: {},
|
13
|
+
rescue_all: false, # true to rescue all exceptions
|
14
|
+
rescue_subclasses: true, # rescue subclasses of exceptions listed
|
15
|
+
rescue_options: { backtrace: false }, # true to display backtrace
|
16
|
+
rescue_handlers: {}, # rescue handler blocks
|
17
|
+
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
18
|
+
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def call!(env)
|
23
|
+
@env = env
|
24
|
+
|
25
|
+
begin
|
26
|
+
error_response(catch(:error) do
|
27
|
+
return @app.call(@env)
|
28
|
+
end)
|
29
|
+
rescue StandardError => e
|
30
|
+
is_rescuable = rescuable?(e.class)
|
31
|
+
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
|
32
|
+
handler = lambda { |arg| error_response(arg) }
|
33
|
+
else
|
34
|
+
raise unless is_rescuable
|
35
|
+
handler = find_handler(e.class)
|
36
|
+
end
|
37
|
+
|
38
|
+
handler.nil? ? handle_error(e) : exec_handler(e, &handler)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_handler(klass)
|
43
|
+
handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
|
44
|
+
handler ||= options[:base_only_rescue_handlers][klass]
|
45
|
+
handler ||= options[:all_rescue_handler]
|
46
|
+
handler
|
47
|
+
end
|
48
|
+
|
49
|
+
def rescuable?(klass)
|
50
|
+
options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
|
51
|
+
end
|
52
|
+
|
53
|
+
def exec_handler(e, &handler)
|
54
|
+
if handler.lambda? && handler.arity == 0
|
55
|
+
instance_exec(&handler)
|
56
|
+
else
|
57
|
+
instance_exec(e, &handler)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_error(e)
|
62
|
+
error_response(message: e.message, backtrace: e.backtrace)
|
63
|
+
end
|
64
|
+
|
65
|
+
def error_response(error = {})
|
66
|
+
status = error[:status] || options[:default_status]
|
67
|
+
message = error[:message] || options[:default_message]
|
68
|
+
headers = { 'Content-Type' => content_type }
|
69
|
+
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
70
|
+
backtrace = error[:backtrace] || []
|
71
|
+
rack_response(format_message(message, backtrace), status, headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
|
75
|
+
if headers['Content-Type'] == 'text/html'
|
76
|
+
message = ERB::Util.html_escape(message)
|
77
|
+
end
|
78
|
+
Rack::Response.new([message], status, headers).finish
|
79
|
+
end
|
80
|
+
|
81
|
+
def format_message(message, backtrace)
|
82
|
+
format = env['api.format'] || options[:format]
|
83
|
+
formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
|
84
|
+
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
|
85
|
+
formatter.call(message, backtrace, options, env)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
# This is a simple middleware for adding before and after filters
|
4
|
+
# to Grape APIs. It is used like so:
|
5
|
+
#
|
6
|
+
# use Grape::Middleware::Filter, before: lambda { do_something }, after: lambda { do_something }
|
7
|
+
class Filter < Base
|
8
|
+
def before
|
9
|
+
app.instance_eval(&options[:before]) if options[:before]
|
10
|
+
end
|
11
|
+
|
12
|
+
def after
|
13
|
+
app.instance_eval(&options[:after]) if options[:after]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Formatter < Base
|
6
|
+
def default_options
|
7
|
+
{
|
8
|
+
default_format: :txt,
|
9
|
+
formatters: {},
|
10
|
+
parsers: {}
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def headers
|
15
|
+
env.dup.inject({}) do |h, (k, v)|
|
16
|
+
h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_')
|
17
|
+
h
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def before
|
22
|
+
negotiate_content_type
|
23
|
+
read_body_input
|
24
|
+
end
|
25
|
+
|
26
|
+
def after
|
27
|
+
status, headers, bodies = *@app_response
|
28
|
+
# allow content-type to be explicitly overwritten
|
29
|
+
api_format = mime_types[headers["Content-Type"]] || env['api.format']
|
30
|
+
formatter = Grape::Formatter::Base.formatter_for api_format, options
|
31
|
+
begin
|
32
|
+
bodymap = bodies.collect do |body|
|
33
|
+
formatter.call body, env
|
34
|
+
end
|
35
|
+
rescue Grape::Exceptions::InvalidFormatter => e
|
36
|
+
throw :error, status: 500, message: e.message
|
37
|
+
end
|
38
|
+
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
|
39
|
+
Rack::Response.new(bodymap, status, headers).to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def request
|
45
|
+
@request ||= Rack::Request.new(env)
|
46
|
+
end
|
47
|
+
|
48
|
+
# store read input in env['api.request.input']
|
49
|
+
def read_body_input
|
50
|
+
if (request.post? || request.put? || request.patch? || request.delete?) &&
|
51
|
+
(!request.form_data? || !request.media_type) &&
|
52
|
+
(!request.parseable_data?) &&
|
53
|
+
(request.content_length.to_i > 0 || request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
|
54
|
+
|
55
|
+
if (input = env['rack.input'])
|
56
|
+
input.rewind
|
57
|
+
body = env['api.request.input'] = input.read
|
58
|
+
begin
|
59
|
+
read_rack_input(body) if body && body.length > 0
|
60
|
+
ensure
|
61
|
+
input.rewind
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# store parsed input in env['api.request.body']
|
68
|
+
def read_rack_input(body)
|
69
|
+
fmt = mime_types[request.media_type] if request.media_type
|
70
|
+
fmt ||= options[:default_format]
|
71
|
+
if content_type_for(fmt)
|
72
|
+
parser = Grape::Parser::Base.parser_for fmt, options
|
73
|
+
if parser
|
74
|
+
begin
|
75
|
+
body = (env['api.request.body'] = parser.call(body, env))
|
76
|
+
if body.is_a?(Hash)
|
77
|
+
if env['rack.request.form_hash']
|
78
|
+
env['rack.request.form_hash'] = env['rack.request.form_hash'].merge(body)
|
79
|
+
else
|
80
|
+
env['rack.request.form_hash'] = body
|
81
|
+
end
|
82
|
+
env['rack.request.form_input'] = env['rack.input']
|
83
|
+
end
|
84
|
+
rescue StandardError => e
|
85
|
+
throw :error, status: 400, message: e.message
|
86
|
+
end
|
87
|
+
else
|
88
|
+
env['api.request.body'] = body
|
89
|
+
end
|
90
|
+
else
|
91
|
+
throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def negotiate_content_type
|
96
|
+
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
|
97
|
+
if content_type_for(fmt)
|
98
|
+
env['api.format'] = fmt
|
99
|
+
else
|
100
|
+
throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_from_extension
|
105
|
+
parts = request.path.split('.')
|
106
|
+
|
107
|
+
if parts.size > 1
|
108
|
+
extension = parts.last
|
109
|
+
# avoid symbol memory leak on an unknown format
|
110
|
+
return extension.to_sym if content_type_for(extension)
|
111
|
+
end
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def format_from_params
|
116
|
+
fmt = Rack::Utils.parse_nested_query(env['QUERY_STRING'])["format"]
|
117
|
+
# avoid symbol memory leak on an unknown format
|
118
|
+
return fmt.to_sym if content_type_for(fmt)
|
119
|
+
fmt
|
120
|
+
end
|
121
|
+
|
122
|
+
def format_from_header
|
123
|
+
mime_array.each do |t|
|
124
|
+
return mime_types[t] if mime_types.key?(t)
|
125
|
+
end
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def mime_array
|
130
|
+
accept = headers['accept']
|
131
|
+
return [] unless accept
|
132
|
+
|
133
|
+
accept_into_mime_and_quality = %r{
|
134
|
+
(
|
135
|
+
\w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
|
136
|
+
(?:
|
137
|
+
(?:;[^,]*?)? # optionally multiple formats in a row
|
138
|
+
;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
|
139
|
+
)?
|
140
|
+
}x
|
141
|
+
|
142
|
+
vendor_prefix_pattern = /vnd\.[^+]+\+/
|
143
|
+
|
144
|
+
accept.scan(accept_into_mime_and_quality)
|
145
|
+
.sort_by { |_, quality_preference| -quality_preference.to_f }
|
146
|
+
.map { |mime, _| mime.sub(vendor_prefix_pattern, '') }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|