roda 3.106.0 → 3.108.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/plugins/_base64.rb +2 -2
  3. data/lib/roda/plugins/_optimized_matching.rb +1 -0
  4. data/lib/roda/plugins/all_verbs.rb +2 -2
  5. data/lib/roda/plugins/assets.rb +50 -6
  6. data/lib/roda/plugins/bearer_token.rb +2 -2
  7. data/lib/roda/plugins/break.rb +9 -1
  8. data/lib/roda/plugins/caching.rb +12 -12
  9. data/lib/roda/plugins/common_logger.rb +4 -4
  10. data/lib/roda/plugins/cookie_flags.rb +2 -2
  11. data/lib/roda/plugins/csrf.rb +3 -0
  12. data/lib/roda/plugins/delay_build.rb +2 -0
  13. data/lib/roda/plugins/each_part.rb +4 -4
  14. data/lib/roda/plugins/error_email.rb +2 -2
  15. data/lib/roda/plugins/exception_page.rb +2 -2
  16. data/lib/roda/plugins/h.rb +4 -4
  17. data/lib/roda/plugins/hash_branches.rb +8 -4
  18. data/lib/roda/plugins/hash_paths.rb +4 -2
  19. data/lib/roda/plugins/hash_public.rb +2 -2
  20. data/lib/roda/plugins/hmac_paths.rb +27 -29
  21. data/lib/roda/plugins/host_routing.rb +29 -4
  22. data/lib/roda/plugins/invalid_request_body.rb +2 -0
  23. data/lib/roda/plugins/json_parser.rb +20 -2
  24. data/lib/roda/plugins/mail_processor.rb +2 -2
  25. data/lib/roda/plugins/mailer.rb +4 -4
  26. data/lib/roda/plugins/middleware.rb +1 -0
  27. data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
  28. data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
  29. data/lib/roda/plugins/params_capturing.rb +16 -7
  30. data/lib/roda/plugins/params_capturing_restore.rb +72 -0
  31. data/lib/roda/plugins/part.rb +4 -4
  32. data/lib/roda/plugins/pass.rb +4 -1
  33. data/lib/roda/plugins/path.rb +6 -6
  34. data/lib/roda/plugins/path_matchers.rb +5 -1
  35. data/lib/roda/plugins/public.rb +4 -4
  36. data/lib/roda/plugins/render.rb +24 -12
  37. data/lib/roda/plugins/render_coverage.rb +7 -6
  38. data/lib/roda/plugins/render_each.rb +2 -2
  39. data/lib/roda/plugins/route_block_args.rb +6 -0
  40. data/lib/roda/plugins/route_csrf.rb +35 -3
  41. data/lib/roda/plugins/run_append_slash.rb +8 -4
  42. data/lib/roda/plugins/sec_fetch_site_csrf.rb +5 -2
  43. data/lib/roda/plugins/sessions.rb +9 -4
  44. data/lib/roda/plugins/shape_friendly.rb +4 -4
  45. data/lib/roda/plugins/static.rb +14 -0
  46. data/lib/roda/plugins/type_routing.rb +1 -1
  47. data/lib/roda/plugins/url_escape.rb +39 -0
  48. data/lib/roda/plugins/view_options.rb +3 -5
  49. data/lib/roda/plugins.rb +4 -4
  50. data/lib/roda/request.rb +27 -10
  51. data/lib/roda/version.rb +1 -1
  52. data/lib/roda.rb +17 -12
  53. metadata +4 -2
@@ -22,8 +22,11 @@ class Roda
22
22
  # # GET /a/ => App gets "/" as PATH_INFO
23
23
  module RunAppendSlash
24
24
  # Set plugin specific options. Options:
25
- # :use_redirects :: Whether to issue 302 redirects when appending the
26
- # trailing slash.
25
+ # :use_redirects :: Whether to issue a redirect to the path with the slash
26
+ # appended, instead of calling the app directly with the
27
+ # trailing slash. Note that if the current path starts
28
+ # with +//+, it will call the app directly instead of
29
+ # redirecting.
27
30
  def self.configure(app, opts=OPTS)
