roda 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +8 -0
- data/doc/release_notes/3.11.0.txt +54 -0
- data/lib/roda.rb +11 -0
- data/lib/roda/plugins/_after_hook.rb +35 -0
- data/lib/roda/plugins/_before_hook.rb +51 -0
- data/lib/roda/plugins/class_level_routing.rb +16 -11
- data/lib/roda/plugins/error_handler.rb +23 -2
- data/lib/roda/plugins/flash.rb +7 -5
- data/lib/roda/plugins/head.rb +7 -4
- data/lib/roda/plugins/heartbeat.rb +8 -4
- data/lib/roda/plugins/hooks.rb +17 -11
- data/lib/roda/plugins/sessions.rb +25 -27
- data/lib/roda/plugins/static_routing.rb +10 -10
- data/lib/roda/plugins/status_handler.rb +9 -7
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/error_handler_spec.rb +22 -0
- data/spec/plugin/flash_spec.rb +33 -31
- data/spec/plugin/hooks_spec.rb +28 -0
- data/spec/plugin/sessions_spec.rb +4 -4
- data/spec/plugin/static_routing_spec.rb +20 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7fc4e03ddcb9b4e5fa37af6c0a151aef49965ef251d8ab177e7b530ec034529
|
4
|
+
data.tar.gz: 78ab54340363d9b963496e143809ca9f809c619d25f98faa08b08f9e3790ae19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56b9bb6e1e03cfa079457238d532d9223fdd6adef8438954dfd601afb68b384945b528c2606a3100881f3154e94024ae46f286364c41fce4c23af624a1ebcee3
|
7
|
+
data.tar.gz: 3856d03202576bd35465b29e975bb84fe23780695b87442c3ba8b09b7e36deb19cddf6a4ea5299249baf21c9e7b62b61e681221580b6d7a5af6b84858395d8a0
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
= 3.11.0 (2018-08-15)
|
2
|
+
|
3
|
+
* Disable default compression of sessions over 128 bytes in the sessions plugin (jeremyevans)
|
4
|
+
|
5
|
+
* Log but otherwise ignore exceptions raised by after processing of error handler response (jeremyevans)
|
6
|
+
|
7
|
+
* Modify internal before/after processing to avoid plugin load order issues (jeremyevans)
|
8
|
+
|
1
9
|
= 3.10.0 (2018-07-18)
|
2
10
|
|
3
11
|
* Remove flash key from session if new flash is empty when rotating flash (jeremyevans)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
= Improvements
|
2
|
+
|
3
|
+
* The order in which internal plugin before and after hooks are run
|
4
|
+
when multiple plugins are loaded is now fixed and does not depend
|
5
|
+
on the order in which the plugins are loaded. This can prevent
|
6
|
+
some issues in cases the plugins were not loaded in the order
|
7
|
+
previously recommended in the documentation.
|
8
|
+
|
9
|
+
Internal plugin before hooks are now run in the following order:
|
10
|
+
|
11
|
+
* hooks
|
12
|
+
* heartbeat
|
13
|
+
* static_routing
|
14
|
+
|
15
|
+
and internal plugin after hooks are now run in the following order:
|
16
|
+
|
17
|
+
* class_level_routing
|
18
|
+
* status_handler
|
19
|
+
* head
|
20
|
+
* flash
|
21
|
+
* session
|
22
|
+
* hooks
|
23
|
+
|
24
|
+
* Default compression of sessions over 128 bytes in length has been
|
25
|
+
disabled in the sessions plugin. Compression of sessions must now
|
26
|
+
be manually enabled if it is desired by setting :gzip_over to an
|
27
|
+
integer.
|
28
|
+
|
29
|
+
This change is being made to avoid possible compression ratio
|
30
|
+
attacks if both sensitive data and user-submitted data are stored in
|
31
|
+
the session. Such attacks were mitigated by the sessions plugin's
|
32
|
+
default use of padding after compression, and the JSON serialization
|
33
|
+
format used, but disabling compression avoids the possibility.
|
34
|
+
|
35
|
+
This does not affect backwards compatibility, as compressed sessions
|
36
|
+
will still be decompressed correctly, unless the size of the session
|
37
|
+
cookie when not using compression is over 4096 bytes.
|
38
|
+
|
39
|
+
= Backwards Compatibility
|
40
|
+
|
41
|
+
* When using the error_handler plugin, if routing raises an exception that
|
42
|
+
is handled by the error handler, but an exception is raised by a plugin
|
43
|
+
internal after hook after the error handler has been run, the exception
|
44
|
+
will be logged to the rack.errors entry in the environment, but it will
|
45
|
+
be otherwise ignored.
|
46
|
+
|
47
|
+
Exceptions raised inside the error handler will continue to be be raised
|
48
|
+
to the application's caller.
|
49
|
+
|
50
|
+
Additionally, the error_handler plugin no longers call before hooks
|
51
|
+
during error handling.
|
52
|
+
|
53
|
+
* A private Roda#_call method has been added. This could potentially
|
54
|
+
cause issues for applications that add their own _call method.
|
data/lib/roda.rb
CHANGED
@@ -239,6 +239,7 @@ class Roda
|
|
239
239
|
# Build the rack app to use
|
240
240
|
def build_rack_app
|
241
241
|
if block = @route_block
|
242
|
+
block = rack_app_route_block(block)
|
242
243
|
app = lambda{|env| new(env).call(&block)}
|
243
244
|
@middleware.reverse_each do |args, bl|
|
244
245
|
mid, *args = args
|
@@ -248,6 +249,12 @@ class Roda
|
|
248
249
|
@app = app
|
249
250
|
end
|
250
251
|
end
|
252
|
+
|
253
|
+
# The route block to use when building the rack app.
|
254
|
+
# Can be modified by plugins.
|
255
|
+
def rack_app_route_block(block)
|
256
|
+
block
|
257
|
+
end
|
251
258
|
end
|
252
259
|
|
253
260
|
# Instance methods for the Roda class.
|
@@ -277,6 +284,10 @@ class Roda
|
|
277
284
|
end
|
278
285
|
end
|
279
286
|
|
287
|
+
# Private alias for internal use
|
288
|
+
alias _call call
|
289
|
+
private :_call
|
290
|
+
|
280
291
|
# The environment hash for the current request. Example:
|
281
292
|
#
|
282
293
|
# env['REQUEST_METHOD'] # => 'GET'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# Internal after hook module, not for external use.
|
7
|
+
# Allows for plugins to configure the order in which
|
8
|
+
# after processing is done by using _roda_after_*
|
9
|
+
# private instance methods that are called in sorted order.
|
10
|
+
module AfterHook # :nodoc:
|
11
|
+
module ClassMethods
|
12
|
+
# Rebuild the _roda_after method whenever a plugin might
|
13
|
+
# have added a _roda_after_* method.
|
14
|
+
def include(*)
|
15
|
+
res = super
|
16
|
+
meths = private_instance_methods.grep(/\A_roda_after_\d\d\z/).sort.map{|s| "#{s}(res)"}.join(';')
|
17
|
+
class_eval("def _roda_after(res); #{meths} end", __FILE__, __LINE__)
|
18
|
+
private :_roda_after
|
19
|
+
res
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
# Run internal after hooks with the response
|
25
|
+
def call
|
26
|
+
res = super
|
27
|
+
ensure
|
28
|
+
_roda_after(res)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
register_plugin(:_after_hook, AfterHook)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# Internal before hook module, not for external use.
|
7
|
+
# Allows for plugins to configure the order in which
|
8
|
+
# before processing is done by using _roda_before_*
|
9
|
+
# private instance methods that are called in sorted order.
|
10
|
+
module BeforeHook # :nodoc:
|
11
|
+
# Rebuild the rack app if the rack app already exists,
|
12
|
+
# so the before hooks are setup inside the rack app
|
13
|
+
# route block.
|
14
|
+
def self.configure(app)
|
15
|
+
app.instance_exec do
|
16
|
+
build_rack_app if @app
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Rebuild the _roda_before method whenever a plugin might
|
22
|
+
# have added a _roda_before_* method.
|
23
|
+
def include(*a)
|
24
|
+
res = super
|
25
|
+
def_roda_before
|
26
|
+
res
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Build a _roda_before method that calls each _roda_before_* method
|
32
|
+
# in order.
|
33
|
+
def def_roda_before
|
34
|
+
meths = private_instance_methods.grep(/\A_roda_before_\d\d\z/).sort.join(';')
|
35
|
+
class_eval("def _roda_before; #{meths} end", __FILE__, __LINE__)
|
36
|
+
private :_roda_before
|
37
|
+
end
|
38
|
+
|
39
|
+
# Modify rack app route block to use before hook.
|
40
|
+
def rack_app_route_block(block)
|
41
|
+
lambda do |r|
|
42
|
+
_roda_before
|
43
|
+
instance_exec(r, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
register_plugin(:_before_hook, BeforeHook)
|
50
|
+
end
|
51
|
+
end
|
@@ -52,6 +52,10 @@ class Roda
|
|
52
52
|
# the normal +route+ class method to define your routing tree. This plugin does make it simpler to
|
53
53
|
# add additional routes after the routing tree has already been defined, though.
|
54
54
|
module ClassLevelRouting
|
55
|
+
def self.load_dependencies(app)
|
56
|
+
app.plugin :_after_hook
|
57
|
+
end
|
58
|
+
|
55
59
|
# Initialize the class_routes array when the plugin is loaded. Also, if the application doesn't
|
56
60
|
# currently have a routing block, setup an empty routing block so that things will still work if
|
57
61
|
# a routing block isn't added.
|
@@ -76,28 +80,29 @@ class Roda
|
|
76
80
|
end
|
77
81
|
|
78
82
|
module InstanceMethods
|
83
|
+
def initialize(_)
|
84
|
+
super
|
85
|
+
@_original_remaining_path = @_request.remaining_path
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
79
90
|
# If the normal routing tree doesn't handle an action, try each class level route
|
80
91
|
# to see if it matches.
|
81
|
-
def
|
82
|
-
|
83
|
-
rp = req.remaining_path
|
84
|
-
result = super
|
85
|
-
|
86
|
-
if result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
|
92
|
+
def _roda_after_10(result)
|
93
|
+
if result && result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
|
87
94
|
# Reset the response so it doesn't inherit the status or any headers from
|
88
95
|
# the original response.
|
89
96
|
@_response.send(:initialize)
|
90
|
-
|
97
|
+
result.replace(_call do |r|
|
91
98
|
opts[:class_level_routes].each do |meth, args, blk|
|
92
|
-
|
99
|
+
r.instance_variable_set(:@remaining_path, @_original_remaining_path)
|
93
100
|
r.public_send(meth, *args) do |*a|
|
94
101
|
instance_exec(*a, &blk)
|
95
102
|
end
|
96
103
|
end
|
97
104
|
nil
|
98
|
-
end
|
99
|
-
else
|
100
|
-
result
|
105
|
+
end)
|
101
106
|
end
|
102
107
|
end
|
103
108
|
end
|
@@ -28,7 +28,15 @@ class Roda
|
|
28
28
|
# default status set to 500, before executing the error handler.
|
29
29
|
# The error handler can change the response status if necessary,
|
30
30
|
# as well set headers and/or write to the body, just like a regular
|
31
|
-
# request.
|
31
|
+
# request. After the error handler returns a response, normal after
|
32
|
+
# processing of that response occurs, except that an exception during
|
33
|
+
# after processing is logged to <tt>env['rack.errors']</tt> but
|
34
|
+
# otherwise ignored. This avoids recursive calls into the
|
35
|
+
# error_handler. Note that if the error_handler itself raises
|
36
|
+
# an exception, the exception will be raised without normal after
|
37
|
+
# processing. This can cause some after processing to run twice
|
38
|
+
# (once before the error_handler is called and once after) if
|
39
|
+
# later after processing raises an exception.
|
32
40
|
#
|
33
41
|
# By default, this plugin handles StandardError and ScriptError.
|
34
42
|
# To override the exception classes it will handle, pass a :classes
|
@@ -36,6 +44,10 @@ class Roda
|
|
36
44
|
#
|
37
45
|
# plugin :error_handler, classes: [StandardError, ScriptError, NoMemoryError]
|
38
46
|
module ErrorHandler
|
47
|
+
def self.load_dependencies(app, *)
|
48
|
+
app.plugin :_after_hook
|
49
|
+
end
|
50
|
+
|
39
51
|
DEFAULT_ERROR_HANDLER_CLASSES = [StandardError, ScriptError].freeze
|
40
52
|
|
41
53
|
# If a block is given, automatically call the +error+ method on
|
@@ -66,7 +78,16 @@ class Roda
|
|
66
78
|
rescue *opts[:error_handler_classes] => e
|
67
79
|
@_response.send(:initialize)
|
68
80
|
@_response.status = 500
|
69
|
-
|
81
|
+
res = _call{handle_error(e)}
|
82
|
+
begin
|
83
|
+
_roda_after(res)
|
84
|
+
rescue => e2
|
85
|
+
if errors = env['rack.errors']
|
86
|
+
errors.puts "Error in after hook processing of error handler: #{e2.class}: #{e2.message}"
|
87
|
+
e2.backtrace.each{|line| errors.puts(line)}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
res
|
70
91
|
end
|
71
92
|
|
72
93
|
private
|
data/lib/roda/plugins/flash.rb
CHANGED
@@ -36,6 +36,10 @@ class Roda
|
|
36
36
|
# flash['a'] # = >'b'
|
37
37
|
# end
|
38
38
|
module Flash
|
39
|
+
def self.load_dependencies(app)
|
40
|
+
app.plugin :_after_hook
|
41
|
+
end
|
42
|
+
|
39
43
|
# Simple flash hash, where assiging to the hash updates the flash
|
40
44
|
# used in the following request.
|
41
45
|
class FlashHash < DelegateClass(Hash)
|
@@ -95,11 +99,11 @@ class Roda
|
|
95
99
|
@_flash ||= FlashHash.new(session['_flash'] || (session['_flash'] = session.delete(:_flash)))
|
96
100
|
end
|
97
101
|
|
102
|
+
private
|
103
|
+
|
98
104
|
# If the routing doesn't raise an error, rotate the flash
|
99
105
|
# hash in the session so the next request has access to it.
|
100
|
-
def
|
101
|
-
res = super
|
102
|
-
|
106
|
+
def _roda_after_40(_)
|
103
107
|
if f = @_flash
|
104
108
|
f = f.next
|
105
109
|
if f.empty?
|
@@ -108,8 +112,6 @@ class Roda
|
|
108
112
|
session['_flash'] = f
|
109
113
|
end
|
110
114
|
end
|
111
|
-
|
112
|
-
res
|
113
115
|
end
|
114
116
|
end
|
115
117
|
end
|
data/lib/roda/plugins/head.rb
CHANGED
@@ -37,6 +37,9 @@ class Roda
|
|
37
37
|
# this plugin those HEAD requests will return a 404 status, which
|
38
38
|
# may prevent search engines from crawling your website.
|
39
39
|
module Head
|
40
|
+
def self.load_dependencies(app)
|
41
|
+
app.plugin :_after_hook
|
42
|
+
end
|
40
43
|
|
41
44
|
# used to ensure proper resource release on HEAD requests
|
42
45
|
# we do not respond to a to_path method, here.
|
@@ -56,11 +59,12 @@ class Roda
|
|
56
59
|
end
|
57
60
|
|
58
61
|
module InstanceMethods
|
62
|
+
private
|
63
|
+
|
59
64
|
# Always use an empty response body for head requests, with a
|
60
65
|
# content length of 0.
|
61
|
-
def
|
62
|
-
res
|
63
|
-
if @_request.head?
|
66
|
+
def _roda_after_30(res)
|
67
|
+
if res && @_request.head?
|
64
68
|
body = res[2]
|
65
69
|
if body.respond_to?(:close)
|
66
70
|
res[2] = CloseLater.new(body)
|
@@ -68,7 +72,6 @@ class Roda
|
|
68
72
|
res[2] = EMPTY_ARRAY
|
69
73
|
end
|
70
74
|
end
|
71
|
-
res
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
@@ -16,20 +16,24 @@ class Roda
|
|
16
16
|
module Heartbeat
|
17
17
|
HEARTBEAT_RESPONSE = [200, {'Content-Type'=>'text/plain'}.freeze, ['OK'.freeze].freeze].freeze
|
18
18
|
|
19
|
+
def self.load_dependencies(app, opts=OPTS)
|
20
|
+
app.plugin :_before_hook
|
21
|
+
end
|
22
|
+
|
19
23
|
# Set the heartbeat path to the given path.
|
20
24
|
def self.configure(app, opts=OPTS)
|
21
25
|
app.opts[:heartbeat_path] = (opts[:path] || app.opts[:heartbeat_path] || "/heartbeat").dup.freeze
|
22
26
|
end
|
23
27
|
|
24
28
|
module InstanceMethods
|
29
|
+
private
|
30
|
+
|
25
31
|
# If the request is for a heartbeat path, return the heartbeat response.
|
26
|
-
def
|
32
|
+
def _roda_before_20
|
27
33
|
if env['PATH_INFO'] == opts[:heartbeat_path]
|
28
34
|
response = HEARTBEAT_RESPONSE.dup
|
29
35
|
response[1] = Hash[response[1]]
|
30
|
-
response
|
31
|
-
else
|
32
|
-
super
|
36
|
+
throw :halt, response
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
data/lib/roda/plugins/hooks.rb
CHANGED
@@ -30,8 +30,14 @@ class Roda
|
|
30
30
|
# Note that the after hook is called with the rack response array
|
31
31
|
# of status, headers, and body. If it wants to change the response,
|
32
32
|
# it must mutate this argument, calling <tt>response.status=</tt> inside
|
33
|
-
# an after block will not affect the returned status.
|
33
|
+
# an after block will not affect the returned status. Note that after
|
34
|
+
# hooks can be called with nil if an exception is raised during routing.
|
34
35
|
module Hooks
|
36
|
+
def self.load_dependencies(app)
|
37
|
+
app.plugin :_before_hook
|
38
|
+
app.plugin :_after_hook
|
39
|
+
end
|
40
|
+
|
35
41
|
def self.configure(app)
|
36
42
|
app.opts[:before_hook] ||= nil
|
37
43
|
app.opts[:after_hook] ||= nil
|
@@ -70,21 +76,21 @@ class Roda
|
|
70
76
|
end
|
71
77
|
|
72
78
|
module InstanceMethods
|
73
|
-
|
74
|
-
# execute the after hooks before returning.
|
75
|
-
def call(&block)
|
76
|
-
res = super do |r|
|
77
|
-
if b = opts[:before_hook]
|
78
|
-
instance_exec(&b)
|
79
|
-
end
|
79
|
+
private
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
ensure
|
81
|
+
# Run after hooks.
|
82
|
+
def _roda_after_90(res)
|
84
83
|
if b = opts[:after_hook]
|
85
84
|
instance_exec(res, &b)
|
86
85
|
end
|
87
86
|
end
|
87
|
+
|
88
|
+
# Run before hooks.
|
89
|
+
def _roda_before_10
|
90
|
+
if b = opts[:before_hook]
|
91
|
+
instance_exec(&b)
|
92
|
+
end
|
93
|
+
end
|
88
94
|
end
|
89
95
|
end
|
90
96
|
|
@@ -21,8 +21,7 @@ class Roda
|
|
21
21
|
# way to support sessions in Roda applications.
|
22
22
|
#
|
23
23
|
# The session cookies are encrypted with AES-256-CTR and then signed with HMAC-SHA-256.
|
24
|
-
# By default, session data
|
25
|
-
# is padded to reduce information leaked based on the session size.
|
24
|
+
# By default, session data is padded to reduce information leaked based on the session size.
|
26
25
|
#
|
27
26
|
# Sessions are serialized via JSON, so session information should only store data that
|
28
27
|
# allows roundtrips via JSON (String, Integer, Float, Array, Hash, true, false, and nil).
|
@@ -34,26 +33,20 @@ class Roda
|
|
34
33
|
# and does not convert all keys to strings.
|
35
34
|
#
|
36
35
|
# All sessions are timestamped and session expiration is enabled by default, with sessions
|
37
|
-
# being valid for 30 days maximum and 7 days since last use. Session creation time is
|
36
|
+
# being valid for 30 days maximum and 7 days since last use by default. Session creation time is
|
38
37
|
# reset whenever the session is empty when serialized and also whenever +clear_session+
|
39
38
|
# is called while processing the request.
|
40
39
|
#
|
41
|
-
# Session secrets can be rotated
|
42
|
-
# rotated at the same time. See options below.
|
40
|
+
# Session secrets can be rotated. See options below.
|
43
41
|
#
|
44
42
|
# The sessions plugin can transparently upgrade sessions from Rack::Session::Cookie
|
45
43
|
# if the default Rack::Session::Cookie coder and HMAC are used, see options below.
|
46
44
|
# It is recommended to only enable transparent upgrades for a brief transition period,
|
47
45
|
# and remove support for them once old sessions have converted or timed out.
|
48
46
|
#
|
49
|
-
#
|
50
|
-
# if the final cookie is too large (>=4096 bytes), a Roda::RodaPlugins::Sessions::CookieTooLarge
|
47
|
+
# If the final cookie is too large (>=4096 bytes), a Roda::RodaPlugins::Sessions::CookieTooLarge
|
51
48
|
# exception will be raised.
|
52
49
|
#
|
53
|
-
# If the flash plugin is used, the sessions plugin should be loaded after the flash
|
54
|
-
# plugin, so that the flash plugin rotates the flash in the session before the sessions
|
55
|
-
# plugin serializes the session.
|
56
|
-
#
|
57
50
|
# = Required Options
|
58
51
|
#
|
59
52
|
# The session cookies this plugin uses are both encrypted and signed, so two separate
|
@@ -70,7 +63,9 @@ class Roda
|
|
70
63
|
# that. If the +:secure+ option is not present in the hash, then
|
71
64
|
# <tt>secure: true</tt> is also set if the request is made over HTTPS. If this option is
|
72
65
|
# given, it will be merged into the default cookie options.
|
73
|
-
# :gzip_over :: For session data over this many bytes, compress it with the deflate algorithm (default:
|
66
|
+
# :gzip_over :: For session data over this many bytes, compress it with the deflate algorithm (default: nil,
|
67
|
+
# so never compress). Note that compression should not be enabled if you are storing data in
|
68
|
+
# the session derived from user input and also storing sensitive data in the session.
|
74
69
|
# :key :: The cookie name to use (default: <tt>'roda.session'</tt>)
|
75
70
|
# :max_seconds :: The maximum number of seconds to allow for total session lifetime, starting with when
|
76
71
|
# the session was originally created. Default is <tt>86400*30</tt> (30 days). Can be set to
|
@@ -140,7 +135,7 @@ class Roda
|
|
140
135
|
# deflate compression, this contains the deflate compressed data.
|
141
136
|
module Sessions
|
142
137
|
DEFAULT_COOKIE_OPTIONS = {:httponly=>true, :path=>'/'.freeze, :same_site=>:lax}.freeze
|
143
|
-
DEFAULT_OPTIONS = {:key => 'roda.session'.freeze, :max_seconds=>86400*30, :max_idle_seconds=>86400*7, :pad_size=>32, :gzip_over=>
|
138
|
+
DEFAULT_OPTIONS = {:key => 'roda.session'.freeze, :max_seconds=>86400*30, :max_idle_seconds=>86400*7, :pad_size=>32, :gzip_over=>nil, :skip_within=>3600}.freeze
|
144
139
|
DEFLATE_BIT = 0x1000
|
145
140
|
PADDING_MASK = 0x0fff
|
146
141
|
SESSION_CREATED_AT = 'roda.session.created_at'.freeze
|
@@ -153,6 +148,10 @@ class Roda
|
|
153
148
|
class CookieTooLarge < RodaError
|
154
149
|
end
|
155
150
|
|
151
|
+
def self.load_dependencies(app, opts=OPTS)
|
152
|
+
app.plugin :_after_hook
|
153
|
+
end
|
154
|
+
|
156
155
|
# Split given secret into a cipher secret and an hmac secret.
|
157
156
|
def self.split_secret(name, secret)
|
158
157
|
raise RodaError, "sessions plugin :#{name} option must be a String" unless secret.is_a?(String)
|
@@ -194,19 +193,6 @@ class Roda
|
|
194
193
|
end
|
195
194
|
|
196
195
|
module InstanceMethods
|
197
|
-
# If session information has been set in the request environment,
|
198
|
-
# update the rack response headers to set the session cookie in
|
199
|
-
# the response.
|
200
|
-
def call
|
201
|
-
res = super
|
202
|
-
|
203
|
-
if session = env['rack.session']
|
204
|
-
@_request.persist_session(res[1], session)
|
205
|
-
end
|
206
|
-
|
207
|
-
res
|
208
|
-
end
|
209
|
-
|
210
196
|
# Clear data from the session, and update the request environment
|
211
197
|
# so that the session cookie will use a new creation timestamp
|
212
198
|
# instead of the previous creation timestamp.
|
@@ -216,6 +202,17 @@ class Roda
|
|
216
202
|
env.delete(SESSION_UPDATED_AT)
|
217
203
|
nil
|
218
204
|
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# If session information has been set in the request environment,
|
209
|
+
# update the rack response headers to set the session cookie in
|
210
|
+
# the response.
|
211
|
+
def _roda_after_50(res)
|
212
|
+
if res && (session = env['rack.session'])
|
213
|
+
@_request.persist_session(res[1], session)
|
214
|
+
end
|
215
|
+
end
|
219
216
|
end
|
220
217
|
|
221
218
|
module RequestMethods
|
@@ -401,8 +398,9 @@ class Roda
|
|
401
398
|
|
402
399
|
bitmap = 0
|
403
400
|
json_length = json_data.bytesize
|
401
|
+
gzip_over = opts[:gzip_over]
|
404
402
|
|
405
|
-
if json_length >
|
403
|
+
if gzip_over && json_length > gzip_over
|
406
404
|
json_data = Zlib.deflate(json_data)
|
407
405
|
json_length = json_data.bytesize
|
408
406
|
bitmap |= DEFLATE_BIT
|
@@ -48,10 +48,11 @@ class Roda
|
|
48
48
|
# As shown above, you can use Roda's routing tree methods inside the
|
49
49
|
# static_route block to have shared behavior for different request methods,
|
50
50
|
# while still handling the request methods differently.
|
51
|
-
#
|
52
|
-
# Note that if you want to use the static_routing plugin and the hooks
|
53
|
-
# plugin at the same time, you should load the hooks plugin first.
|
54
51
|
module StaticRouting
|
52
|
+
def self.load_dependencies(app)
|
53
|
+
app.plugin :_before_hook
|
54
|
+
end
|
55
|
+
|
55
56
|
def self.configure(app)
|
56
57
|
app.opts[:static_routes] = {}
|
57
58
|
end
|
@@ -104,15 +105,14 @@ class Roda
|
|
104
105
|
end
|
105
106
|
|
106
107
|
module InstanceMethods
|
108
|
+
private
|
109
|
+
|
107
110
|
# If there is a static routing method for the given path, call it
|
108
111
|
# instead having the routing tree handle the request.
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
else
|
114
|
-
instance_exec(r, &block)
|
115
|
-
end
|
112
|
+
def _roda_before_30
|
113
|
+
r = @_request
|
114
|
+
if route = self.class.static_route_for(r.request_method, r.path_info)
|
115
|
+
r.static_route(&route)
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
@@ -23,6 +23,10 @@ class Roda
|
|
23
23
|
# cleared. So if you want to be sure the headers are set even in your block,
|
24
24
|
# you need to reset them in the block.
|
25
25
|
module StatusHandler
|
26
|
+
def self.load_dependencies(app)
|
27
|
+
app.plugin :_after_hook
|
28
|
+
end
|
29
|
+
|
26
30
|
def self.configure(app)
|
27
31
|
app.opts[:status_handler] ||= {}
|
28
32
|
end
|
@@ -41,15 +45,13 @@ class Roda
|
|
41
45
|
end
|
42
46
|
|
43
47
|
module InstanceMethods
|
44
|
-
|
45
|
-
def call
|
46
|
-
result = super
|
48
|
+
private
|
47
49
|
|
48
|
-
|
50
|
+
# If routing returns a response we have a handler for, call that handler.
|
51
|
+
def _roda_after_20(result)
|
52
|
+
if result && (block = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
|
49
53
|
@_response.headers.clear
|
50
|
-
|
51
|
-
else
|
52
|
-
result
|
54
|
+
result.replace(_call(&block))
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
data/lib/roda/version.rb
CHANGED
@@ -144,6 +144,28 @@ describe "error_handler plugin" do
|
|
144
144
|
proc{req}.must_raise(ArgumentError)
|
145
145
|
end
|
146
146
|
|
147
|
+
it "logs exceptions during after processing of error handler" do
|
148
|
+
app(:bare) do
|
149
|
+
plugin :error_handler do |e|
|
150
|
+
e.message * 2
|
151
|
+
end
|
152
|
+
plugin :hooks
|
153
|
+
|
154
|
+
after do
|
155
|
+
raise "foo"
|
156
|
+
end
|
157
|
+
|
158
|
+
route do |r|
|
159
|
+
''
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
errors = StringIO.new
|
164
|
+
body('rack.errors'=>errors).must_equal 'foofoo'
|
165
|
+
errors.rewind
|
166
|
+
errors.read.split("\n").first.must_equal "Error in after hook processing of error handler: RuntimeError: foo"
|
167
|
+
end
|
168
|
+
|
147
169
|
it "has access to current remaining_path" do
|
148
170
|
app(:bare) do
|
149
171
|
plugin :error_handler do |e|
|
data/spec/plugin/flash_spec.rb
CHANGED
@@ -3,47 +3,49 @@ require_relative "../spec_helper"
|
|
3
3
|
describe "flash plugin" do
|
4
4
|
include CookieJar
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
[lambda{send(*DEFAULT_SESSION_ARGS); plugin :flash},
|
7
|
+
lambda{plugin :flash; send(*DEFAULT_SESSION_ARGS)}].each do |config|
|
8
|
+
|
9
|
+
it "flash.now[] sets flash for current page" do
|
10
|
+
app(:bare) do
|
11
|
+
instance_exec(&config)
|
12
|
+
|
13
|
+
route do |r|
|
14
|
+
r.on do
|
15
|
+
flash.now['a'] = 'b'
|
16
|
+
flash['a']
|
17
|
+
end
|
15
18
|
end
|
16
19
|
end
|
17
|
-
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
+
body.must_equal 'b'
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
send(*DEFAULT_SESSION_ARGS)
|
24
|
+
it "flash[] sets flash for next page" do
|
25
|
+
app(:bare) do
|
26
|
+
instance_exec(&config)
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
route do |r|
|
29
|
+
r.get('a'){"c#{flash['a']}"}
|
30
|
+
r.get('f'){flash; session['_flash'].inspect}
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
flash['a'] = "b#{flash['a']}"
|
33
|
+
flash['a'] || ''
|
34
|
+
end
|
33
35
|
end
|
34
|
-
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
body.must_equal ''
|
38
|
+
body.must_equal 'b'
|
39
|
+
body.must_equal 'bb'
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
body('/a').must_equal 'cbbb'
|
42
|
+
body.must_equal ''
|
43
|
+
body.must_equal 'b'
|
44
|
+
body.must_equal 'bb'
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
body('/f').must_equal '{"a"=>"bbb"}'
|
47
|
+
body('/f').must_equal 'nil'
|
48
|
+
end
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
data/spec/plugin/hooks_spec.rb
CHANGED
@@ -100,4 +100,32 @@ describe "hooks plugin" do
|
|
100
100
|
end
|
101
101
|
status.must_equal 201
|
102
102
|
end
|
103
|
+
|
104
|
+
it "works with error plugin when loaded first" do
|
105
|
+
app.plugin(:error_handler){|e| "error"}
|
106
|
+
app.before do
|
107
|
+
raise "before" if @_request.path == '/b'
|
108
|
+
end
|
109
|
+
app.after do
|
110
|
+
raise "after" if @_request.path == '/a'
|
111
|
+
end
|
112
|
+
body('/a').must_equal "error"
|
113
|
+
body('/b').must_equal "error"
|
114
|
+
end
|
115
|
+
|
116
|
+
it "works with error plugin when loaded after" do
|
117
|
+
app(:bare) do
|
118
|
+
plugin(:error_handler){|e| "error"}
|
119
|
+
plugin :hooks
|
120
|
+
before do
|
121
|
+
raise "before" if @_request.path == '/b'
|
122
|
+
end
|
123
|
+
after do
|
124
|
+
raise "after" if @_request.path == '/a'
|
125
|
+
end
|
126
|
+
route{}
|
127
|
+
end
|
128
|
+
body('/a').must_equal "error"
|
129
|
+
body('/b').must_equal "error"
|
130
|
+
end
|
103
131
|
end
|
@@ -205,15 +205,15 @@ describe "sessions plugin" do
|
|
205
205
|
|
206
206
|
it "compresses data over a certain size by default" do
|
207
207
|
long = 'b'*8192
|
208
|
+
proc{body("/s/foo/#{long}")}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
|
209
|
+
|
210
|
+
@app.plugin(:sessions, :gzip_over=>8000)
|
208
211
|
body("/s/foo/#{long}").must_equal long
|
209
|
-
body("/g/foo").must_equal long
|
212
|
+
body("/g/foo", 'QUERY_STRING'=>'sut=3700').must_equal long
|
210
213
|
|
211
214
|
@app.plugin(:sessions, :gzip_over=>15000)
|
212
215
|
proc{body("/g/foo", 'QUERY_STRING'=>'sut=3700')}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
|
213
216
|
|
214
|
-
@app.plugin(:sessions, :gzip_over=>8000)
|
215
|
-
body("/g/foo", 'QUERY_STRING'=>'sut=3700').must_equal long
|
216
|
-
|
217
217
|
errors.must_equal []
|
218
218
|
end
|
219
219
|
|
@@ -66,6 +66,26 @@ describe "static_routing plugin" do
|
|
66
66
|
a.must_equal [1,3,2]
|
67
67
|
end
|
68
68
|
|
69
|
+
it "works with hooks plugin if loaded before" do
|
70
|
+
a = []
|
71
|
+
app(:bare) do
|
72
|
+
plugin :static_routing
|
73
|
+
plugin :hooks
|
74
|
+
|
75
|
+
before{a << 1}
|
76
|
+
after{a << 2}
|
77
|
+
|
78
|
+
static_route "/foo" do |r|
|
79
|
+
a << 3
|
80
|
+
"bar"
|
81
|
+
end
|
82
|
+
|
83
|
+
route{}
|
84
|
+
end
|
85
|
+
body('/foo').must_equal 'bar'
|
86
|
+
a.must_equal [1,3,2]
|
87
|
+
end
|
88
|
+
|
69
89
|
it "does not allow placeholders in static routes" do
|
70
90
|
app(:bare) do
|
71
91
|
plugin :static_routing
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -206,6 +206,7 @@ extra_rdoc_files:
|
|
206
206
|
- doc/release_notes/3.8.0.txt
|
207
207
|
- doc/release_notes/3.9.0.txt
|
208
208
|
- doc/release_notes/3.10.0.txt
|
209
|
+
- doc/release_notes/3.11.0.txt
|
209
210
|
files:
|
210
211
|
- CHANGELOG
|
211
212
|
- MIT-LICENSE
|
@@ -250,6 +251,7 @@ files:
|
|
250
251
|
- doc/release_notes/3.0.0.txt
|
251
252
|
- doc/release_notes/3.1.0.txt
|
252
253
|
- doc/release_notes/3.10.0.txt
|
254
|
+
- doc/release_notes/3.11.0.txt
|
253
255
|
- doc/release_notes/3.2.0.txt
|
254
256
|
- doc/release_notes/3.3.0.txt
|
255
257
|
- doc/release_notes/3.4.0.txt
|
@@ -259,6 +261,8 @@ files:
|
|
259
261
|
- doc/release_notes/3.8.0.txt
|
260
262
|
- doc/release_notes/3.9.0.txt
|
261
263
|
- lib/roda.rb
|
264
|
+
- lib/roda/plugins/_after_hook.rb
|
265
|
+
- lib/roda/plugins/_before_hook.rb
|
262
266
|
- lib/roda/plugins/_symbol_regexp_matchers.rb
|
263
267
|
- lib/roda/plugins/all_verbs.rb
|
264
268
|
- lib/roda/plugins/assets.rb
|