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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90404574f8af3e952feb444cabe5bb01430e2d7c3658daee9f22f0c22e25d5f6
4
- data.tar.gz: 347ba47cf4c3b9e280848ba717a1a4a9f57f8ac0712d2aaa8e5942020c22cb29
3
+ metadata.gz: 88f17808e272159aaa6d5f1239eeb3453935aa188c0933b753e5a66046212e03
4
+ data.tar.gz: 952920ff3e1dafeefea80acefcbd3fb5e03f0033c2ca909757c697e2acd7c955
5
5
  SHA512:
6
- metadata.gz: 00e03ccebd8d48c4c1576ae0da2929d85a30430114b9a292bd8b9029fba31b4a7fa10767d7fb59a643c4280fb659e9afa90dacde27c7a5bdbc98fc5ee1df40a6
7
- data.tar.gz: 7867f8da5bad9db4102f09fc692158feee7605c8021b5d01bb07ebfe57d3a82ca0b18d7fc1341d6e5cdf9ff534736a6c9c3ce2420b2078abe96209ad423bce92
6
+ metadata.gz: fdd21d3152a257f21d1cfb33562334b71de82e1f017899dae0d5e9974f9b1028824c017f82d54405d5f88bed4d53607f6c238bb21c1907492da38b3b755e1a73
7
+ data.tar.gz: 61ed1ac97df5919e486a82abb62ad47b45603ec3c46d230ce6aa6f7ac697cd52b45af167a597ad691ec6de5267886e6b420efc0d3117a3604ba45f613703e80d
@@ -9,12 +9,12 @@ class Roda
9
9
  def decode64(str)
10
10
  str.unpack1("m0")
11
11
  end
12
- # :nocov:
12
+ # simplecov:disable
13
13
  else
14
14
  def decode64(str)
15
15
  str.unpack("m0")[0]
16
16
  end
17
- # :nocov:
17
+ # simplecov:enable
18
18
  end
19
19
 
20
20
  def urlsafe_encode64(bin)
@@ -44,6 +44,7 @@ class Roda
44
44
  rp = @remaining_path
45
45
  if rp.getbyte(0) == 47
46
46
  if last = rp.index('/', 1)
47
+ return false if last == 1
47
48
  @remaining_path = rp[last, rp.length]
48
49
  always{yield rp[1, last-1]}
49
50
  elsif (len = rp.length) > 1
@@ -6,7 +6,7 @@ class Roda
6
6
  # The all_verbs plugin adds methods for http verbs other than
7
7
  # get and post. The following verbs are added, assuming
8
8
  # rack handles them: delete, head, options, link, patch, put,
9
- # trace, unlink.
9
+ # query, trace, unlink.
10
10
  #
11
11
  # These methods operate just like Roda's default get and post
12
12
  # methods, so using them without any arguments just checks for
@@ -33,7 +33,7 @@ class Roda
33
33
  # isn't documentation for the individual methods created.
34
34
  module AllVerbs
35
35
  module RequestMethods
36
- %w'delete head options link patch put trace unlink'.each do |verb|
36
+ %w'delete head options link patch put query trace unlink'.each do |verb|
37
37
  if ::Rack::Request.method_defined?("#{verb}?")
38
38
  class_eval(<<-END, __FILE__, __LINE__+1)
39
39
  def #{verb}(*args, &block)
@@ -159,6 +159,35 @@ class Roda
159
159
  # plugin :assets,
160
160
  # css_opts: {style: :compressed}
161
161
  #