28
31
  app.opts[:run_append_slash_redirect] = !!opts[:use_redirects]
29
32
  end
@@ -32,10 +35,11 @@ class Roda
32
35
  # Calls the given rack app. If the path matches the root of the app but
33
36
  # does not contain a trailing slash, a trailing slash is appended to the
34
37
  # path internally, or a redirect is issued when configured with
35
- # <tt>use_redirects: true</tt>.
38
+ # <tt>use_redirects: true</tt>. If the path starts with +//+,
39
+ # the redirect approach will not be used.
36
40
  def run(*)
37
41
  if @remaining_path.empty?
38
- if scope.opts[:run_append_slash_redirect]
42
+ if scope.opts[:run_append_slash_redirect] && !(path = self.path).start_with?('//')
39
43
  redirect("#{path}/")
40
44
  else
41
45
  @remaining_path += '/'
@@ -54,7 +54,7 @@ class Roda
54
54
  # :raise :: raise a Roda::RodaPlugins::SecFetchSiteCsrf::CsrfFailure
55
55
  # exception
56
56
  # :empty_403 :: return a blank 403 page
57
- # :clear_session :: clear the current session
57
+ # :clear_session :: clear the current session (deprecated)
58
58
  #
59
59
  # The plugin also supports a block, in which case failures will call the block
60
60
  # as a routing block (the block should accept the request object).
@@ -83,8 +83,10 @@ class Roda
83
83
  end
84
84
 
85
85
  case options[:csrf_failure]
86
- when :raise, :empty_403, :clear_session, :method
86
+ when :raise, :empty_403, :method
87
87
  # nothing
88
+ when :clear_session
89
+ RodaPlugins.warn "Passing :clear_session as the :csrf_failure option to the sec_fetch_site_csrf plugin is deprecated"
88
90
  else
89
91
  raise RodaError, "Unsupported :csrf_failure plugin option: #{options[:csrf_failure].inspect}"
90
92
  end
@@ -118,6 +120,7 @@ class Roda
118
120
  headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
119
121
  throw :halt, @_response.finish_with_body([])
120
122
  when :clear_session
123
+ # RODA4: Remove
121
124
  session.clear
122
125
  else # when :method
123
126
  @_request.on{_roda_sec_fetch_site_csrf_failure(@_request)}
@@ -5,9 +5,9 @@ require 'openssl'
5
5
  begin
6
6
  OpenSSL::Cipher.new("aes-256-ctr")
7
7
  rescue OpenSSL::Cipher::CipherError
8
- # :nocov:
8
+ # simplecov:disable
9
9
  raise LoadError, "Roda sessions plugin requires the aes-256-ctr cipher"
10
- # :nocov:
10
+ # simplecov:enable
11
11
  end
12
12
 
13
13
  require 'json'
@@ -67,7 +67,7 @@ class Roda
67
67
  # given, it will be merged into the default cookie options.
68
68
  # :env_key :: The key in `env` where the session should be located. Defaults to <tt>"rack.session"</tt>, the
69
69
  # default key for sessions in Rack.
70
- # :gzip_over :: For session data over this many bytes, compress it with the deflate algorithm (default: nil,
70
+ # :gzip_over (deprecated) :: For session data over this many bytes, compress it with the deflate algorithm (default: nil,
71
71
  # so never compress). Note that compression should not be enabled if you are storing data in
72
72
  # the session derived from user input and also storing sensitive data in the session.
73
73
  # :key :: The cookie name to use (default: <tt>'roda.session'</tt>)
@@ -94,7 +94,7 @@ class Roda
94
94
  # (default: 3600).
95
95
  # :upgrade_from_rack_session_cookie_key :: The cookie name to use for transparently upgrading from
