roda 3.107.0 → 3.109.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/opts.rb +22 -0
  3. data/lib/roda/plugins/Integer_matcher_max.rb +1 -1
  4. data/lib/roda/plugins/_add_script_name.rb +14 -0
  5. data/lib/roda/plugins/_base64.rb +2 -2
  6. data/lib/roda/plugins/_json.rb +22 -0
  7. data/lib/roda/plugins/_optimized_matching.rb +1 -0
  8. data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
  9. data/lib/roda/plugins/additional_render_engines.rb +5 -1
  10. data/lib/roda/plugins/additional_view_directories.rb +5 -1
  11. data/lib/roda/plugins/all_verbs.rb +2 -2
  12. data/lib/roda/plugins/assets.rb +11 -14
  13. data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
  14. data/lib/roda/plugins/autoload_named_routes.rb +9 -3
  15. data/lib/roda/plugins/bearer_token.rb +2 -2
  16. data/lib/roda/plugins/break.rb +9 -1
  17. data/lib/roda/plugins/caching.rb +12 -12
  18. data/lib/roda/plugins/capture_erb.rb +5 -1
  19. data/lib/roda/plugins/chunked.rb +11 -5
  20. data/lib/roda/plugins/class_level_routing.rb +5 -3
  21. data/lib/roda/plugins/class_matchers.rb +3 -1
  22. data/lib/roda/plugins/common_logger.rb +11 -7
  23. data/lib/roda/plugins/conditional_sessions.rb +1 -1
  24. data/lib/roda/plugins/content_for.rb +5 -1
  25. data/lib/roda/plugins/content_security_policy.rb +6 -2
  26. data/lib/roda/plugins/cookie_flags.rb +8 -4
  27. data/lib/roda/plugins/cookies.rb +7 -3
  28. data/lib/roda/plugins/custom_block_results.rb +5 -3
  29. data/lib/roda/plugins/custom_matchers.rb +4 -2
  30. data/lib/roda/plugins/default_headers.rb +2 -5
  31. data/lib/roda/plugins/default_status.rb +1 -0
  32. data/lib/roda/plugins/each_part.rb +23 -27
  33. data/lib/roda/plugins/environments.rb +3 -7
  34. data/lib/roda/plugins/error_email.rb +11 -7
  35. data/lib/roda/plugins/error_handler.rb +5 -3
  36. data/lib/roda/plugins/error_mail.rb +6 -2
  37. data/lib/roda/plugins/exception_page.rb +2 -2
  38. data/lib/roda/plugins/h.rb +4 -4
  39. data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
  40. data/lib/roda/plugins/hash_branches.rb +15 -9
  41. data/lib/roda/plugins/hash_paths.rb +11 -7
  42. data/lib/roda/plugins/hash_public.rb +13 -9
  43. data/lib/roda/plugins/hash_public_cache.rb +9 -7
  44. data/lib/roda/plugins/hash_routes.rb +5 -3
  45. data/lib/roda/plugins/heartbeat.rb +6 -2
  46. data/lib/roda/plugins/hmac_paths.rb +36 -32
  47. data/lib/roda/plugins/hooks.rb +13 -10
  48. data/lib/roda/plugins/host_authorization.rb +7 -2
  49. data/lib/roda/plugins/host_routing.rb +36 -6
  50. data/lib/roda/plugins/invalid_request_body.rb +22 -4
  51. data/lib/roda/plugins/ip_from_header.rb +5 -1
  52. data/lib/roda/plugins/json.rb +10 -8
  53. data/lib/roda/plugins/json_parser.rb +43 -11
  54. data/lib/roda/plugins/mail_processor.rb +11 -8
  55. data/lib/roda/plugins/mailer.rb +9 -7
  56. data/lib/roda/plugins/match_affix.rb +6 -1
  57. data/lib/roda/plugins/match_hook_args.rb +7 -5
  58. data/lib/roda/plugins/middleware.rb +13 -7
  59. data/lib/roda/plugins/module_include.rb +1 -1
  60. data/lib/roda/plugins/multi_public.rb +6 -2
  61. data/lib/roda/plugins/multi_route.rb +1 -1
  62. data/lib/roda/plugins/multi_run.rb +13 -17
  63. data/lib/roda/plugins/named_routes.rb +8 -6
  64. data/lib/roda/plugins/named_templates.rb +5 -3
  65. data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
  66. data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
  67. data/lib/roda/plugins/params_capturing.rb +16 -7
  68. data/lib/roda/plugins/params_capturing_restore.rb +72 -0
  69. data/lib/roda/plugins/part.rb +4 -4
  70. data/lib/roda/plugins/pass.rb +4 -1
  71. data/lib/roda/plugins/path.rb +22 -20
  72. data/lib/roda/plugins/path_matchers.rb +5 -1
  73. data/lib/roda/plugins/path_rewriter.rb +8 -5
  74. data/lib/roda/plugins/permissions_policy.rb +7 -3
  75. data/lib/roda/plugins/public.rb +15 -14
  76. data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
  77. data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
  78. data/lib/roda/plugins/render.rb +58 -61
  79. data/lib/roda/plugins/render_coverage.rb +12 -8
  80. data/lib/roda/plugins/render_each.rb +2 -2
  81. data/lib/roda/plugins/render_locals.rb +12 -6
  82. data/lib/roda/plugins/request_aref.rb +1 -1
  83. data/lib/roda/plugins/response_content_type.rb +7 -3
  84. data/lib/roda/plugins/route_csrf.rb +42 -5
  85. data/lib/roda/plugins/run_append_slash.rb +12 -4
  86. data/lib/roda/plugins/sec_fetch_site_csrf.rb +11 -4
  87. data/lib/roda/plugins/sessions.rb +17 -13
  88. data/lib/roda/plugins/shape_friendly.rb +7 -5
  89. data/lib/roda/plugins/sinatra_helpers.rb +7 -3
  90. data/lib/roda/plugins/static.rb +3 -0
  91. data/lib/roda/plugins/status_handler.rb +5 -3
  92. data/lib/roda/plugins/strip_path_prefix.rb +4 -2
  93. data/lib/roda/plugins/symbol_matchers.rb +3 -1
  94. data/lib/roda/plugins/symbol_views.rb +1 -1
  95. data/lib/roda/plugins/timestamp_public.rb +8 -4
  96. data/lib/roda/plugins/type_routing.rb +10 -6
  97. data/lib/roda/plugins/typecast_params.rb +2 -2
  98. data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
  99. data/lib/roda/plugins/url_escape.rb +2 -14
  100. data/lib/roda/plugins/view_options.rb +3 -5
  101. data/lib/roda/plugins.rb +14 -4
  102. data/lib/roda/request.rb +25 -9
  103. data/lib/roda/version.rb +1 -1
  104. data/lib/roda.rb +32 -21
  105. metadata +7 -3