162
+ # In Roda 4, automatic compression of assets will not be attempted. You will
163
+ # need to specify the compressor to use using a Proc or Method. Here are values
164
+ # that you can use.
165
+ #
166
+ # ==== yui
167
+ #
168
+ # require 'yuicompressor'
169
+ # plugin :assets,
170
+ # css_compressor: lambda{|v| ::YUICompressor.compress_css(v, munge: true) },
171
+ # js_compressor: lambda{|v| ::YUICompressor.compress_js(v, munge: true) }
172
+ #
173
+ # ==== closure-compiler
174
+ #
175
+ # require 'closure-compiler'
176
+ # plugin :assets, js_compressor: ::Closure::Compiler.new.method(:compile)
177
+ #
178
+ # ==== uglifier
179
+ #
180
+ # require 'uglifier'
181
+ # plugin :assets, js_compressor: Uglifier.method(:compile)
182
+ #
183
+ # ==== minjs
184
+ #
185
+ # require 'minjs'
186
+ # plugin :assets,
187
+ # js_compressor: proc { |content|
188
+ # Minjs::Compressor::Compressor.new(debug: false).compress(content).to_js
189
+ # }
190
+ #
162
191
  # === Source Maps (CSS)
163
192
  #
164
193
  # The assets plugin does not have direct support for source maps, so it is
@@ -274,7 +303,7 @@ class Roda
274
303
  # :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
275
304
  # :concat_only :: Whether to just concatenate instead of concatenating
276
305
  # and compressing files (default: false)
277
- # :css_compressor :: Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try
306
+ # :css_compressor :: Compressor to use for compressing CSS, either a Proc, Method, :yui, :none, or nil (the default, which will try
278
307
  # :yui if available, but not fail if it is not available)
279
308
  # :css_dir :: Directory name containing your css source, inside :path (default: 'css')
280
309
  # :css_headers :: A hash of additional headers for your rendered css files
@@ -288,7 +317,7 @@ class Roda
288
317
  # related group are contained in a subdirectory with the same name (default: true)
289
318
  # :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
290
319
  # :headers :: A hash of additional headers for both js and css rendered files
291
- # :js_compressor :: Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs,
320
+ # :js_compressor :: Compressor to use for compressing javascript, either a Proc, Method, :yui, :closure, :uglifier, :minjs,
292
321
  # :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
293
322
  # not fail if any of them is not available)
294
323
  # :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
@@ -454,6 +483,17 @@ class Roda
454
483
  opts[:js_suffix] = (opts[:add_suffix] ? '.js' : '').freeze
455
484
  opts[:css_suffix] = (opts[:add_suffix] ? '.css' : '').freeze
456
485
 
486
+ unless opts[:concat_only]
487
+ [:css_compressor, :js_compressor].each do |s|
488
+ case v = opts[s]
489
+ when nil, :none, Proc, Method
490
+ nil
491
+ else
492
+ RodaPlugins.warn "Support for the assets plugin #{s}: #{v.inspect} option will be removed in Roda 4. See the assets plugin documentation for how to use an appropriate proc or method."
493
+ end
494
+ end
495
+ end
496
+
457
497
  opts.freeze
458
498
  end
459
499
 
@@ -535,6 +575,7 @@ class Roda
535
575
  app.read_asset_file(file, type)
536
576
  end.join("\n")
537
577
 
578
+ # RODA4: Remove :concat_only option
538
579
  unless o[:concat_only]
539
580
  content = compress_asset(content, type)
540
581
  end
@@ -564,6 +605,8 @@ class Roda
564
605
  return content
565
606
  when nil
566
607
  # default, try different compressors
608
+ when Proc, Method
609
+ return compressor.call(content)
567
610
  else
568
611
  # Allow calling private compress methods
569
612
  return send("compress_#{type}_#{compressor}", content)
@@ -579,6 +622,7 @@ class Roda
579
622
  begin
580
623
  # Allow calling private compress methods
581
624
  if c = send("compress_#{type}_#{comp}", content)
625
+ RodaPlugins.warn "Support for automatic compression of assets by trying common asset compressor libraries will be removed in Roda 4. The #{comp} #{type.to_s.upcase} compressor is currently used. See the assets plugin documentation for how to use an appropriate value for the assets plugin :#{type}_compressor option."
582
626
  return c
583
627
  end
584
628
  rescue LoadError, CompressorNotFound
@@ -615,9 +659,9 @@ class Roda
615
659
  begin
616
660
  require 'uglifier'
617
661
  rescue => e
618
- # :nocov:
662
+ # simplecov:disable
619
663
  raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
620
- # :nocov:
664
+ # simplecov:enable
621
665
  end
622
666
 
