hanami-controller 2.0.0.alpha8 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +123 -3
- data/hanami-controller.gemspec +21 -20
- data/lib/hanami/action/base_params.rb +20 -57
- data/lib/hanami/action/cache/cache_control.rb +14 -10
- data/lib/hanami/action/cache/conditional_get.rb +8 -26
- data/lib/hanami/action/cache/directives.rb +8 -6
- data/lib/hanami/action/cache/expires.rb +10 -11
- data/lib/hanami/action/cache.rb +5 -3
- data/lib/hanami/action/configuration.rb +20 -12
- data/lib/hanami/action/constants.rb +261 -0
- data/lib/hanami/action/cookie_jar.rb +37 -55
- data/lib/hanami/action/cookies.rb +2 -0
- data/lib/hanami/action/csrf_protection.rb +28 -31
- data/lib/hanami/action/flash.rb +4 -0
- data/lib/hanami/action/halt.rb +4 -0
- data/lib/hanami/action/mime.rb +103 -73
- data/lib/hanami/action/params.rb +41 -31
- data/lib/hanami/action/rack/file.rb +5 -9
- data/lib/hanami/action/request.rb +10 -59
- data/lib/hanami/action/response.rb +89 -46
- data/lib/hanami/action/session.rb +5 -3
- data/lib/hanami/action/validatable.rb +18 -19
- data/lib/hanami/action/view_name_inferrer.rb +10 -0
- data/lib/hanami/action.rb +133 -216
- data/lib/hanami/controller/error.rb +2 -0
- data/lib/hanami/controller/version.rb +3 -1
- data/lib/hanami/controller.rb +10 -12
- data/lib/hanami/http/status.rb +3 -1
- metadata +22 -8
- data/lib/hanami/action/glue.rb +0 -40
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/utils"
|
4
|
+
require "hanami/utils/hash"
|
2
5
|
|
3
6
|
module Hanami
|
4
7
|
class Action
|
@@ -10,40 +13,9 @@ module Hanami
|
|
10
13
|
#
|
11
14
|
# @see Hanami::Action::Cookies#cookies
|
12
15
|
class CookieJar
|
13
|
-
# The key that returns raw cookies from the Rack env
|
14
|
-
#
|
15
|
-
# @since 0.1.0
|
16
|
-
# @api private
|
17
|
-
HTTP_HEADER = 'HTTP_COOKIE'.freeze
|
18
|
-
|
19
|
-
# The key used by Rack to set the session cookie
|
20
|
-
#
|
21
|
-
# We let CookieJar to NOT take care of this cookie, but it leaves the
|
22
|
-
# responsibility to the Rack middleware that handle sessions.
|
23
|
-
#
|
24
|
-
# This prevents <tt>Set-Cookie</tt> to be sent twice.
|
25
|
-
#
|
26
|
-
# @since 0.5.1
|
27
|
-
# @api private
|
28
|
-
#
|
29
|
-
# @see https://github.com/hanami/controller/issues/138
|
30
|
-
RACK_SESSION_KEY = :'rack.session'
|
31
|
-
|
32
|
-
# The key used by Rack to set the cookies as an Hash in the env
|
33
|
-
#
|
34
|
-
# @since 0.1.0
|
35
|
-
# @api private
|
36
|
-
COOKIE_HASH_KEY = 'rack.request.cookie_hash'.freeze
|
37
|
-
|
38
|
-
# The key used by Rack to set the cookies as a String in the env
|
39
|
-
#
|
40
|
-
# @since 0.1.0
|
41
|
-
# @api private
|
42
|
-
COOKIE_STRING_KEY = 'rack.request.cookie_string'.freeze
|
43
|
-
|
44
16
|
# @since 0.4.5
|
45
17
|
# @api private
|
46
|
-
COOKIE_SEPARATOR =
|
18
|
+
COOKIE_SEPARATOR = ";,"
|
47
19
|
|
48
20
|
# Initialize the CookieJar
|
49
21
|
#
|
@@ -68,11 +40,14 @@ module Hanami
|
|
68
40
|
#
|
69
41
|
# @see Hanami::Action::Cookies#finish
|
70
42
|
def finish
|
71
|
-
@cookies.delete(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
43
|
+
@cookies.delete(Action::RACK_SESSION)
|
44
|
+
if changed?
|
45
|
+
@cookies.each do |k, v|
|
46
|
+
next unless changed?(k)
|
47
|
+
|
48
|
+
v.nil? ? delete_cookie(k) : set_cookie(k, _merge_default_values(v))
|
49
|
+
end
|
50
|
+
end
|
76
51
|
end
|
77
52
|
|
78
53
|
# Returns the object associated with the given key
|
@@ -122,12 +97,12 @@ module Hanami
|
|
122
97
|
#
|
123
98
|
# @example
|
124
99
|
# require "hanami/controller"
|
125
|
-
# class MyAction
|
126
|
-
# include Hanami::Action
|
100
|
+
# class MyAction < Hanami::Action
|
127
101
|
# include Hanami::Action::Cookies
|
128
102
|
#
|
129
|
-
# def
|
130
|
-
#
|
103
|
+
# def handle(req, res)
|
104
|
+
# # read cookies
|
105
|
+
# req.cookies.each do |key, value|
|
131
106
|
# # ...
|
132
107
|
# end
|
133
108
|
# end
|
@@ -167,10 +142,10 @@ module Hanami
|
|
167
142
|
# @api private
|
168
143
|
def _merge_default_values(value)
|
169
144
|
cookies_options = if value.is_a?(::Hash)
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
145
|
+
value.merge! _add_expires_option(value)
|
146
|
+
else
|
147
|
+
{value: value}
|
148
|
+
end
|
174
149
|
@default_options.merge cookies_options
|
175
150
|
end
|
176
151
|
|
@@ -179,8 +154,8 @@ module Hanami
|
|
179
154
|
# @since 0.4.3
|
180
155
|
# @api private
|
181
156
|
def _add_expires_option(value)
|
182
|
-
if value.
|
183
|
-
{
|
157
|
+
if value.key?(:max_age) && !value.key?(:expires)
|
158
|
+
{expires: (Time.now + value[:max_age])}
|
184
159
|
else
|
185
160
|
{}
|
186
161
|
end
|
@@ -193,11 +168,12 @@ module Hanami
|
|
193
168
|
# @since 0.1.0
|
194
169
|
# @api private
|
195
170
|
def extract(env)
|
196
|
-
hash = env[COOKIE_HASH_KEY] ||= {}
|
197
|
-
string = env[
|
171
|
+
hash = env[Action::COOKIE_HASH_KEY] ||= {}
|
172
|
+
string = env[Action::HTTP_COOKIE]
|
173
|
+
|
174
|
+
return hash if string == env[Action::COOKIE_STRING_KEY]
|
198
175
|
|
199
|
-
|
200
|
-
# TODO Next Rack 1.7.x ?? version will have ::Rack::Utils.parse_cookies
|
176
|
+
# TODO: Next Rack 1.7.x ?? version will have ::Rack::Utils.parse_cookies
|
201
177
|
# We can then replace the following lines.
|
202
178
|
hash.clear
|
203
179
|
|
@@ -206,9 +182,15 @@ module Hanami
|
|
206
182
|
# the Cookie header such that those with more specific Path attributes
|
207
183
|
# precede those with less specific. Ordering with respect to other
|
208
184
|
# attributes (e.g., Domain) is unspecified.
|
209
|
-
cookies = ::Rack::Utils.parse_query(string, COOKIE_SEPARATOR) { |s|
|
210
|
-
|
211
|
-
|
185
|
+
cookies = ::Rack::Utils.parse_query(string, COOKIE_SEPARATOR) { |s|
|
186
|
+
begin
|
187
|
+
::Rack::Utils.unescape(s)
|
188
|
+
rescue StandardError
|
189
|
+
s
|
190
|
+
end
|
191
|
+
}
|
192
|
+
cookies.each { |k, v| hash[k] = v.is_a?(Array) ? v.first : v }
|
193
|
+
env[Action::COOKIE_STRING_KEY] = string
|
212
194
|
hash
|
213
195
|
end
|
214
196
|
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "hanami/utils/blank"
|
4
|
+
require "hanami/controller/error"
|
2
5
|
require "rack/utils"
|
3
6
|
require "securerandom"
|
4
7
|
|
@@ -8,7 +11,7 @@ module Hanami
|
|
8
11
|
# Invalid CSRF Token
|
9
12
|
#
|
10
13
|
# @since 0.4.0
|
11
|
-
class InvalidCSRFTokenError < ::
|
14
|
+
class InvalidCSRFTokenError < Controller::Error
|
12
15
|
end
|
13
16
|
|
14
17
|
# CSRF Protection
|
@@ -38,10 +41,8 @@ module Hanami
|
|
38
41
|
#
|
39
42
|
# @example Custom Handling
|
40
43
|
# module Web::Controllers::Books
|
41
|
-
# class Create
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# def call(params)
|
44
|
+
# class Create < Web::Action
|
45
|
+
# def handl(*)
|
45
46
|
# # ...
|
46
47
|
# end
|
47
48
|
#
|
@@ -56,16 +57,14 @@ module Hanami
|
|
56
57
|
#
|
57
58
|
# @example Bypass Security Check
|
58
59
|
# module Web::Controllers::Books
|
59
|
-
# class Create
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# def call(params)
|
60
|
+
# class Create < Web::Action
|
61
|
+
# def handle(*)
|
63
62
|
# # ...
|
64
63
|
# end
|
65
64
|
#
|
66
65
|
# private
|
67
66
|
#
|
68
|
-
# def verify_csrf_token?
|
67
|
+
# def verify_csrf_token?(req, res)
|
69
68
|
# false
|
70
69
|
# end
|
71
70
|
# end
|
@@ -87,18 +86,20 @@ module Hanami
|
|
87
86
|
# @since 0.4.0
|
88
87
|
# @api private
|
89
88
|
IDEMPOTENT_HTTP_METHODS = Hash[
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
89
|
+
Action::GET => true,
|
90
|
+
Action::HEAD => true,
|
91
|
+
Action::TRACE => true,
|
92
|
+
Action::OPTIONS => true
|
94
93
|
].freeze
|
95
94
|
|
96
95
|
# @since 0.4.0
|
97
96
|
# @api private
|
98
97
|
def self.included(action)
|
99
|
-
|
100
|
-
|
101
|
-
|
98
|
+
unless Hanami.respond_to?(:env?) && Hanami.env?(:test)
|
99
|
+
action.class_eval do
|
100
|
+
before :set_csrf_token, :verify_csrf_token
|
101
|
+
end
|
102
|
+
end
|
102
103
|
end
|
103
104
|
|
104
105
|
private
|
@@ -107,7 +108,7 @@ module Hanami
|
|
107
108
|
#
|
108
109
|
# @since 0.4.0
|
109
110
|
# @api private
|
110
|
-
def set_csrf_token(
|
111
|
+
def set_csrf_token(_req, res)
|
111
112
|
res.session[CSRF_TOKEN] ||= generate_csrf_token
|
112
113
|
end
|
113
114
|
|
@@ -141,7 +142,7 @@ module Hanami
|
|
141
142
|
# Verify the CSRF token was passed in params.
|
142
143
|
#
|
143
144
|
# @api private
|
144
|
-
def missing_csrf_token?(req,
|
145
|
+
def missing_csrf_token?(req, *)
|
145
146
|
Hanami::Utils::Blank.blank?(req.params[CSRF_TOKEN])
|
146
147
|
end
|
147
148
|
|
@@ -161,21 +162,19 @@ module Hanami
|
|
161
162
|
#
|
162
163
|
# @example
|
163
164
|
# module Web::Controllers::Books
|
164
|
-
# class Create
|
165
|
-
#
|
166
|
-
#
|
167
|
-
# def call(params)
|
165
|
+
# class Create < Web::Action
|
166
|
+
# def call(*)
|
168
167
|
# # ...
|
169
168
|
# end
|
170
169
|
#
|
171
170
|
# private
|
172
171
|
#
|
173
|
-
# def verify_csrf_token?
|
172
|
+
# def verify_csrf_token?(req, res)
|
174
173
|
# false
|
175
174
|
# end
|
176
175
|
# end
|
177
176
|
# end
|
178
|
-
def verify_csrf_token?(req,
|
177
|
+
def verify_csrf_token?(req, *)
|
179
178
|
!IDEMPOTENT_HTTP_METHODS[req.request_method]
|
180
179
|
end
|
181
180
|
|
@@ -191,21 +190,19 @@ module Hanami
|
|
191
190
|
#
|
192
191
|
# @example
|
193
192
|
# module Web::Controllers::Books
|
194
|
-
# class Create
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# def call(params)
|
193
|
+
# class Create < Web::Action
|
194
|
+
# def call(*)
|
198
195
|
# # ...
|
199
196
|
# end
|
200
197
|
#
|
201
198
|
# private
|
202
199
|
#
|
203
|
-
# def handle_invalid_csrf_token
|
200
|
+
# def handle_invalid_csrf_token(req, res)
|
204
201
|
# # custom invalid CSRF management goes here
|
205
202
|
# end
|
206
203
|
# end
|
207
204
|
# end
|
208
|
-
def handle_invalid_csrf_token(
|
205
|
+
def handle_invalid_csrf_token(*, res)
|
209
206
|
res.session.clear
|
210
207
|
raise InvalidCSRFTokenError
|
211
208
|
end
|
data/lib/hanami/action/flash.rb
CHANGED
data/lib/hanami/action/halt.rb
CHANGED
data/lib/hanami/action/mime.rb
CHANGED
@@ -1,85 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "hanami/utils"
|
2
4
|
require "rack/utils"
|
3
5
|
require "rack/mime"
|
4
6
|
|
5
7
|
module Hanami
|
6
8
|
class Action
|
7
|
-
module Mime
|
8
|
-
DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
|
9
|
-
DEFAULT_CHARSET = 'utf-8'.freeze
|
10
|
-
|
11
|
-
# The key that returns content mime type from the Rack env
|
12
|
-
#
|
13
|
-
# @since 2.0.0
|
14
|
-
# @api private
|
15
|
-
HTTP_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
16
|
-
|
17
|
-
# The header key to set the mime type of the response
|
18
|
-
#
|
19
|
-
# @since 0.1.0
|
20
|
-
# @api private
|
21
|
-
CONTENT_TYPE = 'Content-Type'.freeze
|
22
|
-
|
9
|
+
module Mime # rubocop:disable Metrics/ModuleLength
|
23
10
|
# Most commom MIME Types used for responses
|
24
11
|
#
|
25
12
|
# @since 1.0.0
|
26
13
|
# @api private
|
27
14
|
TYPES = {
|
28
|
-
txt:
|
29
|
-
html:
|
30
|
-
json:
|
31
|
-
manifest:
|
32
|
-
atom:
|
33
|
-
avi:
|
34
|
-
bmp:
|
35
|
-
bz:
|
36
|
-
bz2:
|
37
|
-
chm:
|
38
|
-
css:
|
39
|
-
csv:
|
40
|
-
flv:
|
41
|
-
gif:
|
42
|
-
gz:
|
43
|
-
h264:
|
44
|
-
ico:
|
45
|
-
ics:
|
46
|
-
jpg:
|
47
|
-
js:
|
48
|
-
mp4:
|
49
|
-
mov:
|
50
|
-
mp3:
|
51
|
-
mp4a:
|
52
|
-
mpg:
|
53
|
-
oga:
|
54
|
-
ogg:
|
55
|
-
ogv:
|
56
|
-
pdf:
|
57
|
-
pgp:
|
58
|
-
png:
|
59
|
-
psd:
|
60
|
-
rss:
|
61
|
-
rtf:
|
62
|
-
sh:
|
63
|
-
svg:
|
64
|
-
swf:
|
65
|
-
tar:
|
66
|
-
torrent:
|
67
|
-
tsv:
|
68
|
-
uri:
|
69
|
-
vcs:
|
70
|
-
wav:
|
71
|
-
webm:
|
72
|
-
wmv:
|
73
|
-
woff:
|
74
|
-
woff2:
|
75
|
-
wsdl:
|
76
|
-
xhtml:
|
77
|
-
xml:
|
78
|
-
xslt:
|
79
|
-
yml:
|
80
|
-
zip:
|
15
|
+
txt: "text/plain",
|
16
|
+
html: "text/html",
|
17
|
+
json: "application/json",
|
18
|
+
manifest: "text/cache-manifest",
|
19
|
+
atom: "application/atom+xml",
|
20
|
+
avi: "video/x-msvideo",
|
21
|
+
bmp: "image/bmp",
|
22
|
+
bz: "application/x-bzip",
|
23
|
+
bz2: "application/x-bzip2",
|
24
|
+
chm: "application/vnd.ms-htmlhelp",
|
25
|
+
css: "text/css",
|
26
|
+
csv: "text/csv",
|
27
|
+
flv: "video/x-flv",
|
28
|
+
gif: "image/gif",
|
29
|
+
gz: "application/x-gzip",
|
30
|
+
h264: "video/h264",
|
31
|
+
ico: "image/vnd.microsoft.icon",
|
32
|
+
ics: "text/calendar",
|
33
|
+
jpg: "image/jpeg",
|
34
|
+
js: "application/javascript",
|
35
|
+
mp4: "video/mp4",
|
36
|
+
mov: "video/quicktime",
|
37
|
+
mp3: "audio/mpeg",
|
38
|
+
mp4a: "audio/mp4",
|
39
|
+
mpg: "video/mpeg",
|
40
|
+
oga: "audio/ogg",
|
41
|
+
ogg: "application/ogg",
|
42
|
+
ogv: "video/ogg",
|
43
|
+
pdf: "application/pdf",
|
44
|
+
pgp: "application/pgp-encrypted",
|
45
|
+
png: "image/png",
|
46
|
+
psd: "image/vnd.adobe.photoshop",
|
47
|
+
rss: "application/rss+xml",
|
48
|
+
rtf: "application/rtf",
|
49
|
+
sh: "application/x-sh",
|
50
|
+
svg: "image/svg+xml",
|
51
|
+
swf: "application/x-shockwave-flash",
|
52
|
+
tar: "application/x-tar",
|
53
|
+
torrent: "application/x-bittorrent",
|
54
|
+
tsv: "text/tab-separated-values",
|
55
|
+
uri: "text/uri-list",
|
56
|
+
vcs: "text/x-vcalendar",
|
57
|
+
wav: "audio/x-wav",
|
58
|
+
webm: "video/webm",
|
59
|
+
wmv: "video/x-ms-wmv",
|
60
|
+
woff: "application/font-woff",
|
61
|
+
woff2: "application/font-woff2",
|
62
|
+
wsdl: "application/wsdl+xml",
|
63
|
+
xhtml: "application/xhtml+xml",
|
64
|
+
xml: "application/xml",
|
65
|
+
xslt: "application/xslt+xml",
|
66
|
+
yml: "text/yaml",
|
67
|
+
zip: "application/zip"
|
81
68
|
}.freeze
|
82
69
|
|
70
|
+
# @since 2.0.0
|
71
|
+
# @api private
|
83
72
|
def self.content_type_with_charset(content_type, charset)
|
84
73
|
"#{content_type}; charset=#{charset}"
|
85
74
|
end
|
@@ -92,27 +81,38 @@ module Hanami
|
|
92
81
|
# lastly it will fallback to DEFAULT_CONTENT_TYPE
|
93
82
|
#
|
94
83
|
# @return [String]
|
84
|
+
#
|
85
|
+
# @since 2.0.0
|
86
|
+
# @api private
|
95
87
|
def self.content_type(configuration, request, accepted_mime_types)
|
96
88
|
if request.accept_header?
|
97
89
|
type = best_q_match(request.accept, accepted_mime_types)
|
98
90
|
return type if type
|
99
91
|
end
|
100
92
|
|
101
|
-
default_response_type(configuration) || default_content_type(configuration) || DEFAULT_CONTENT_TYPE
|
93
|
+
default_response_type(configuration) || default_content_type(configuration) || Action::DEFAULT_CONTENT_TYPE
|
102
94
|
end
|
103
95
|
|
96
|
+
# @since 2.0.0
|
97
|
+
# @api private
|
104
98
|
def self.charset(default_charset)
|
105
|
-
default_charset || DEFAULT_CHARSET
|
99
|
+
default_charset || Action::DEFAULT_CHARSET
|
106
100
|
end
|
107
101
|
|
102
|
+
# @since 2.0.0
|
103
|
+
# @api private
|
108
104
|
def self.default_response_type(configuration)
|
109
105
|
format_to_mime_type(configuration.default_response_format, configuration)
|
110
106
|
end
|
111
107
|
|
108
|
+
# @since 2.0.0
|
109
|
+
# @api private
|
112
110
|
def self.default_content_type(configuration)
|
113
111
|
format_to_mime_type(configuration.default_request_format, configuration)
|
114
112
|
end
|
115
113
|
|
114
|
+
# @since 2.0.0
|
115
|
+
# @api private
|
116
116
|
def self.format_to_mime_type(format, configuration)
|
117
117
|
return if format.nil?
|
118
118
|
|
@@ -128,12 +128,18 @@ module Hanami
|
|
128
128
|
# detect_format("text/html; charset=utf-8", configuration) #=> :html
|
129
129
|
#
|
130
130
|
# @return [Symbol, nil]
|
131
|
+
#
|
132
|
+
# @since 2.0.0
|
133
|
+
# @api private
|
131
134
|
def self.detect_format(content_type, configuration)
|
132
135
|
return if content_type.nil?
|
136
|
+
|
133
137
|
ct = content_type.split(";").first
|
134
138
|
configuration.format_for(ct) || format_for(ct)
|
135
139
|
end
|
136
140
|
|
141
|
+
# @since 2.0.0
|
142
|
+
# @api private
|
137
143
|
def self.format_for(content_type)
|
138
144
|
TYPES.key(content_type)
|
139
145
|
end
|
@@ -145,6 +151,9 @@ module Hanami
|
|
145
151
|
# @return [Array<String>, nil]
|
146
152
|
#
|
147
153
|
# @raise [Hanami::Controller::UnknownFormatError] if the format is invalid
|
154
|
+
#
|
155
|
+
# @since 2.0.0
|
156
|
+
# @api private
|
148
157
|
def self.restrict_mime_types(configuration, accepted_formats)
|
149
158
|
return if accepted_formats.empty?
|
150
159
|
|
@@ -155,6 +164,7 @@ module Hanami
|
|
155
164
|
accepted_mime_types = mime_types & configuration.mime_types
|
156
165
|
|
157
166
|
return if accepted_mime_types.empty?
|
167
|
+
|
158
168
|
accepted_mime_types
|
159
169
|
end
|
160
170
|
|
@@ -164,8 +174,13 @@ module Hanami
|
|
164
174
|
# If no Content-Type is sent in the request it will check the default_request_format
|
165
175
|
#
|
166
176
|
# @return [TrueClass, FalseClass]
|
177
|
+
#
|
178
|
+
# @since 2.0.0
|
179
|
+
# @api private
|
167
180
|
def self.accepted_mime_type?(request, accepted_mime_types, configuration)
|
168
|
-
mime_type = request.env[HTTP_CONTENT_TYPE] ||
|
181
|
+
mime_type = request.env[Action::HTTP_CONTENT_TYPE] ||
|
182
|
+
default_content_type(configuration) ||
|
183
|
+
Action::DEFAULT_CONTENT_TYPE
|
169
184
|
|
170
185
|
!accepted_mime_types.find { |mt| ::Rack::Mime.match?(mt, mime_type) }.nil?
|
171
186
|
end
|
@@ -175,6 +190,9 @@ module Hanami
|
|
175
190
|
# @see Hanami::Action::Mime#call
|
176
191
|
#
|
177
192
|
# @return [String]
|
193
|
+
#
|
194
|
+
# @since 2.0.0
|
195
|
+
# @api private
|
178
196
|
def self.calculate_content_type_with_charset(configuration, request, accepted_mime_types)
|
179
197
|
charset = self.charset(configuration.default_charset)
|
180
198
|
content_type = self.content_type(configuration, request, accepted_mime_types)
|
@@ -197,6 +215,7 @@ module Hanami
|
|
197
215
|
::Rack::Utils.q_values(q_value_header).each_with_index.map do |(req_mime, quality), index|
|
198
216
|
match = available_mimes.find { |am| ::Rack::Mime.match?(am, req_mime) }
|
199
217
|
next unless match
|
218
|
+
|
200
219
|
RequestMimeWeight.new(req_mime, quality, index, match)
|
201
220
|
end.compact.max&.format
|
202
221
|
end
|
@@ -204,6 +223,16 @@ module Hanami
|
|
204
223
|
# @since 1.0.1
|
205
224
|
# @api private
|
206
225
|
class RequestMimeWeight
|
226
|
+
# @since 2.0.0
|
227
|
+
# @api private
|
228
|
+
MIME_SEPARATOR = "/"
|
229
|
+
private_constant :MIME_SEPARATOR
|
230
|
+
|
231
|
+
# @since 2.0.0
|
232
|
+
# @api private
|
233
|
+
MIME_WILDCARD = "*"
|
234
|
+
private_constant :MIME_WILDCARD
|
235
|
+
|
207
236
|
include Comparable
|
208
237
|
|
209
238
|
# @since 1.0.1
|
@@ -237,6 +266,7 @@ module Hanami
|
|
237
266
|
# @api private
|
238
267
|
def <=>(other)
|
239
268
|
return priority <=> other.priority unless priority == other.priority
|
269
|
+
|
240
270
|
other.index <=> index
|
241
271
|
end
|
242
272
|
|
@@ -245,7 +275,7 @@ module Hanami
|
|
245
275
|
# @since 1.0.1
|
246
276
|
# @api private
|
247
277
|
def calculate_priority(mime)
|
248
|
-
@priority ||= (mime.split(
|
278
|
+
@priority ||= (mime.split(MIME_SEPARATOR, 2).count(MIME_WILDCARD) * -10) + quality
|
249
279
|
end
|
250
280
|
end
|
251
281
|
end
|