@@ -1122,7 +1122,7 @@ class Roda
1122
1122
  # before processing them.
1123
1123
  def self.configure(app, opts=OPTS, &block)
1124
1124
  app.const_set(:TypecastParams, Class.new(RodaPlugins::TypecastParams::Params)) unless app.const_defined?(:TypecastParams)
1125
- app::TypecastParams.class_eval(&block) if block
1125
+ app::TypecastParams.class_eval(&block) if block # RODA4: use class_exec
1126
1126
  if opts[:strip] == :all
1127
1127
  app::TypecastParams.send(:include, StringStripper)
1128
1128
  end
@@ -1133,7 +1133,7 @@ class Roda
1133
1133
  app::TypecastParams.send(:include, SkipBytesizeChecking)
1134
1134
  end
1135
1135
  if opts[:date_parse_input_handler]
1136
- app::TypecastParams.class_eval do
1136
+ app::TypecastParams.class_exec do
1137
1137
  include DateParseInputHandler
1138
1138
  define_method(:handle_date_parse_input, &opts[:date_parse_input_handler])
1139
1139
  private :handle_date_parse_input
@@ -90,7 +90,7 @@ class Roda
90
90
  end
91
91
 
92
92
  if default = opts[:default_size]
93
- app::TypecastParams.class_eval do
93
+ app::TypecastParams.class_exec do
94
94
  meths = ['', 'convert_', '_convert_array_', '_max_input_bytesize_for_', '_invalid_value_message_for_']