96
96
  # Rack::Session:Cookie (defaults to <tt>'rack.session'</tt>).
97
- # :upgrade_from_rack_session_cookie_secret :: The secret for the HMAC-SHA1 signature when allowing
97
+ # :upgrade_from_rack_session_cookie_secret (deprecated) :: The secret for the HMAC-SHA1 signature when allowing
98
98
  # transparent upgrades from Rack::Session::Cookie. Using this
99
99
  # option is only recommended during a short transition period,
100
100
  # and is not enabled by default as it lowers security.
@@ -189,12 +189,17 @@ class Roda
189
189
  opts[:session_version_num] = opts[:per_cookie_cipher_secret] ? 1 : 0
190
190
 
191
191
  if opts[:upgrade_from_rack_session_cookie_secret]
192
+ RodaPlugins.warn("Support for upgrading rack session cookie sessions will be removed in Roda 4.")
192
193
  opts[:upgrade_from_rack_session_cookie_key] ||= 'rack.session'
193
194
  rsco = opts[:upgrade_from_rack_session_cookie_options] = Hash[opts[:upgrade_from_rack_session_cookie_options] || OPTS]
194
195
  rsco[:path] ||= co[:path]
195
196
  rsco[:domain] ||= co[:domain]
196
197
  end
197
198
 
199
+ if opts[:gzip_over]
200
+ RodaPlugins.warn("Support for compressing session data will be removed in Roda 4.")
201
+ end
202
+
198
203
  opts[:cipher_secret], opts[:hmac_secret] = split_secret(:secret, opts[:secret])
199
204
  opts[:old_cipher_secret], opts[:old_hmac_secret] = (split_secret(:old_secret, opts[:old_secret]) if opts[:old_secret])
200
205
 
@@ -90,9 +90,9 @@ class Roda
90
90
  end
91
91
  nil
92
92
  end
93
- # :nocov:
93
+ # simplecov:disable
94
94
  ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
95
- # :nocov:
95
+ # simplecov:enable
96
96
 
97
97
  private
98
98
 
@@ -132,9 +132,9 @@ class Roda
132
132
  end
133
133
  end
134
134
 
135
- # :nocov:
135
+ # simplecov:disable
136
136
  if RUBY_VERSION >= '4.0'
137
- # :nocov:
137
+ # simplecov:enable
138
138
  module InstanceMethods
139
139
  private
140
140
 
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ # RODA4: Remove plugin
4
+
3
5
  require 'rack/static'
4
6
 
5
7
  #
@@ -19,6 +21,9 @@ class Roda
19
21
  # uses a separate option for it.
20
22
  #
21
23
  # Users of this plugin may want to consider using the public plugin instead.
24
+ # As this only loads a middleware, it does not integrate with other Roda
25
+ # security features, such as the content_security_policy and host_authorization
26
+ # plugins.
22
27
  #
23
28
  # Examples:
24
29
  #
@@ -26,6 +31,15 @@ class Roda
26
31
  # plugin :static, ['/js', '/css'] # path: /path/to/app/public
27
32
  # plugin :static, ['/images'], root: 'pub' # path: /path/to/app/pub
28
33
  # plugin :static, ['/media'], root: '/path/to/public' # path: /path/to/public
34
+ #
35
+ # This plugin will no longer ship with Roda starting in Roda 4. As the plugin only
36
+ # configures a middleware, you should switch to doing so directly. Translating
37
+ # the above example to direct middleware usage:
38
+ #
39
+ # require 'rack/static'
40
+ # use Rack::Static, urls: ['/js', '/css'], root: '/path/to/app/public'
41
+ # use Rack::Static, urls: ['/images'], root: '/path/to/app/pub'
42
+ # use Rack::Static, urls: ['/media'], root: '/path/to/public'
29
43
  module Static
30
44
  # Load the Rack::Static middleware. Use the paths given as the :urls option,
31
45
  # and set the :root option to be relative to the application's :root option.