623
667
  Uglifier.compile(content)
@@ -645,11 +689,11 @@ class Roda
645
689
  digest = begin
646
690
  require 'openssl'
647
691
  ::OpenSSL::Digest
648
- # :nocov:
692
+ # simplecov:disable
649
693
  rescue LoadError
650
694
  require 'digest/sha2'
651
695
  ::Digest
652
- # :nocov:
696
+ # simplecov:enable
653
697
  end
654
698
  digest.const_get(algo.to_s.upcase).hexdigest(content)
655
699
  end
@@ -8,9 +8,9 @@ class Roda
8
8
  # in the authorization header will be recognized as long as they start
9
9
  # with the case insensitive string "bearer ".
10
10
  module BearerToken
11
- # :nocov:
11
+ # simplecov:disable
12
12
  METHOD = RUBY_VERSION >= "2.4" ? :match? : :match
13
- # :nocov:
13
+ # simplecov:enable
14
14
 
15
15
  module RequestMethods
16
16
  # Return the bearer token for the request if there is one in the
@@ -22,12 +22,20 @@ class Roda
22
22
  # end
23
23
  #
24
24
  # This provides the same basic feature as the pass plugin, but
25
- # uses Ruby's standard control flow primative instead of a
25
+ # uses Ruby's standard control flow primitive instead of a
26
26
  # separate method.
27
27
  module Break
28
28
  module RequestMethods
29
29
  private
30
30
 
31
+ # Handle passing inside the match block.
32
+ def always
33
+ rp = @remaining_path
34
+ super
35
+ ensure
36
+ @remaining_path = rp
37
+ end
38
+
31
39
  # Handle break inside match blocks, restoring remaining path.
32
40
  def if_match(_)
33
41
  rp = @remaining_path
@@ -75,28 +75,27 @@ class Roda
75
75
  # Set the last modified time of the resource using the Last-Modified header.
76
76
  # The +time+ argument should be a Time instance.
77
77
  #
78
- # If the current request includes an If-Modified-Since header that is
79
- # equal or later than the time specified, immediately returns a response
80
- # with a 304 status.
81
- #
82
78
  # If the current request includes an If-Unmodified-Since header that is
83
79
  # before than the time specified, immediately returns a response
84
80
  # with a 412 status.
81
+ #
82
+ # If the current request does not include an If-None-Match header and
83
+ # includes an If-Modified-Since header that is equal or later than the
84
+ # time specified, immediately returns a response with a 304 status.
85
85
  def last_modified(time)
86
86
  return unless time
87
87
  res = response
88
88
  e = env
89
89
  res[RodaResponseHeaders::LAST_MODIFIED] = time.httpdate
90
- return if e['HTTP_IF_NONE_MATCH']
91
90
  status = res.status
92
91
 