95
95
  %w[int uint pos_int pos_uint Integer Integeru].each do |type|
96
96
  meths.each do |prefix|
@@ -16,20 +16,8 @@ class Roda
16
16
  # end
17
17
  module UrlEscape
18
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
- # :nocov:
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
- # :nocov:
19
+ define_method(:url_escape, Rack::Utils.instance_method(:escape))
20
+ define_method(:url_unescape, Rack::Utils.instance_method(:unescape))
33
21
  end
34
22
  end
35
23
 
@@ -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
@@ -32,6 +32,16 @@ class Roda
32
32
  plugin
33
33
  end
34
34
 
35
+ # Define an attr_reader method for name that uses the instance variable
36
+ # <tt>@opt_*</tt>. Designed to be used with instance variables set via RodaOpts#[]=.
37
+ def self.opt_attr_reader(mod, key, name: key)
38
+ opt_name = :"opt_#{key}"
39
+ mod.send(:attr_reader, opt_name)
40
+ mod.send(:alias_method, name, opt_name)
41
+ mod.send(:remove_method, opt_name)
42
+ nil
43
+ end
44
+
35
45
  # Register the given plugin with Roda, so that it can be loaded using #plugin
36
46
  # with a symbol. Should be used by plugin files. Example:
37
47
  #
@@ -43,11 +53,11 @@ class Roda
43
53
  # Deprecate the constant with the given name in the given module,
44
54
  # if the ruby version supports it.
45
55
  def self.deprecate_constant(mod, name)
46
- # :nocov:
56
+ # simplecov:disable
47
57
  if RUBY_VERSION >= '2.3'
48
58
  mod.deprecate_constant(name)
49
59
  end
50
- # :nocov:
60
+ # simplecov:enable
51
61
  end
52
62
 
53
63
  if RUBY_VERSION >= '3.3'
@@ -57,12 +67,12 @@ class Roda
57
67
  mod.set_temporary_name(yield)
58
68
  mod
59
69
  end
60
- # :nocov:
70
+ # simplecov:disable
61
71
  else
62
72
  def self.set_temp_name(mod)
63
73
  mod
64
74
  end
65
75
  end
66
- # :nocov:
76
+ # simplecov:enable
67
77
  end
68
78
  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,12 +576,12 @@ class Roda
568
576
 
569
577
  if meth
570
578
  return unless captures = scope.send(meth, *captures)
571
- # :nocov:
579
+ # simplecov:disable
572
580
  # RODA4: Remove elsif block
573
581
  elsif defined?(yield)
574
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.")
575
583
  return unless captures = yield(*captures)
576
- # :nocov:
584
+ # simplecov:enable
577
585
  end
578
586
 
579
587
  @remaining_path = matchdata.post_match
@@ -594,13 +602,13 @@ class Roda
594
602
  if regexp.match?(rp)
595
603
  if last = rp.index('/', 1)
596
604
  val = rp[1, last-1]
597
- @remaining_path = rp[last, rp.length]
605
+ new_rp = rp[last, rp.length]
598
606
  else
599
607
  val = rp[1, rp.length]
600
- @remaining_path = ""
608
+ new_rp = ""
601
609
  end
602
610
 
603
- if meth
611
+ ret = if meth
604
612
  if captures = scope.send(meth, val)
605
613
  if captures.is_a?(Array)
606
614
  @captures.concat(captures)
