hanami-action 3.0.0.rc1
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/CHANGELOG.md +985 -0
- data/LICENSE +20 -0
- data/README.md +873 -0
- data/hanami-action.gemspec +39 -0
- data/lib/hanami/action/body_parser/json.rb +20 -0
- data/lib/hanami/action/body_parser/multipart_form.rb +22 -0
- data/lib/hanami/action/body_parser.rb +109 -0
- data/lib/hanami/action/cache/cache_control.rb +84 -0
- data/lib/hanami/action/cache/conditional_get.rb +101 -0
- data/lib/hanami/action/cache/directives.rb +126 -0
- data/lib/hanami/action/cache/expires.rb +84 -0
- data/lib/hanami/action/cache.rb +29 -0
- data/lib/hanami/action/config/formats.rb +256 -0
- data/lib/hanami/action/config.rb +172 -0
- data/lib/hanami/action/constants.rb +283 -0
- data/lib/hanami/action/cookie_jar.rb +214 -0
- data/lib/hanami/action/cookies.rb +27 -0
- data/lib/hanami/action/csrf_protection.rb +217 -0
- data/lib/hanami/action/errors.rb +109 -0
- data/lib/hanami/action/flash.rb +176 -0
- data/lib/hanami/action/halt.rb +18 -0
- data/lib/hanami/action/mime/request_mime_weight.rb +66 -0
- data/lib/hanami/action/mime.rb +438 -0
- data/lib/hanami/action/params.rb +342 -0
- data/lib/hanami/action/rack/file.rb +41 -0
- data/lib/hanami/action/rack_utils.rb +11 -0
- data/lib/hanami/action/request/session.rb +68 -0
- data/lib/hanami/action/request.rb +141 -0
- data/lib/hanami/action/response.rb +481 -0
- data/lib/hanami/action/session.rb +47 -0
- data/lib/hanami/action/validatable.rb +166 -0
- data/lib/hanami/action/version.rb +13 -0
- data/lib/hanami/action/view_name_inferrer.rb +56 -0
- data/lib/hanami/action.rb +672 -0
- data/lib/hanami/http/status.rb +149 -0
- data/lib/hanami-action.rb +3 -0
- metadata +153 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack/utils"
|
|
4
|
+
require "hanami/utils/hash"
|
|
5
|
+
|
|
6
|
+
module Hanami
|
|
7
|
+
class Action
|
|
8
|
+
# A set of HTTP Cookies
|
|
9
|
+
#
|
|
10
|
+
# It acts as an Hash
|
|
11
|
+
#
|
|
12
|
+
# @since 0.1.0
|
|
13
|
+
#
|
|
14
|
+
# @see Hanami::Action::Cookies#cookies
|
|
15
|
+
class CookieJar
|
|
16
|
+
# @since 0.4.5
|
|
17
|
+
# @api private
|
|
18
|
+
COOKIE_SEPARATOR = ";,"
|
|
19
|
+
|
|
20
|
+
# Initialize the CookieJar
|
|
21
|
+
#
|
|
22
|
+
# @param env [Hash] a raw Rack env
|
|
23
|
+
# @param headers [Hash] the response headers
|
|
24
|
+
#
|
|
25
|
+
# @return [CookieJar]
|
|
26
|
+
#
|
|
27
|
+
# @since 0.1.0
|
|
28
|
+
def initialize(env, headers, default_options)
|
|
29
|
+
@_headers = headers
|
|
30
|
+
@cookies = Utils::Hash.deep_symbolize(extract(env))
|
|
31
|
+
@default_options = default_options
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Finalize itself, by setting the proper headers to add and remove
|
|
35
|
+
# cookies, before the response is returned to the webserver.
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
# @since 0.1.0
|
|
40
|
+
#
|
|
41
|
+
# @see Hanami::Action::Cookies#finish
|
|
42
|
+
def finish
|
|
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
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the object associated with the given key
|
|
54
|
+
#
|
|
55
|
+
# @param key [Symbol] the key
|
|
56
|
+
#
|
|
57
|
+
# @return [Object,nil] return the associated object, if found
|
|
58
|
+
#
|
|
59
|
+
# @since 0.2.0
|
|
60
|
+
def [](key)
|
|
61
|
+
@cookies[key]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Associate the given value with the given key and store them
|
|
65
|
+
#
|
|
66
|
+
# @param key [Symbol] the key
|
|
67
|
+
# @param value [#to_s,Hash] value that can be serialized as a string or
|
|
68
|
+
# expressed as a Hash
|
|
69
|
+
# @option value [String] :value - Value of the cookie
|
|
70
|
+
# @option value [String] :domain - The domain
|
|
71
|
+
# @option value [String] :path - The path
|
|
72
|
+
# @option value [Integer] :max_age - Duration expressed in seconds
|
|
73
|
+
# @option value [Time] :expires - Expiration time
|
|
74
|
+
# @option value [TrueClass,FalseClass] :secure - Restrict cookie to secure
|
|
75
|
+
# connections
|
|
76
|
+
# @option value [TrueClass,FalseClass] :httponly - Restrict JavaScript
|
|
77
|
+
# access
|
|
78
|
+
#
|
|
79
|
+
# @return [void]
|
|
80
|
+
#
|
|
81
|
+
# @since 0.2.0
|
|
82
|
+
#
|
|
83
|
+
# @see http://en.wikipedia.org/wiki/HTTP_cookie
|
|
84
|
+
def []=(key, value)
|
|
85
|
+
changes << key
|
|
86
|
+
@cookies[key] = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Iterates cookies
|
|
90
|
+
#
|
|
91
|
+
# @param blk [Proc] the block to be yielded
|
|
92
|
+
# @yield [key, value] the key/value pair for each cookie
|
|
93
|
+
#
|
|
94
|
+
# @return [void]
|
|
95
|
+
#
|
|
96
|
+
# @since 1.1.0
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# require "hanami/action"
|
|
100
|
+
# class MyAction < Hanami::Action
|
|
101
|
+
# include Hanami::Action::Cookies
|
|
102
|
+
#
|
|
103
|
+
# def handle(req, res)
|
|
104
|
+
# # read cookies
|
|
105
|
+
# req.cookies.each do |key, value|
|
|
106
|
+
# # ...
|
|
107
|
+
# end
|
|
108
|
+
# end
|
|
109
|
+
# end
|
|
110
|
+
def each(&blk)
|
|
111
|
+
@cookies.each(&blk)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
# Keep track of changed keys
|
|
117
|
+
#
|
|
118
|
+
# @since 0.7.0
|
|
119
|
+
# @api private
|
|
120
|
+
def changes
|
|
121
|
+
@changes ||= Set.new
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Check if the entire set of cookies has changed within the current request.
|
|
125
|
+
# If <tt>key</tt> is given, it checks the associated cookie has changed.
|
|
126
|
+
#
|
|
127
|
+
# @since 0.7.0
|
|
128
|
+
# @api private
|
|
129
|
+
def changed?(key = nil)
|
|
130
|
+
if key.nil?
|
|
131
|
+
changes.any?
|
|
132
|
+
else
|
|
133
|
+
changes.include?(key)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Merge default cookies options with values provided by user
|
|
138
|
+
#
|
|
139
|
+
# Cookies values provided by user are respected
|
|
140
|
+
#
|
|
141
|
+
# @since 0.4.0
|
|
142
|
+
# @api private
|
|
143
|
+
def _merge_default_values(value)
|
|
144
|
+
cookies_options = if value.is_a?(::Hash)
|
|
145
|
+
value.merge! _add_expires_option(value)
|
|
146
|
+
else
|
|
147
|
+
{value: value}
|
|
148
|
+
end
|
|
149
|
+
@default_options.merge cookies_options
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Add expires option to cookies if :max_age presents
|
|
153
|
+
#
|
|
154
|
+
# @since 0.4.3
|
|
155
|
+
# @api private
|
|
156
|
+
def _add_expires_option(value)
|
|
157
|
+
if value.key?(:max_age) && !value.key?(:expires)
|
|
158
|
+
{expires: (Time.now + value[:max_age])}
|
|
159
|
+
else
|
|
160
|
+
{}
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Extract the cookies from the raw Rack env.
|
|
165
|
+
#
|
|
166
|
+
# This implementation is borrowed from Rack::Request#cookies.
|
|
167
|
+
#
|
|
168
|
+
# @since 0.1.0
|
|
169
|
+
# @api private
|
|
170
|
+
def extract(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]
|
|
175
|
+
|
|
176
|
+
# TODO: Next Rack 1.7.x ?? version will have ::Rack::Utils.parse_cookies
|
|
177
|
+
# We can then replace the following lines.
|
|
178
|
+
hash.clear
|
|
179
|
+
|
|
180
|
+
# According to RFC 2109:
|
|
181
|
+
# If multiple cookies satisfy the criteria above, they are ordered in
|
|
182
|
+
# the Cookie header such that those with more specific Path attributes
|
|
183
|
+
# precede those with less specific. Ordering with respect to other
|
|
184
|
+
# attributes (e.g., Domain) is unspecified.
|
|
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
|
|
194
|
+
hash
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Set a cookie in the headers
|
|
198
|
+
#
|
|
199
|
+
# @since 0.1.0
|
|
200
|
+
# @api private
|
|
201
|
+
def set_cookie(key, value)
|
|
202
|
+
::Rack::Utils.set_cookie_header!(@_headers, key, value)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Remove a cookie from the headers
|
|
206
|
+
#
|
|
207
|
+
# @since 0.1.0
|
|
208
|
+
# @api private
|
|
209
|
+
def delete_cookie(key)
|
|
210
|
+
::Rack::Utils.delete_cookie_header!(@_headers, key, {})
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Action
|
|
5
|
+
# Cookies API
|
|
6
|
+
#
|
|
7
|
+
# This module isn't included by default.
|
|
8
|
+
#
|
|
9
|
+
# @since 0.1.0
|
|
10
|
+
#
|
|
11
|
+
# @see Hanami::Action::Cookies#cookies
|
|
12
|
+
module Cookies
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Finalize the response by flushing cookies into the response
|
|
16
|
+
#
|
|
17
|
+
# @since 0.1.0
|
|
18
|
+
# @api private
|
|
19
|
+
#
|
|
20
|
+
# @see Hanami::Action#finish
|
|
21
|
+
def finish(req, res, *)
|
|
22
|
+
res.cookies.finish
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/utils/blank"
|
|
4
|
+
require "rack/utils"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require_relative "errors"
|
|
7
|
+
|
|
8
|
+
module Hanami
|
|
9
|
+
# @api private
|
|
10
|
+
class Action
|
|
11
|
+
# CSRF Protection
|
|
12
|
+
#
|
|
13
|
+
# This security mechanism is enabled automatically if sessions are turned on.
|
|
14
|
+
#
|
|
15
|
+
# It stores a "challenge" token in session. For each "state changing request"
|
|
16
|
+
# (eg. <tt>POST</tt>, <tt>PATCH</tt> etc..), we should send a special param
|
|
17
|
+
# <tt>_csrf_token</tt> or header <tt>X-CSRF-Token</tt> which contain the "challenge"
|
|
18
|
+
# token.
|
|
19
|
+
#
|
|
20
|
+
# If the request token matches with the challenge token, the flow can continue.
|
|
21
|
+
# Otherwise the application detects an attack attempt, it reset the session
|
|
22
|
+
# and <tt>Hanami::Action::InvalidCSRFTokenError</tt> is raised.
|
|
23
|
+
#
|
|
24
|
+
# We can specify a custom handling strategy, by overriding <tt>#handle_invalid_csrf_token</tt>.
|
|
25
|
+
#
|
|
26
|
+
# Form helper (<tt>#form_for</tt>) automatically sets a hidden field with the
|
|
27
|
+
# correct token. A special view method (<tt>#csrf_token</tt>) is available in
|
|
28
|
+
# case the form markup is manually crafted.
|
|
29
|
+
#
|
|
30
|
+
# We can disable this check on action basis, by overriding <tt>#verify_csrf_token?</tt>.
|
|
31
|
+
#
|
|
32
|
+
# @since 0.4.0
|
|
33
|
+
#
|
|
34
|
+
# @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29
|
|
35
|
+
# @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
|
|
36
|
+
#
|
|
37
|
+
# @example Custom Handling
|
|
38
|
+
# module Web::Controllers::Books
|
|
39
|
+
# class Create < Web::Action
|
|
40
|
+
# def handl(*)
|
|
41
|
+
# # ...
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# private
|
|
45
|
+
#
|
|
46
|
+
# def handle_invalid_csrf_token
|
|
47
|
+
# Web::Logger.warn "CSRF attack: expected #{ session[:_csrf_token] }, was #{ params[:_csrf_token] }"
|
|
48
|
+
# # manual handling
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# @example Bypass Security Check
|
|
54
|
+
# module Web::Controllers::Books
|
|
55
|
+
# class Create < Web::Action
|
|
56
|
+
# def handle(*)
|
|
57
|
+
# # ...
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# private
|
|
61
|
+
#
|
|
62
|
+
# def verify_csrf_token?(req, res)
|
|
63
|
+
# false
|
|
64
|
+
# end
|
|
65
|
+
# end
|
|
66
|
+
# end
|
|
67
|
+
module CSRFProtection
|
|
68
|
+
# Session and params key for CSRF token.
|
|
69
|
+
#
|
|
70
|
+
# This key is shared with <tt>hanami-action</tt> and <tt>hanami-helpers</tt>
|
|
71
|
+
#
|
|
72
|
+
# @since 0.4.0
|
|
73
|
+
# @api private
|
|
74
|
+
CSRF_TOKEN = :_csrf_token
|
|
75
|
+
|
|
76
|
+
# Idempotent HTTP methods
|
|
77
|
+
#
|
|
78
|
+
# By default, the check isn't performed if the request method is included
|
|
79
|
+
# in this list.
|
|
80
|
+
#
|
|
81
|
+
# @since 0.4.0
|
|
82
|
+
# @api private
|
|
83
|
+
IDEMPOTENT_HTTP_METHODS = Hash[
|
|
84
|
+
Action::GET => true,
|
|
85
|
+
Action::HEAD => true,
|
|
86
|
+
Action::TRACE => true,
|
|
87
|
+
Action::OPTIONS => true
|
|
88
|
+
].freeze
|
|
89
|
+
|
|
90
|
+
# @since 0.4.0
|
|
91
|
+
# @api private
|
|
92
|
+
def self.included(action)
|
|
93
|
+
unless Hanami.respond_to?(:env?) && Hanami.env?(:test)
|
|
94
|
+
action.include Hanami::Action::Session
|
|
95
|
+
action.class_eval do
|
|
96
|
+
before :set_csrf_token, :verify_csrf_token
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Set CSRF Token in session
|
|
104
|
+
#
|
|
105
|
+
# @since 0.4.0
|
|
106
|
+
# @api private
|
|
107
|
+
def set_csrf_token(_req, res)
|
|
108
|
+
res.session[CSRF_TOKEN] ||= generate_csrf_token
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get CSRF Token in request.
|
|
112
|
+
#
|
|
113
|
+
# Retreives the CSRF token from the request param <tt>_csrf_token</tt> or the request header
|
|
114
|
+
# <tt>X-CSRF-Token</tt>.
|
|
115
|
+
#
|
|
116
|
+
# @api private
|
|
117
|
+
def request_csrf_token(req)
|
|
118
|
+
req.params.raw[CSRF_TOKEN.to_s] || req.get_header("HTTP_X_CSRF_TOKEN")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Verify if CSRF token from params, matches the one stored in session.
|
|
122
|
+
# If not, it raises an error.
|
|
123
|
+
#
|
|
124
|
+
# Don't override this method.
|
|
125
|
+
#
|
|
126
|
+
# To bypass the security check, please override <tt>#verify_csrf_token?</tt>.
|
|
127
|
+
# For custom handling of an attack, please override <tt>#handle_invalid_csrf_token</tt>.
|
|
128
|
+
#
|
|
129
|
+
# @since 0.4.0
|
|
130
|
+
# @api private
|
|
131
|
+
def verify_csrf_token(req, res)
|
|
132
|
+
handle_invalid_csrf_token(req, res) if invalid_csrf_token?(req, res)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Verify if CSRF token from params, matches the one stored in session.
|
|
136
|
+
#
|
|
137
|
+
# Don't override this method.
|
|
138
|
+
#
|
|
139
|
+
# @since 0.4.0
|
|
140
|
+
# @api private
|
|
141
|
+
def invalid_csrf_token?(req, res)
|
|
142
|
+
return false unless verify_csrf_token?(req, res)
|
|
143
|
+
|
|
144
|
+
missing_csrf_token?(req, res) ||
|
|
145
|
+
!::Rack::Utils.secure_compare(req.session[CSRF_TOKEN], request_csrf_token(req))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Verify the CSRF token was passed in params.
|
|
149
|
+
#
|
|
150
|
+
# @api private
|
|
151
|
+
def missing_csrf_token?(req, *)
|
|
152
|
+
Hanami::Utils::Blank.blank?(request_csrf_token(req))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Generates a random CSRF Token
|
|
156
|
+
#
|
|
157
|
+
# @since 0.4.0
|
|
158
|
+
# @api private
|
|
159
|
+
def generate_csrf_token
|
|
160
|
+
SecureRandom.hex(32)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Decide if perform the check or not.
|
|
164
|
+
#
|
|
165
|
+
# Override and return <tt>false</tt> if you want to bypass security check.
|
|
166
|
+
#
|
|
167
|
+
# @since 0.4.0
|
|
168
|
+
#
|
|
169
|
+
# @example
|
|
170
|
+
# module Web::Controllers::Books
|
|
171
|
+
# class Create < Web::Action
|
|
172
|
+
# def call(*)
|
|
173
|
+
# # ...
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# private
|
|
177
|
+
#
|
|
178
|
+
# def verify_csrf_token?(req, res)
|
|
179
|
+
# false
|
|
180
|
+
# end
|
|
181
|
+
# end
|
|
182
|
+
# end
|
|
183
|
+
def verify_csrf_token?(req, *)
|
|
184
|
+
!IDEMPOTENT_HTTP_METHODS[req.request_method]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Handle CSRF attack.
|
|
188
|
+
#
|
|
189
|
+
# The default policy resets the session and raises an exception.
|
|
190
|
+
#
|
|
191
|
+
# Override this method, for custom handling.
|
|
192
|
+
#
|
|
193
|
+
# @raise [Hanami::Action::InvalidCSRFTokenError]
|
|
194
|
+
#
|
|
195
|
+
# @since 0.4.0
|
|
196
|
+
#
|
|
197
|
+
# @example
|
|
198
|
+
# module Web::Controllers::Books
|
|
199
|
+
# class Create < Web::Action
|
|
200
|
+
# def call(*)
|
|
201
|
+
# # ...
|
|
202
|
+
# end
|
|
203
|
+
#
|
|
204
|
+
# private
|
|
205
|
+
#
|
|
206
|
+
# def handle_invalid_csrf_token(req, res)
|
|
207
|
+
# # custom invalid CSRF management goes here
|
|
208
|
+
# end
|
|
209
|
+
# end
|
|
210
|
+
# end
|
|
211
|
+
def handle_invalid_csrf_token(*, res)
|
|
212
|
+
res.session.clear
|
|
213
|
+
raise InvalidCSRFTokenError
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Action
|
|
5
|
+
# Base class for all Action errors.
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
# @since 2.0.0
|
|
9
|
+
class Error < ::StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Unknown status HTTP Status error
|
|
13
|
+
#
|
|
14
|
+
# @since 2.0.2
|
|
15
|
+
#
|
|
16
|
+
# @see Hanami::Action::Response#status=
|
|
17
|
+
# @see https://guides.hanamirb.org/v2.0/actions/status-codes/
|
|
18
|
+
class UnknownHttpStatusError < Error
|
|
19
|
+
# @since 2.0.2
|
|
20
|
+
# @api private
|
|
21
|
+
def initialize(code)
|
|
22
|
+
super("unknown HTTP status: `#{code.inspect}'")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Unknown format error
|
|
27
|
+
#
|
|
28
|
+
# This error is raised when a action sets a format that it isn't recognized
|
|
29
|
+
# both by `Hanami::Action::Configuration` and the list of Rack mime types
|
|
30
|
+
#
|
|
31
|
+
# @since 2.0.0
|
|
32
|
+
#
|
|
33
|
+
# @see Hanami::Action::Mime#format=
|
|
34
|
+
class UnknownFormatError < Error
|
|
35
|
+
# @since 2.0.0
|
|
36
|
+
# @api private
|
|
37
|
+
def initialize(format)
|
|
38
|
+
message = <<~MSG
|
|
39
|
+
Cannot find a corresponding MIME type for format `#{format.inspect}'.
|
|
40
|
+
MSG
|
|
41
|
+
|
|
42
|
+
unless blank?(format)
|
|
43
|
+
message += <<~MSG
|
|
44
|
+
|
|
45
|
+
Configure one via: `config.actions.formats.add(:#{format}, "MIME_TYPE_HERE")' in `config/app.rb' to share between actions of a Hanami app.
|
|
46
|
+
|
|
47
|
+
Or make it available only in the current action: `config.formats.add(:#{format}, "MIME_TYPE_HERE")'.
|
|
48
|
+
MSG
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
super(message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def blank?(format)
|
|
57
|
+
format.to_s.match(/\A[[:space:]]*\z/)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Error raised when body parsing fails.
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
# @since x.x.x
|
|
65
|
+
class BodyParsingError < Error
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Error raised when session is accessed but not enabled.
|
|
69
|
+
#
|
|
70
|
+
# This error is raised when `session` or `flash` is accessed/set on request/response objects
|
|
71
|
+
# in actions which do not include `Hanami::Action::Session`.
|
|
72
|
+
#
|
|
73
|
+
# @see Hanami::Action::Session
|
|
74
|
+
# @see Hanami::Action::Request#session
|
|
75
|
+
# @see Hanami::Action::Response#session
|
|
76
|
+
# @see Hanami::Action::Response#flash
|
|
77
|
+
#
|
|
78
|
+
# @api public
|
|
79
|
+
# @since 2.0.0
|
|
80
|
+
class MissingSessionError < Error
|
|
81
|
+
# @api private
|
|
82
|
+
# @since 2.0.0
|
|
83
|
+
def initialize(session_method)
|
|
84
|
+
super(<<~TEXT)
|
|
85
|
+
Sessions are not enabled. To use `#{session_method}`:
|
|
86
|
+
|
|
87
|
+
Configure sessions in your Hanami app, e.g.
|
|
88
|
+
|
|
89
|
+
module MyApp
|
|
90
|
+
class App < Hanami::App
|
|
91
|
+
# See Rack::Session::Cookie for options
|
|
92
|
+
config.actions.sessions = :cookie, {**cookie_session_options}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
Or include session support directly in your action class:
|
|
97
|
+
|
|
98
|
+
include Hanami::Action::Session
|
|
99
|
+
TEXT
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Invalid CSRF Token
|
|
104
|
+
#
|
|
105
|
+
# @since 0.4.0
|
|
106
|
+
class InvalidCSRFTokenError < Error
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|