93
- if (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
94
- res.status = 304
92
+ if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
93
+ res.status = 412
95
94
  halt
96
95
  end
97
96
 
98
- if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
99
- res.status = 412
97
+ if !e['HTTP_IF_NONE_MATCH'] && (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
98
+ res.status = 304
100
99
  halt
101
100
  end
102
101
  end
@@ -113,8 +112,9 @@ class Roda
113
112
  # matching etag, immediately returns a response with a 304 or 412 status,
114
113
  # depending on the request method.
115
114
  #
116
- # When the current request includes an If-Match header with a
117
- # etag that doesn't match, immediately returns a response with a 412 status.
115
+ # When the current request includes an If-Match header with an etag that
116
+ # doesn't match, or where the :weak option was used, immediately returns a
117
+ # response with a 412 status.
118
118
  def etag(value, opts=OPTS)
119
119
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
120
120
  weak = opts[:weak]
@@ -132,7 +132,7 @@ class Roda
132
132
  end
133
133
 
134
134
  if ifm = e['HTTP_IF_MATCH']
135
- unless etag_matches?(ifm, etag, new_resource)
135
+ if weak || !etag_matches?(ifm, etag, new_resource)
136
136
  res.status = 412
137
137
  halt
138
138
  end
@@ -36,11 +36,11 @@ class Roda
36
36
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
37
37
  end
38
38
  else
39
- # :nocov:
39
+ # simplecov:disable
40
40
  def self.start_timer # :nodoc:
41
41
  Time.now
42
42
  end
43
- # :nocov:
43
+ # simplecov:enable
44
44
  end
45
45
 
46
46
  module InstanceMethods
@@ -61,10 +61,10 @@ class Roda
61
61
  line = "#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{status} #{((length = headers[RodaResponseHeaders::CONTENT_LENGTH]) && (length unless length == '0')) || '-'} #{elapsed_time} "
62
62
  if MUTATE_LINE
63
63
  line.gsub!(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
64
- # :nocov:
64
+ # simplecov:disable
65
65
  else
66
66
  line = line.gsub(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
67
- # :nocov:
67
+ # simplecov:enable
68
68
  end
69
69
  line[-1] = "\n"
70
70
  opts[:common_logger_meth].call(line)
@@ -48,9 +48,9 @@ class Roda
48
48
  # Note that this plugin only affects cookies set by the application, and does not
49
49
  # affect cookies set by middleware the application is using.
50
50
  module CookieFlags
51
- # :nocov:
51
+ # simplecov:disable
52
52
  MATCH_METH = RUBY_VERSION >= '2.4' ? :match? : :match
53
- # :nocov:
53
+ # simplecov:enable
54
54
  private_constant :MATCH_METH
55
55
 
56
56
  DEFAULTS = {:secure=>true, :httponly=>true, :same_site=>'strict', :action=>:modify}.freeze
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ # RODA4: Remove plugin
4
+
3
5
  require 'rack/csrf'
4
6
 
5
7
  class Roda
@@ -7,6 +9,7 @@ class Roda
7
9
  # This plugin is no longer recommended for use, it exists only for
8
10
  # backwards compatibility. Consider using the route_csrf plugin
9
11
  # instead, as that provides stronger CSRF protection.
12
+ # This plugin will no longer ship with Roda starting in Roda 4.
10
13
  #
11
14
  # The csrf plugin adds CSRF protection using rack_csrf, along with
12
15
  # some csrf helper methods to use in your views. To use it, load
@@ -3,6 +3,8 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
+ warn("The delay_build plugin no longer has an effect. It will be removed in Roda 4.")
7
+
6
8
  module DelayBuild
7
9
  module ClassMethods
8
10
  # No-op for backwards compatibility
@@ -43,19 +43,19 @@ class Roda
43
43
  render_each(enum, template, :locals=>locals, &block)
44
44
  end
45
45
  RUBY
46
- # :nocov:
46
+ # simplecov:disable
47
47
  else
48
48
  def each_part(enum, template, locals=OPTS, &block)
49
49
  render_each(enum, template, :locals=>locals, &block)
50
50
  end
51
51
  end
52
- # :nocov:
52
+ # simplecov:enable
53
53
  end
54
54
 
55
55
  module AssumeFixedLocalsInstanceMethods
56
- # :nocov:
56
+ # simplecov:disable
57
57
  if RUBY_VERSION >= '3.0'
58
- # :nocov:
58
+ # simplecov:enable
59
59
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
60
60
  def each_part(enum, template, **locals, &block)
61
61
  if optimized_method = _cached_render_each_template_method(template)
@@ -44,9 +44,9 @@ class Roda
44
44
  :filter=>lambda{|k,v| false},
45
45
  :headers=>OPTS,
46
46
  :host=>'localhost',
47
- # :nocov:
47
+ # simplecov:disable
48
48
  :emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
49
- # :nocov:
49
+ # simplecov:enable
50
50
  :default_headers=>lambda do |h, e|
51
51
  subject = if e.respond_to?(:message)
52
52
  "#{e.class}: #{e.message}"
@@ -415,14 +415,14 @@ END
415
415
  def exception_page_exception_message(exception)
416
416
  exception.detailed_message(highlight: false).to_s
417
417
  end
418
- # :nocov:
418
+ # simplecov:disable
419
419
  else
420
420
  # Return message to use for exception.
421
421
  def exception_page_exception_message(exception)
422
422
  exception.message.to_s
423
423
  end
424
424
  end
425
- # :nocov:
425
+ # simplecov:enable
426
426
  end
427
427
 
428
428
  module RequestMethods
@@ -17,10 +17,10 @@ class Roda
17
17
  begin
18
18
  require 'cgi/escape'
19
19
  unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1
20
- # :nocov:
20
+ # simplecov:disable
21
21
  CGI = Object.new
22
22
  CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util)
23
- # :nocov:
23
+ # simplecov:enable
24
24
  end
25
25
 
26
26
  module InstanceMethods
@@ -30,7 +30,7 @@ class Roda
30
30
  end
31
31
  end
32
32
  rescue LoadError
33
- # :nocov:
33
+ # simplecov:disable
34
34
 
35
35
  # A Hash of entities and their escaped equivalents,
36
36
  # to be escaped by h().
@@ -50,7 +50,7 @@ class Roda
50
50
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
51
51
  end
52
52
  end
53
- # :nocov:
53
+ # simplecov:enable
54
54
  end
55
55
  end
56
56
 
@@ -128,12 +128,16 @@ class Roda
128
128
  if routes = roda_class.opts[:hash_branches][namespace]
129
129
  if segment_end = rp.index('/', 1)
130
130
  if meth = routes[rp[0, segment_end]]
131
- @remaining_path = rp[segment_end, 100000000]
132
- always{scope.send(meth, self)}
131
+ always do
132
+ @remaining_path = rp[segment_end, 100000000]
133
+ scope.send(meth, self)
134
+ end
133
135
  end
134
136
  elsif meth = routes[rp]
135
- @remaining_path = ''
136
- always{scope.send(meth, self)}
137
+ always do
138
+ @remaining_path = ''
139
+ scope.send(meth, self)
140
+ end
137
141
  end
138
142
  end
139
143
  end
@@ -116,8 +116,10 @@ class Roda
116
116
  # remaining path, and dispatch to that block if there is one.
117
117
  def hash_paths(namespace=matched_path)
118
118
  if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
119
- @remaining_path = ''
120
- always{scope.send(meth, self)}
119
+ always do
120
+ @remaining_path = ''
121
+ scope.send(meth, self)
122
+ end
121
123
  end
122
124
  end
123
125
  end
@@ -51,11 +51,11 @@ class Roda
51
51
  Digest = begin
52
52
  require 'openssl'
53
53
  ::OpenSSL::Digest
54
- # :nocov:
54
+ # simplecov:disable
55
55
  rescue LoadError
56
56
  require 'digest/sha2'
57
57
  ::Digest
58
- # :nocov:
58
+ # simplecov:enable
59
59
  end
60
60
 
61
61
  def self.load_dependencies(app, opts = OPTS)
@@ -346,43 +346,41 @@ class Roda
346
346
 
347
347
  if submitted_hmac.bytesize == 64
348
348
  on String do |flags|
349
- if flags.bytesize >= 1
350
- if flags.include?('n') ^ !scope.hmac_path_namespace(opts).nil?
351
- # Namespace required and not provided, or provided and not required.
352
- # Bail early to avoid unnecessary HMAC calculation.
353
- @remaining_path = orig_path
354
- return
355
- end
349
+ if flags.include?('n') ^ !scope.hmac_path_namespace(opts).nil?
350
+ # Namespace required and not provided, or provided and not required.
351
+ # Bail early to avoid unnecessary HMAC calculation.
352
+ @remaining_path = orig_path
353
+ return
354
+ end
356
355
 
357
- if flags.include?('m')
358
- rpath = "#{env['REQUEST_METHOD'].to_s.upcase}:#{rpath}"
359
- end
356
+ if flags.include?('m')
357
+ rpath = "#{env['REQUEST_METHOD'].to_s.upcase}:#{rpath}"
358
+ end
360
359
 
361
- if flags.include?('p')
362
- rpath = "#{rpath}?#{env["QUERY_STRING"]}"
363
- end
360
+ if flags.include?('p')
361
+ rpath = "#{rpath}?#{env["QUERY_STRING"]}"
362
+ end
364
363
 
365
- if hmac_path_valid?(mpath, rpath, submitted_hmac, opts)
366
- if flags.include?('t')
367
- on Integer do |int|
368
- if int >= Time.now.to_i
369
- always(&block)
370
- else
371
- # Return from method without matching
372
- @remaining_path = orig_path
373
- return
374
- end
364
+ if hmac_path_valid?(mpath, rpath, submitted_hmac, opts)
365
+ if flags.include?('t')
366
+ on Integer do |int|
367
+ if int >= Time.now.to_i
368
+ always(&block)
369
+ else
370
+ # Return from method without matching
371
+ @remaining_path = orig_path
372
+ return
375
373
  end
376
- else
377
- always(&block)
378
374
  end
375
+ else
376
+ always(&block)
379
377
  end
380
378
  end
381
-
382
- # Return from method without matching
383
- @remaining_path = orig_path
384
- return
385
379
  end
380
+
381
+ # Return from method without matching
382
+ @remaining_path = orig_path
383
+ return
386
384
  end
387
385
 
388
386
  # Return from method without matching
@@ -68,8 +68,29 @@ class Roda
68
68
  # end
69
69
  # end
70
70
  #
71
- # This plugin uses the host method on the request to get the hostname (this method
72
- # is defined by Rack).
71
+ # This plugin uses the +host+ method on the request to get the hostname (this method
72
+ # is defined by Rack). Note that as of Rack 3.2, Rack's default approach to determine
73
+ # the hostname is insecure in many cases, since:
74
+ #
75
+ # * It will use a forwarded header instead of the +host+ header by default if the
76
+ # request was from a trusted proxy.
77
+ # * Rack trust proxies from any private IP address range by default
78
+ # (this can be disabled using <tt>Rack::Request.ip_filter=</tt>).
79
+ # * Rack supports both +forwarded+ and <tt>x-forwarded-host</tt> headers, will check
80
+ # both by default, and the order of checking depends on Rack's configuration
81
+ # (this can be configured using <tt>Rack::Request.forwarded_priority</tt>).
82
+ # * If the application can be reached via an arbitrary +host+ header, the attacker
83
+ # has full control over the <tt>Rack::Request#host</tt> value even when forwarded
84
+ # host usage is disabled.
85
+ #
86
+ # You should configure Rack appropriately for your deployment, not allowing the use
87
+ # of forwarded information unless your deployment is using a reverse proxy, and only
88
+ # allowing the specific forwarded header set by the expected reverse proxy if a
89
+ # reverse proxy is used. Rack 3.3 should include more control in this area,
90
+ # and Rack 4 should ship with more secure defaults in regard to forwarding headers.
91
+ #
92
+ # It is recommended to use the host_authorization plugin to check that the requested
93
+ # host is allowed when using this plugin.
73
94
  module HostRouting
74
95
  REQUEST_INSTANCE_VARIABLES = [:@_host_routing_host].freeze
75
96
 
@@ -140,7 +161,7 @@ class Roda
140
161
  # Treat all given hostnames as routing to the give host.
141
162
  def to(host, *hostnames)
142
163
  hostnames.each do |hostname|
143
- @host_hash[hostname] = host
164
+ @host_hash[hostname.downcase.freeze] = host
144
165
  end
145
166
  end
146
167
 
@@ -180,7 +201,11 @@ class Roda
180
201
  # * The return value of the +hosts.default+ block, if given
181
202
  # * The default value provided in the +hosts.default+ call
182
203
  def _get_host_routing_host
183
- host = self.host || ""
204
+ if host = self.host
205
+ host = host.downcase
206
+ else
207
+ host = ""
208
+ end
184
209
 
185
210
  roda_class.opts[:host_routing_hash][host] ||
186
211
  scope._host_routing_default(host) ||
@@ -72,6 +72,8 @@ class Roda
72
72
  # raises an exception.
73
73
  def POST
74
74
  super
75
+ rescue RodaError
76
+ raise
75
77
  rescue => e
76
78
  handle_invalid_request_body(e)
77
79
  end