@@ -611,12 +619,16 @@ class Roda
611
619
  else
612
620
  @captures << val
613
621
  end
622
+
623
+ @remaining_path = new_rp if ret
624
+
625
+ ret
614
626
  end
615
627
  end
616
- # :nocov:
628
+ # simplecov:disable
617
629
  else
618
630
  alias _consume_single_segment consume
619
- # :nocov:
631
+ # simplecov:enable
620
632
  end
621
633
 
622
634
  # The default path to use for redirects when a path is not given.
@@ -627,9 +639,13 @@ class Roda
627
639
  #
628
640
  # If the current request is a GET request, raise an error, as otherwise
629
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.
630
645
  def default_redirect_path
631
646
  raise RodaError, "must provide path argument to redirect for get requests" if is_get?
632
- path
647
+ path = self.path
648
+ path.start_with?('//') ? '/' : path
633
649
  end
634
650
 
635
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 = 107
7
+ RodaMinorVersion = 109
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
@@ -5,6 +5,7 @@ require_relative "roda/request"
5
5
  require_relative "roda/response"
6
6
  require_relative "roda/plugins"
7
7
  require_relative "roda/cache"
8
+ require_relative "roda/opts"
8
9
  require_relative "roda/version"
9
10
 
10
11
  # The main class for Roda. Roda is built completely out of plugins, with the
@@ -18,7 +19,8 @@ class Roda
18
19
  @inherit_middleware = true
19
20
  @middleware = []
20
21
  @plugins = []
21
- @opts = {}
22
+ @opts = RodaOpts.new
23
+ @opts.roda_class = self
22
24
  @raw_route_block = nil
23
25
  @route_block = nil
24
26
  @rack_app_route_block = nil
@@ -30,6 +32,8 @@ class Roda
30
32
  module Base
31
33
  # Class methods for the Roda class.
32
34
  module ClassMethods
35
+ RodaPlugins.opt_attr_reader(self, :root, name: :app_root)
36
+
33
37
  # The rack application that this class uses.
34
38
  def app
35
39
  @app || build_rack_app
@@ -140,11 +144,11 @@ class Roda
140
144
  # Fallback to instance_exec in this case.
141
145
  b = block
142
146
  block = if RUBY_VERSION >= '2.7'
143
- eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
147
+ lambda{|*a, **kw| instance_exec(*a, **kw, &b)} # Keyword arguments fallback
144
148
  else
145
- # :nocov:
149
+ # simplecov:disable
146
150
  lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
147
- # :nocov:
151
+ # simplecov:enable
148
152
  end
149
153
  else
150
154
  arity_meth = meth
@@ -189,7 +193,7 @@ class Roda
189
193
  end
190
194
 
191
195
  # Expand the given path, using the root argument as the base directory.
192
- def expand_path(path, root=opts[:root])
196
+ def expand_path(path, root=app_root)
193
197
  ::File.expand_path(path, root)
194
198
  end
195
199
 
@@ -207,9 +211,9 @@ class Roda
207
211
  # If the _roda_run_main_route instance method has not been overridden,
208
212
  # make it an alias to _roda_main_route for performance
209
213
  if instance_method(:_roda_run_main_route).owner == InstanceMethods
210
- class_eval("alias _roda_run_main_route _roda_main_route")
214
+ alias_method :_roda_run_main_route, :_roda_main_route
211
215
  end
212
- self::RodaResponse.class_eval do
216
+ self::RodaResponse.class_exec do
213
217
  if instance_method(:set_default_headers).owner == ResponseMethods &&
214
218
  instance_method(:default_headers).owner == ResponseMethods
215
219
 
@@ -226,7 +230,7 @@ class Roda
226
230
  plugin :direct_call
227
231
  end
228
232
 
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]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
233
+ 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}
230
234
  plugin :_optimized_matching
231
235
  end
232
236
  end
@@ -253,21 +257,28 @@ class Roda
253
257
  def inherited(subclass)