@@ -198,11 +198,11 @@ class Roda
198
198
  # The response type indicated by the Accept request header.
199
199
  def accept_response_type
200
200
  mimes = @scope.opts[:type_routing][:mimes]
201
+ response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
201
202
 
202
203
  @env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
203
204
  mime, _= part.split(/\s*;\s*/, 2)
204
205
  if sym = mimes[mime]
205
- response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
206
206
  return sym
207
207
  end
208
208
  end
@@ -0,0 +1,39 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/utils'
4
+
5
+ #
6
+ class Roda
7
+ module RodaPlugins
8
+ # The url_escape plugin add url_escape and url_unescape methods
9
+ # to the route block scope.
10
+ #
11
+ # plugin :url_escape
12
+ #
13
+ # route do |r|
14
+ # link = "/path/q=#{url_escape(r.params["some_param"])}"
15
+ # response_body(link)
16
+ # end
17
+ module UrlEscape
18
+ module InstanceMethods
19
+ if RUBY_VERSION >= '2'
20
+ define_method(:url_escape, Rack::Utils.instance_method(:escape))
21
+ define_method(:url_unescape, Rack::Utils.instance_method(:unescape))
22
+ # simplecov:disable
23
+ else
24
+ def url_escape(v)
25
+ Rack::Utils.escape(v)
26
+ end
27
+
28
+ def url_unescape(v)
29
+ Rack::Utils.unescape(v)
30
+ end
31
+ end
32
+ # simplecov:enable
33
+ end
34
+ end
35
+
36
+ register_plugin(:url_escape, UrlEscape)
37
+ end
38
+ end
39
+
@@ -158,13 +158,11 @@ class Roda
158
158
  # template isn't a layout template, merge the options
159
159
  # and locals into the returned hash.
160
160
  def parse_template_opts(template, opts)
161
- t_opts = super
162
-
163
- if !t_opts[:_is_layout] && (v_opts = @_view_options)
164
- t_opts.merge!(v_opts)
161
+ if (v_opts = @_view_options) && (!template.is_a?(Hash) || !template[:_is_layout])
162
+ opts = v_opts.merge(opts)
165
163
  end
166
164
 
167
- t_opts
165
+ super
168
166
  end
169
167
 
170
168
  # If layout options or locals have been set,
data/lib/roda/plugins.rb CHANGED
@@ -43,11 +43,11 @@ class Roda
43
43
  # Deprecate the constant with the given name in the given module,
44
44
  # if the ruby version supports it.
45
45
  def self.deprecate_constant(mod, name)
46
- # :nocov:
46
+ # simplecov:disable
47
47
  if RUBY_VERSION >= '2.3'
48
48
  mod.deprecate_constant(name)
49
49
  end
50
- # :nocov:
50
+ # simplecov:enable
51
51
  end
52
52
 
53
53
  if RUBY_VERSION >= '3.3'
@@ -57,12 +57,12 @@ class Roda
57
57
  mod.set_temporary_name(yield)
58
58
  mod
59
59
  end
60
- # :nocov:
60
+ # simplecov:disable
61
61
  else
62
62
  def self.set_temp_name(mod)
63
63
  mod
64
64
  end
65
65
  end
66
- # :nocov:
66
+ # simplecov:enable
67
67
  end
68
68
  end
data/lib/roda/request.rb CHANGED
@@ -437,8 +437,15 @@ class Roda
437
437
 
438
438
  # Match the given hash if all hash matchers match.
439
439
  def _match_hash(hash)
440
+ rp = @remaining_path
441
+ captures_len = @captures.length
442
+
440
443
  # Allow calling private methods, as match methods are generally private
441
- hash.all?{|k,v| send("match_#{k}", v)}
444
+ return true if hash.all?{|k,v| send("match_#{k}", v)}
445
+
446
+ @remaining_path = rp
447
+ @captures.slice!(captures_len, 10000000)
448
+ false
442
449
  end
443
450
 
