hanami-controller 2.0.0.alpha8 → 2.0.0.beta1
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 +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
|