254
258
  raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
255
259
 
256
- # Mark current class as having been subclassed, as some optimizations
257
- # depend on the class not being subclassed
258
- opts[:subclassed] = true
259
-
260
260
  super
261
261
  subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
262
262
  subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
263
263
  subclass.instance_variable_set(:@plugins, @plugins.dup)
264
- subclass.instance_variable_set(:@opts, opts.dup)
265
- subclass.opts.delete(:subclassed)
266
- subclass.opts.to_a.each do |k,v|
267
- if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
268
- subclass.opts[k] = v.dup
264
+
265
+ opts = RodaOpts.new
266
+ opts.roda_class = subclass
267
+ @opts.each do |k,v|
268
+ opts[k] = if k == :subclassed
269
+ false
270
+ elsif (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
271
+ v.dup
272
+ else
273
+ v
269
274
  end
270
275
  end
276
+ subclass.instance_variable_set(:@opts, opts)
277
+
278
+ # Mark current class as having been subclassed, as some optimizations
279
+ # depend on the class not being subclassed
280
+ @opts[:subclassed] = true
281
+
271
282
  if block = @raw_route_block
272
283
  subclass.route(&block)
273
284
  end
@@ -312,9 +323,9 @@ class Roda
312
323
  plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
313
324
  @app = nil
314
325
  end
315
- # :nocov:
326
+ # simplecov:disable
316
327
  ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
317
- # :nocov:
328
+ # simplecov:enable
318
329
 
319
330
  # Setup routing tree for the current Roda application, and build the
320
331
  # underlying rack application using the stored middleware. Requires
@@ -350,9 +361,9 @@ class Roda
350
361
  @middleware << [args, block].freeze
351
362
  @app = nil
352
363
  end
353
- # :nocov:
364
+ # simplecov:disable
354
365
  ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
355
- # :nocov:
366
+ # simplecov:enable
356
367
 
357
368
  private
358
369
 
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.107.0
4
+ version: 3.109.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -159,11 +159,14 @@ files:
159
159
  - MIT-LICENSE
160
160
  - lib/roda.rb
161
161
  - lib/roda/cache.rb
162
+ - lib/roda/opts.rb
162
163
  - lib/roda/plugins.rb
163
164
  - lib/roda/plugins/Integer_matcher_max.rb
165
+ - lib/roda/plugins/_add_script_name.rb
164
166
  - lib/roda/plugins/_after_hook.rb
165
167
  - lib/roda/plugins/_base64.rb
166
168
  - lib/roda/plugins/_before_hook.rb
169
+ - lib/roda/plugins/_json.rb
167
170
  - lib/roda/plugins/_optimized_matching.rb
168
171
  - lib/roda/plugins/_symbol_class_matchers.rb
169
172
  - lib/roda/plugins/_symbol_regexp_matchers.rb
@@ -259,6 +262,7 @@ files:
259
262
  - lib/roda/plugins/padrino_render.rb
260
263
  - lib/roda/plugins/param_matchers.rb
261
264
  - lib/roda/plugins/params_capturing.rb
265
+ - lib/roda/plugins/params_capturing_restore.rb
262
266
  - lib/roda/plugins/part.rb
263
267
  - lib/roda/plugins/partials.rb
264
268
  - lib/roda/plugins/pass.rb
@@ -333,14 +337,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
333
337
  requirements:
334
338
  - - ">="
335
339
  - !ruby/object:Gem::Version
336
- version: 1.9.2
340
+ version: '2.0'
337
341
  required_rubygems_version: !ruby/object:Gem::Requirement
338
342
  requirements:
339
343
  - - ">="
340
344
  - !ruby/object:Gem::Version
341
345
  version: '0'
342
346
  requirements: []
343
- rubygems_version: 4.0.16
347
+ rubygems_version: 4.0.20
344
348
  specification_version: 4
345
349
  summary: Routing tree web toolkit
346
350
  test_files: []