444
451
  # Match integer segment of up to 100 decimal characters, and yield resulting value as an
@@ -510,6 +517,7 @@ class Roda
510
517
  rp = @remaining_path
511
518
  if rp.getbyte(0) == 47
512
519
  if last = rp.index('/', 1)
520
+ return false if last == 1
513
521
  @captures << rp[1, last-1]
514
522
  @remaining_path = rp[last, rp.length]
515
523
  elsif (len = rp.length) > 1
@@ -568,11 +576,12 @@ class Roda
568
576
 
569
577
  if meth
570
578
  return unless captures = scope.send(meth, *captures)
571
- # :nocov:
579
+ # simplecov:disable
580
+ # RODA4: Remove elsif block
572
581
  elsif defined?(yield)
573
- # RODA4: Remove
582
+ RodaPlugins.warn("Passing a block to RodaRequest#consume will be ignored in Roda 4. Update the code to pass a scope method symbol as the second argument.")
574
583
  return unless captures = yield(*captures)
575
- # :nocov:
584
+ # simplecov:enable
576
585
  end
577
586
 
578
587
  @remaining_path = matchdata.post_match
@@ -593,13 +602,13 @@ class Roda
593
602
  if regexp.match?(rp)
594
603
  if last = rp.index('/', 1)
595
604
  val = rp[1, last-1]
596
- @remaining_path = rp[last, rp.length]
605
+ new_rp = rp[last, rp.length]
597
606
  else
598
607
  val = rp[1, rp.length]
599
- @remaining_path = ""
608
+ new_rp = ""
600
609
  end
601
610
 
602
- if meth
611
+ ret = if meth
603
612
  if captures = scope.send(meth, val)
604
613
  if captures.is_a?(Array)
605
614
  @captures.concat(captures)
@@ -610,12 +619,16 @@ class Roda
610
619
  else
611
620
  @captures << val
612
621
  end
622
+
623
+ @remaining_path = new_rp if ret
624
+
625
+ ret
613
626
  end
614
627
  end
615
- # :nocov:
628
+ # simplecov:disable
616
629
  else
617
630
  alias _consume_single_segment consume
618
- # :nocov:
631
+ # simplecov:enable
619
632
  end
620
633
 
621
634
  # The default path to use for redirects when a path is not given.
@@ -626,9 +639,13 @@ class Roda
626
639
  #
627
640
  # If the current request is a GET request, raise an error, as otherwise
628
641
  # it is easy to create an infinite redirect.
642
+ #
643
+ # If the path starts +//+, then this redirects to the root path
644
+ # (+/+) instead of using a protocol-relative redirect.
629
645
  def default_redirect_path
630
646
  raise RodaError, "must provide path argument to redirect for get requests" if is_get?
631
- path
647
+ path = self.path
648
+ path.start_with?('//') ? '/' : path
632
649
  end
633
650
 
634
651
  # The default status to use for redirects if a status is not provided,
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 106
7
+ RodaMinorVersion = 108
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
data/lib/roda.rb CHANGED
@@ -71,29 +71,34 @@ class Roda
71
71
  #
72
72
  # If the :check_arity app option is not set to false, Roda will check that
73
73
  # the arity of the block matches the expected arity, and compensate for
74
- # cases where it does not. If it is set to :warn, Roda will warn in the
75
- # cases where the arity does not match what is expected.
74
+ # cases where it does not. The :check_arity default app option setting is
75
+ # :warn, in which case Roda will warn in the cases where the arity does not
76
+ # match what is expected.
76
77
  #
77
78
  # If the expected arity is :any, Roda must perform a dynamic arity check
78
79
  # when the method is called, which can hurt performance even in the case
79
80
  # where the arity matches. The :check_dynamic_arity app option can be
80
81
  # set to false to turn off the dynamic arity checks. The
81
82
  # :check_dynamic_arity app option can be to :warn to warn if Roda needs
