roda 3.107.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 (49) 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 +4 -4
  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/each_part.rb +4 -4
  12. data/lib/roda/plugins/error_email.rb +2 -2
  13. data/lib/roda/plugins/exception_page.rb +2 -2
  14. data/lib/roda/plugins/h.rb +4 -4
  15. data/lib/roda/plugins/hash_branches.rb +8 -4
  16. data/lib/roda/plugins/hash_paths.rb +4 -2
  17. data/lib/roda/plugins/hash_public.rb +2 -2
  18. data/lib/roda/plugins/hmac_paths.rb +27 -29
  19. data/lib/roda/plugins/host_routing.rb +29 -4
  20. data/lib/roda/plugins/invalid_request_body.rb +2 -0
  21. data/lib/roda/plugins/json_parser.rb +20 -2
  22. data/lib/roda/plugins/mail_processor.rb +2 -2
  23. data/lib/roda/plugins/mailer.rb +4 -4
  24. data/lib/roda/plugins/optimized_segment_matchers.rb +13 -6
  25. data/lib/roda/plugins/optimized_string_matchers.rb +6 -3
  26. data/lib/roda/plugins/params_capturing.rb +16 -7
  27. data/lib/roda/plugins/params_capturing_restore.rb +72 -0
  28. data/lib/roda/plugins/part.rb +4 -4
  29. data/lib/roda/plugins/pass.rb +4 -1
  30. data/lib/roda/plugins/path.rb +6 -6
  31. data/lib/roda/plugins/path_matchers.rb +5 -1
  32. data/lib/roda/plugins/public.rb +4 -4
  33. data/lib/roda/plugins/render.rb +24 -12
  34. data/lib/roda/plugins/render_coverage.rb +7 -6
  35. data/lib/roda/plugins/render_each.rb +2 -2
  36. data/lib/roda/plugins/route_csrf.rb +35 -3
  37. data/lib/roda/plugins/run_append_slash.rb +8 -4
  38. data/lib/roda/plugins/sec_fetch_site_csrf.rb +5 -2
  39. data/lib/roda/plugins/sessions.rb +2 -2
  40. data/lib/roda/plugins/shape_friendly.rb +4 -4
  41. data/lib/roda/plugins/static.rb +3 -0
  42. data/lib/roda/plugins/type_routing.rb +1 -1
  43. data/lib/roda/plugins/url_escape.rb +2 -2
  44. data/lib/roda/plugins/view_options.rb +3 -5
  45. data/lib/roda/plugins.rb +4 -4
  46. data/lib/roda/request.rb +25 -9
  47. data/lib/roda/version.rb +1 -1
  48. data/lib/roda.rb +7 -7
  49. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84bb84bd98ebe0d00d48315d9036ae1c22d93c92715922a6dba297f225c9fec7
4
- data.tar.gz: 7cf2abbe3656ec22aef206a4e895c2520a8f80dc0ed11ec058a8a5f4cf3194b5
3
+ metadata.gz: 88f17808e272159aaa6d5f1239eeb3453935aa188c0933b753e5a66046212e03
4
+ data.tar.gz: 952920ff3e1dafeefea80acefcbd3fb5e03f0033c2ca909757c697e2acd7c955
5
5
  SHA512:
6
- metadata.gz: 874ed7d5065e77c593bccd1939d3742acf3e65a0b6ea6eb95e02cd6ce9ca55c9819cfafe6ecfc61ce548075832b91c68800e7f5ae35774efb60949f0a86cb1ae
7
- data.tar.gz: 40a37368dfc79527849fec46d615c26680cad15dd590b8b6a3dba0fa2809c2b90bacefa70f65b78566f88c62485f97afd715494a214fcc87050125ac7743d519
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)
@@ -659,9 +659,9 @@ class Roda
659
659
  begin
660
660
  require 'uglifier'
661
661
  rescue => e
662
- # :nocov:
662
+ # simplecov:disable
663
663
  raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
664
- # :nocov:
664
+ # simplecov:enable
665
665
  end
666
666
 
667
667
  Uglifier.compile(content)
@@ -689,11 +689,11 @@ class Roda
689
689
  digest = begin
690
690
  require 'openssl'
691
691
  ::OpenSSL::Digest
692
- # :nocov:
692
+ # simplecov:disable
693
693
  rescue LoadError
694
694
  require 'digest/sha2'
695
695
  ::Digest
696
- # :nocov:
696
+ # simplecov:enable
697
697
  end
698
698
  digest.const_get(algo.to_s.upcase).hexdigest(content)
699
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
@@ -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
@@ -17,7 +17,19 @@ class Roda
17
17
  module JsonParser
18
18
  DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
19
19
 
20
+ # simplecov:disable
21
+ MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match
22
+ # simplecov:enable
23
+ private_constant :MATCH_METHOD
24
+
20
25
  # Handle options for the json_parser plugin:
26
+ # :content_type_regexp :: A regexp used to determine if the request's
27
+ # content type is JSON. For backwards compatibility,
28
+ # uses the insecure /json/ if not provided. Warns
29
+ # if this option is not provided. The recommended
30
+ # value to use is /\Aapplication\/json\b/i or
31
+ # /\Aapplication\/(?:vnd\.api\+)?json\b/i. Roda 4
32
+ # will default to the latter.
21
33
  # :error_handler :: A proc to call if an exception is raised when