82
- # to adjust arity dynamically.
83
+ # to adjust arity dynamically. The default value of the :check_dynamic_arity
84
+ # app option is to use the same value as the :check_arity app option.
83
85
  #
84
86
  # Roda only checks arity for regular blocks, not lambda blocks, as the
85
87
  # fixes Roda uses for regular blocks would not work for lambda blocks.
86
88
  #
87
89
  # Roda does not support blocks with required keyword arguments if the
88
90
  # expected arity is 0 or 1.
91
+ #
92
+ # Support for the :check_arity and :check_dynamic_arity options will be
93
+ # removed in Roda 4.
89
94
  def define_roda_method(meth, expected_arity, &block)
90
95
  if meth.is_a?(String)
91
96
  meth = roda_method_name(meth)
92
97
  end
93
98
  call_meth = meth
94
99
 
95
- # RODA4: Switch to false # :warn in last Roda 3 version
96
- if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
100
+ # RODA4: Remove support for :check_arity, default to false behavior
101
+ if (check_arity = opts.fetch(:check_arity, :warn)) && !block.lambda?
97
102
  required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
98
103
 
99
104
  if keyword == :required && (expected_arity == 0 || expected_arity == 1)
@@ -137,9 +142,9 @@ class Roda
137
142
  block = if RUBY_VERSION >= '2.7'
138
143
  eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
139
144
  else
140
- # :nocov:
145
+ # simplecov:disable
141
146
  lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
142
- # :nocov:
147
+ # simplecov:enable
143
148
  end
144
149
  else
145
150
  arity_meth = meth
@@ -221,7 +226,7 @@ class Roda
221
226
  plugin :direct_call
222
227
  end
223
228
 
224
- if RUBY_VERSION > "2.4" && ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
229
+ if RUBY_VERSION > "2.4" && ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class, :always]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
225
230
  plugin :_optimized_matching
226
231
  end
227
232
  end
@@ -307,9 +312,9 @@ class Roda
307
312
  plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
308
313
  @app = nil
309
314
  end
310
- # :nocov:
315
+ # simplecov:disable
311
316
  ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
312
- # :nocov:
317
+ # simplecov:enable
313
318
 
314
319
  # Setup routing tree for the current Roda application, and build the
315
320
  # underlying rack application using the stored middleware. Requires
@@ -345,9 +350,9 @@ class Roda
345
350
  @middleware << [args, block].freeze
346
351
  @app = nil
347
352
  end
348
- # :nocov:
353
+ # simplecov:disable
349
354
  ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
350
- # :nocov:
355
+ # simplecov:enable
351
356
 
352
357
  private
353
358
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.106.0
4
+ version: 3.108.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -259,6 +259,7 @@ files:
259
259
  - lib/roda/plugins/padrino_render.rb
260
260
  - lib/roda/plugins/param_matchers.rb
261
261
  - lib/roda/plugins/params_capturing.rb
262
+ - lib/roda/plugins/params_capturing_restore.rb
262
263
  - lib/roda/plugins/part.rb
263
264
  - lib/roda/plugins/partials.rb
264
265
  - lib/roda/plugins/pass.rb
@@ -310,6 +311,7 @@ files:
310
311
  - lib/roda/plugins/typecast_params.rb
311
312
  - lib/roda/plugins/typecast_params_sized_integers.rb
312
313
  - lib/roda/plugins/unescape_path.rb
314
+ - lib/roda/plugins/url_escape.rb
313
315
  - lib/roda/plugins/view_options.rb
314
316
  - lib/roda/plugins/view_subdir_leading_slash.rb
315
317
  - lib/roda/request.rb
@@ -339,7 +341,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
339
341
  - !ruby/object:Gem::Version
340
342
  version: '0'
341
343
  requirements: []
342
- rubygems_version: 4.0.10
344
+ rubygems_version: 4.0.16
343
345
  specification_version: 4
344
346
  summary: Routing tree web toolkit
345
347
  test_files: []