22
34
  # parsing a JSON request body. The proc is called
23
35
  # with the request object, and should probably call
@@ -37,6 +49,13 @@ class Roda
37
49
  app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
38
50
  app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || app.opts[:json_parser] || JSON.method(:parse)
39
51
  app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
52
+ app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.opts[:json_parser_content_type_regexp]
53
+
54
+ unless app.opts[:json_parser_content_type_regexp]
55
+ # RODA4: Switch to /\Aapplication\/(?:vnd\.api\+)?json\b/i by default
56
+ RodaPlugins.warn(':content_type_regexp option not provided to json_parser plugin, using /json/ for backwards compatibility, which may be insecure, using /\Aapplication\/json\b/i or /\Aapplication\/(?:vnd\.api\+)?json\b/i is recommended')
57
+ app.opts[:json_parser_content_type_regexp] = /json/
58
+ end
40
59
 
41
60
  case opts[:wrap]
42
61
  when :unless_hash, :always
@@ -57,7 +76,7 @@ class Roda
57
76
  return post_params
58
77
  end
59
78
 
60
- unless (input = env["rack.input"]) && (content_type = self.content_type) && content_type.include?('json')
79
+ unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.opts[:json_parser_content_type_regexp].send(MATCH_METHOD, content_type)
61
80
  return super
62
81
  end
63
82
 
@@ -84,7 +103,6 @@ class Roda
84
103
  args << self if roda_class.opts[:json_parser_include_request]
85
104
  roda_class.opts[:json_parser_parser].call(*args)
86
105
  end
87
-
88
106
 
89
107
  # Rack 3 dropped requirement that input be rewindable
90
108
  if Rack.release >= '3'
@@ -510,11 +510,11 @@ class Roda
510
510
  a1.casecmp?(a2)
511
511
  end
512
512
  else
513
- # :nocov:
513
+ # simplecov:disable
514
514
  def address_match?(a1, a2)
515
515
  a1.downcase == a2.downcase
516
516
  end
517
- # :nocov:
517
+ # simplecov:enable
518
518
  end
519
519
 
520
520
  # Match if any of the given addresses match the given val, which
@@ -153,9 +153,9 @@ class Roda
153
153
  mail
154
154
  end
155
155
  end
156
- # :nocov:
156
+ # simplecov:disable
157
157
  ruby2_keywords(:mail) if respond_to?(:ruby2_keywords, true)
158
- # :nocov:
158
+ # simplecov:enable
159
159
 
160
160
  # Calls +mail+ with given arguments and immediately sends the resulting mail.
161
161
  def sendmail(*args)
@@ -163,9 +163,9 @@ class Roda
163
163
  m.deliver
164
164
  end
165
165
  end
166
- # :nocov:
166
+ # simplecov:disable
167
167
  ruby2_keywords(:sendmail) if respond_to?(:ruby2_keywords, true)
168
- # :nocov:
168
+ # simplecov:enable
169
169
  end
170
170
 
171
171
  module RequestMethods
@@ -27,11 +27,16 @@ class Roda
27
27
  rp = @remaining_path
28
28
  if rp.getbyte(0) == 47
29
29
  if last = rp.index('/', 1)
30
- @remaining_path = rp[last, rp.length]
31
- always{yield rp[1, last-1]}
30
+ return false if last == 1
31
+ always do
32
+ @remaining_path = rp[last, rp.length]
33
+ yield rp[1, last-1]
34
+ end
32
35
  elsif (len = rp.length) > 1
33
- @remaining_path = ""
34
- always{yield rp[1, len]}
36
+ always do
37
+ @remaining_path = ""
38
+ yield rp[1, len]
39
+ end
35
40
  end
36
41
  end
37
42
  end
@@ -41,8 +46,10 @@ class Roda
41
46
  def is_segment
42
47
  rp = @remaining_path
43
48
  if rp.getbyte(0) == 47 && !rp.index('/', 1) && (len = rp.length) > 1
44
- @remaining_path = ""
45
- always{yield rp[1, len]}
49
+ always do
50
+ @remaining_path = ""
51
+ yield rp[1, len]
52
+ end
46
53
  end
47
54
  end
48
55
  end
@@ -28,7 +28,11 @@ class Roda
28
28
  module RequestMethods
29
29
  # Optimized version of +on+ that only supports a single string.
30
30
  def on_branch(s)
31
- always{yield} if _match_string(s)
31
+ rp = @remaining_path
32
+ if _match_string(s)
33
+ always{yield}
34
+ @remaining_path = rp
35
+ end
32
36
  end
33
37
 
34
38
  # Optimized version of +is+ that only supports a single string.
@@ -37,9 +41,8 @@ class Roda
37
41
  if _match_string(s)
38
42
  if @remaining_path.empty?
39
43
  always{yield}
40
- else
41
- @remaining_path = rp
42
44
  end
45
+ @remaining_path = rp
43
46
  end
44
47
  end
45
48
  end