actionpack 7.2.2.1 → 8.0.0.beta1

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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -139
  3. data/lib/abstract_controller/helpers.rb +0 -2
  4. data/lib/action_controller/metal/allow_browser.rb +1 -1
  5. data/lib/action_controller/metal/conditional_get.rb +5 -1
  6. data/lib/action_controller/metal/http_authentication.rb +4 -1
  7. data/lib/action_controller/metal/instrumentation.rb +1 -2
  8. data/lib/action_controller/metal/live.rb +10 -2
  9. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  10. data/lib/action_controller/metal/renderers.rb +2 -1
  11. data/lib/action_controller/metal/streaming.rb +5 -84
  12. data/lib/action_controller/metal/strong_parameters.rb +274 -73
  13. data/lib/action_controller/railtie.rb +1 -1
  14. data/lib/action_controller/test_case.rb +2 -0
  15. data/lib/action_dispatch/http/cache.rb +27 -10
  16. data/lib/action_dispatch/http/content_security_policy.rb +13 -25
  17. data/lib/action_dispatch/http/filter_parameters.rb +4 -9
  18. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  19. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  20. data/lib/action_dispatch/http/request.rb +4 -2
  21. data/lib/action_dispatch/journey/parser.rb +99 -196
  22. data/lib/action_dispatch/journey/scanner.rb +40 -42
  23. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  24. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
  25. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  26. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  27. data/lib/action_dispatch/railtie.rb +2 -0
  28. data/lib/action_dispatch/routing/inspector.rb +1 -1
  29. data/lib/action_dispatch/routing/mapper.rb +25 -17
  30. data/lib/action_dispatch/routing/route_set.rb +18 -6
  31. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  32. data/lib/action_dispatch.rb +0 -4
  33. data/lib/action_pack/gem_version.rb +4 -4
  34. metadata +13 -35
  35. data/lib/action_dispatch/journey/parser.y +0 -50
  36. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -8,7 +8,8 @@ require "active_support/core_ext/array/wrap"
8
8
  module ActionDispatch # :nodoc:
9
9
  # # Action Dispatch Content Security Policy
10
10
  #
11
- # Configures the HTTP [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
11
+ # Configures the HTTP [Content-Security-Policy]
12
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
12
13
  # response header to help protect against XSS and
13
14
  # injection attacks.
14
15
  #
@@ -26,9 +27,6 @@ module ActionDispatch # :nodoc:
26
27
  # policy.report_uri "/csp-violation-report-endpoint"
27
28
  # end
28
29
  class ContentSecurityPolicy
29
- class InvalidDirectiveError < StandardError
30
- end
31
-
32
30
  class Middleware
33
31
  def initialize(app)
34
32
  @app = app
@@ -128,6 +126,7 @@ module ActionDispatch # :nodoc:
128
126
  MAPPINGS = {
129
127
  self: "'self'",
130
128
  unsafe_eval: "'unsafe-eval'",
129
+ wasm_unsafe_eval: "'wasm-unsafe-eval'",
131
130
  unsafe_hashes: "'unsafe-hashes'",
132
131
  unsafe_inline: "'unsafe-inline'",
133
132
  none: "'none'",
@@ -228,7 +227,8 @@ module ActionDispatch # :nodoc:
228
227
  end
229
228
  end
230
229
 
231
- # Enable the [report-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
230
+ # Enable the [report-uri]
231
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
232
232
  # directive. Violation reports will be sent to the
233
233
  # specified URI:
234
234
  #
@@ -238,7 +238,8 @@ module ActionDispatch # :nodoc:
238
238
  @directives["report-uri"] = [uri]
239
239
  end
240
240
 
241
- # Specify asset types for which [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
241
+ # Specify asset types for which [Subresource Integrity]
242
+ # (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
242
243
  #
243
244
  # policy.require_sri_for :script, :style
244
245
  #
@@ -254,7 +255,8 @@ module ActionDispatch # :nodoc:
254
255
  end
255
256
  end
256
257
 
257
- # Specify whether a [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
258
+ # Specify whether a [sandbox]
259
+ # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
258
260
  # should be enabled for the requested resource:
259
261
  #
260
262
  # policy.sandbox
@@ -322,9 +324,9 @@ module ActionDispatch # :nodoc:
322
324
  @directives.map do |directive, sources|
323
325
  if sources.is_a?(Array)
324
326
  if nonce && nonce_directive?(directive, nonce_directives)
325
- "#{directive} #{build_directive(directive, sources, context).join(' ')} 'nonce-#{nonce}'"
327
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
326
328
  else
327
- "#{directive} #{build_directive(directive, sources, context).join(' ')}"
329
+ "#{directive} #{build_directive(sources, context).join(' ')}"
328
330
  end
329
331
  elsif sources
330
332
  directive
@@ -334,22 +336,8 @@ module ActionDispatch # :nodoc:
334
336
  end
335
337
  end
336
338
 
337
- def validate(directive, sources)
338
- sources.flatten.each do |source|
339
- if source.include?(";") || source != source.gsub(/[[:space:]]/, "")
340
- raise InvalidDirectiveError, <<~MSG.squish
341
- Invalid Content Security Policy #{directive}: "#{source}".
342
- Directive values must not contain whitespace or semicolons.
343
- Please use multiple arguments or other directive methods instead.
344
- MSG
345
- end
346
- end
347
- end
348
-
349
- def build_directive(directive, sources, context)
350
- resolved_sources = sources.map { |source| resolve_source(source, context) }
351
-
352
- validate(directive, resolved_sources)
339
+ def build_directive(sources, context)
340
+ sources.map { |source| resolve_source(source, context) }
353
341
  end
354
342
 
355
343
  def resolve_source(source, context)
@@ -68,17 +68,12 @@ module ActionDispatch
68
68
  ActiveSupport::ParameterFilter.new(filters)
69
69
  end
70
70
 
71
+ KV_RE = "[^&;=]+"
72
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
73
  def filtered_query_string # :doc:
72
- parts = query_string.split(/([&;])/)
73
- filtered_parts = parts.map do |part|
74
- if part.include?("=")
75
- key, value = part.split("=", 2)
76
- parameter_filter.filter(key => value).first.join("=")
77
- else
78
- part
79
- end
74
+ query_string.gsub(PAIR_RE) do |_|
75
+ parameter_filter.filter($1 => $2).first.join("=")
80
76
  end
81
- filtered_parts.join("")
82
77
  end
83
78
  end
84
79
  end
@@ -37,16 +37,9 @@ module ActionDispatch
37
37
  def parameter_filtered_location
38
38
  uri = URI.parse(location)
39
39
  unless uri.query.nil? || uri.query.empty?
40
- parts = uri.query.split(/([&;])/)
41
- filtered_parts = parts.map do |part|
42
- if part.include?("=")
43
- key, value = part.split("=", 2)
44
- request.parameter_filter.filter(key => value).first.join("=")
45
- else
46
- part
47
- end
40
+ uri.query.gsub!(FilterParameters::PAIR_RE) do
41
+ request.parameter_filter.filter($1 => $2).first.join("=")
48
42
  end
49
- uri.query = filtered_parts.join("")
50
43
  end
51
44
  uri.to_s
52
45
  rescue URI::Error
@@ -86,12 +86,14 @@ module ActionDispatch # :nodoc:
86
86
  ambient_light_sensor: "ambient-light-sensor",
87
87
  autoplay: "autoplay",
88
88
  camera: "camera",
89
+ display_capture: "display-capture",
89
90
  encrypted_media: "encrypted-media",
90
91
  fullscreen: "fullscreen",
91
92
  geolocation: "geolocation",
92
93
  gyroscope: "gyroscope",
93
94
  hid: "hid",
94
95
  idle_detection: "idle-detection",
96
+ keyboard_map: "keyboard-map",
95
97
  magnetometer: "magnetometer",
96
98
  microphone: "microphone",
97
99
  midi: "midi",
@@ -55,6 +55,8 @@ module ActionDispatch
55
55
  METHOD
56
56
  end
57
57
 
58
+ TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
59
+
58
60
  def self.empty
59
61
  new({})
60
62
  end
@@ -282,7 +284,7 @@ module ActionDispatch
282
284
 
283
285
  # Returns the content length of the request as an integer.
284
286
  def content_length
285
- return raw_post.bytesize if headers.key?("Transfer-Encoding")
287
+ return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
286
288
  super.to_i
287
289
  end
288
290
 
@@ -468,7 +470,7 @@ module ActionDispatch
468
470
  def read_body_stream
469
471
  if body_stream
470
472
  reset_stream(body_stream) do
471
- if headers.key?("Transfer-Encoding")
473
+ if has_header?(TRANSFER_ENCODING)
472
474
  body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
473
475
  else
474
476
  body_stream.read(content_length)
@@ -1,200 +1,103 @@
1
- #
2
- # DO NOT MODIFY!!!!
3
- # This file is automatically generated by Racc 1.4.16 from
4
- # Racc grammar file "".
1
+ # frozen_string_literal: true
5
2
 
6
- # :markup: markdown
3
+ require "action_dispatch/journey/scanner"
4
+ require "action_dispatch/journey/nodes/node"
7
5
 
8
- require 'racc/parser.rb'
9
-
10
- # :stopdoc:
11
-
12
- require "action_dispatch/journey/parser_extras"
13
6
  module ActionDispatch
14
- module Journey
15
- class Parser < Racc::Parser
16
- ##### State transition tables begin ###
17
-
18
- racc_action_table = [
19
- 13, 15, 14, 7, 19, 16, 8, 19, 13, 15,
20
- 14, 7, 17, 16, 8, 13, 15, 14, 7, 21,
21
- 16, 8, 13, 15, 14, 7, 24, 16, 8 ]
22
-
23
- racc_action_check = [
24
- 2, 2, 2, 2, 22, 2, 2, 2, 19, 19,
25
- 19, 19, 1, 19, 19, 7, 7, 7, 7, 17,
26
- 7, 7, 0, 0, 0, 0, 20, 0, 0 ]
27
-
28
- racc_action_pointer = [
29
- 20, 12, -2, nil, nil, nil, nil, 13, nil, nil,
30
- nil, nil, nil, nil, nil, nil, nil, 19, nil, 6,
31
- 20, nil, -5, nil, nil ]
32
-
33
- racc_action_default = [
34
- -19, -19, -2, -3, -4, -5, -6, -19, -10, -11,
35
- -12, -13, -14, -15, -16, -17, -18, -19, -1, -19,
36
- -19, 25, -8, -9, -7 ]
37
-
38
- racc_goto_table = [
39
- 1, 22, 18, 23, nil, nil, nil, 20 ]
40
-
41
- racc_goto_check = [
42
- 1, 2, 1, 3, nil, nil, nil, 1 ]
43
-
44
- racc_goto_pointer = [
45
- nil, 0, -18, -16, nil, nil, nil, nil, nil, nil,
46
- nil ]
47
-
48
- racc_goto_default = [
49
- nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
50
- 12 ]
51
-
52
- racc_reduce_table = [
53
- 0, 0, :racc_error,
54
- 2, 11, :_reduce_1,
55
- 1, 11, :_reduce_2,
56
- 1, 11, :_reduce_none,
57
- 1, 12, :_reduce_none,
58
- 1, 12, :_reduce_none,
59
- 1, 12, :_reduce_none,
60
- 3, 15, :_reduce_7,
61
- 3, 13, :_reduce_8,
62
- 3, 13, :_reduce_9,
63
- 1, 16, :_reduce_10,
64
- 1, 14, :_reduce_none,
65
- 1, 14, :_reduce_none,
66
- 1, 14, :_reduce_none,
67
- 1, 14, :_reduce_none,
68
- 1, 19, :_reduce_15,
69
- 1, 17, :_reduce_16,
70
- 1, 18, :_reduce_17,
71
- 1, 20, :_reduce_18 ]
72
-
73
- racc_reduce_n = 19
74
-
75
- racc_shift_n = 25
76
-
77
- racc_token_table = {
78
- false => 0,
79
- :error => 1,
80
- :SLASH => 2,
81
- :LITERAL => 3,
82
- :SYMBOL => 4,
83
- :LPAREN => 5,
84
- :RPAREN => 6,
85
- :DOT => 7,
86
- :STAR => 8,
87
- :OR => 9 }
88
-
89
- racc_nt_base = 10
90
-
91
- racc_use_result_var = false
92
-
93
- Racc_arg = [
94
- racc_action_table,
95
- racc_action_check,
96
- racc_action_default,
97
- racc_action_pointer,
98
- racc_goto_table,
99
- racc_goto_check,
100
- racc_goto_default,
101
- racc_goto_pointer,
102
- racc_nt_base,
103
- racc_reduce_table,
104
- racc_token_table,
105
- racc_shift_n,
106
- racc_reduce_n,
107
- racc_use_result_var ]
108
-
109
- Racc_token_to_s_table = [
110
- "$end",
111
- "error",
112
- "SLASH",
113
- "LITERAL",
114
- "SYMBOL",
115
- "LPAREN",
116
- "RPAREN",
117
- "DOT",
118
- "STAR",
119
- "OR",
120
- "$start",
121
- "expressions",
122
- "expression",
123
- "or",
124
- "terminal",
125
- "group",
126
- "star",
127
- "symbol",
128
- "literal",
129
- "slash",
130
- "dot" ]
131
-
132
- Racc_debug_parser = false
133
-
134
- ##### State transition tables end #####
135
-
136
- # reduce 0 omitted
137
-
138
- def _reduce_1(val, _values)
139
- Cat.new(val.first, val.last)
140
- end
141
-
142
- def _reduce_2(val, _values)
143
- val.first
144
- end
145
-
146
- # reduce 3 omitted
147
-
148
- # reduce 4 omitted
149
-
150
- # reduce 5 omitted
151
-
152
- # reduce 6 omitted
153
-
154
- def _reduce_7(val, _values)
155
- Group.new(val[1])
156
- end
157
-
158
- def _reduce_8(val, _values)
159
- Or.new([val.first, val.last])
160
- end
161
-
162
- def _reduce_9(val, _values)
163
- Or.new([val.first, val.last])
164
- end
165
-
166
- def _reduce_10(val, _values)
167
- Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP))
7
+ module Journey # :nodoc:
8
+ class Parser # :nodoc:
9
+ include Journey::Nodes
10
+
11
+ def self.parse(string)
12
+ new.parse string
13
+ end
14
+
15
+ def initialize
16
+ @scanner = Scanner.new
17
+ @next_token = nil
18
+ end
19
+
20
+ def parse(string)
21
+ @scanner.scan_setup(string)
22
+ advance_token
23
+ do_parse
24
+ end
25
+
26
+ private
27
+ def advance_token
28
+ @next_token = @scanner.next_token
29
+ end
30
+
31
+ def do_parse
32
+ parse_expressions
33
+ end
34
+
35
+ def parse_expressions
36
+ node = parse_expression
37
+
38
+ while @next_token
39
+ case @next_token
40
+ when :RPAREN
41
+ break
42
+ when :OR
43
+ node = parse_or(node)
44
+ else
45
+ node = Cat.new(node, parse_expressions)
46
+ end
47
+ end
48
+
49
+ node
50
+ end
51
+
52
+ def parse_or(lhs)
53
+ advance_token
54
+ node = parse_expression
55
+ Or.new([lhs, node])
56
+ end
57
+
58
+ def parse_expression
59
+ if @next_token == :STAR
60
+ parse_star
61
+ elsif @next_token == :LPAREN
62
+ parse_group
63
+ else
64
+ parse_terminal
65
+ end
66
+ end
67
+
68
+ def parse_star
69
+ node = Star.new(Symbol.new(@scanner.last_string, Symbol::GREEDY_EXP))
70
+ advance_token
71
+ node
72
+ end
73
+
74
+ def parse_group
75
+ advance_token
76
+ node = parse_expressions
77
+ if @next_token == :RPAREN
78
+ node = Group.new(node)
79
+ advance_token
80
+ node
81
+ else
82
+ raise ArgumentError, "missing right parenthesis."
83
+ end
84
+ end
85
+
86
+ def parse_terminal
87
+ node = case @next_token
88
+ when :SYMBOL
89
+ Symbol.new(@scanner.last_string)
90
+ when :LITERAL
91
+ Literal.new(@scanner.last_literal)
92
+ when :SLASH
93
+ Slash.new("/")
94
+ when :DOT
95
+ Dot.new(".")
96
+ end
97
+
98
+ advance_token
99
+ node
100
+ end
101
+ end
102
+ end
168
103
  end
169
-
170
- # reduce 11 omitted
171
-
172
- # reduce 12 omitted
173
-
174
- # reduce 13 omitted
175
-
176
- # reduce 14 omitted
177
-
178
- def _reduce_15(val, _values)
179
- Slash.new(val.first)
180
- end
181
-
182
- def _reduce_16(val, _values)
183
- Symbol.new(val.first)
184
- end
185
-
186
- def _reduce_17(val, _values)
187
- Literal.new(val.first)
188
- end
189
-
190
- def _reduce_18(val, _values)
191
- Dot.new(val.first)
192
- end
193
-
194
- def _reduce_none(val, _values)
195
- val[0]
196
- end
197
-
198
- end # class Parser
199
- end # module Journey
200
- end # module ActionDispatch
@@ -7,64 +7,62 @@ require "strscan"
7
7
  module ActionDispatch
8
8
  module Journey # :nodoc:
9
9
  class Scanner # :nodoc:
10
+ STATIC_TOKENS = Array.new(150)
11
+ STATIC_TOKENS[".".ord] = :DOT
12
+ STATIC_TOKENS["/".ord] = :SLASH
13
+ STATIC_TOKENS["(".ord] = :LPAREN
14
+ STATIC_TOKENS[")".ord] = :RPAREN
15
+ STATIC_TOKENS["|".ord] = :OR
16
+ STATIC_TOKENS[":".ord] = :SYMBOL
17
+ STATIC_TOKENS["*".ord] = :STAR
18
+ STATIC_TOKENS.freeze
19
+
20
+ class Scanner < StringScanner
21
+ unless method_defined?(:peek_byte) # https://github.com/ruby/strscan/pull/89
22
+ def peek_byte
23
+ string.getbyte(pos)
24
+ end
25
+ end
26
+ end
27
+
10
28
  def initialize
11
- @ss = nil
29
+ @scanner = nil
30
+ @length = nil
12
31
  end
13
32
 
14
33
  def scan_setup(str)
15
- @ss = StringScanner.new(str)
34
+ @scanner = Scanner.new(str)
16
35
  end
17
36
 
18
- def eos?
19
- @ss.eos?
20
- end
37
+ def next_token
38
+ return if @scanner.eos?
21
39
 
22
- def pos
23
- @ss.pos
40
+ until token = scan || @scanner.eos?; end
41
+ token
24
42
  end
25
43
 
26
- def pre_match
27
- @ss.pre_match
44
+ def last_string
45
+ -@scanner.string.byteslice(@scanner.pos - @length, @length)
28
46
  end
29
47
 
30
- def next_token
31
- return if @ss.eos?
32
-
33
- until token = scan || @ss.eos?; end
34
- token
48
+ def last_literal
49
+ last_str = @scanner.string.byteslice(@scanner.pos - @length, @length)
50
+ last_str.tr! "\\", ""
51
+ -last_str
35
52
  end
36
53
 
37
54
  private
38
- # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
39
- # https://bugs.ruby-lang.org/issues/13077
40
- def dedup_scan(regex)
41
- r = @ss.scan(regex)
42
- r ? -r : nil
43
- end
44
-
45
55
  def scan
56
+ next_byte = @scanner.peek_byte
46
57
  case
47
- # /
48
- when @ss.skip(/\//)
49
- [:SLASH, "/"]
50
- when @ss.skip(/\(/)
51
- [:LPAREN, "("]
52
- when @ss.skip(/\)/)
53
- [:RPAREN, ")"]
54
- when @ss.skip(/\|/)
55
- [:OR, "|"]
56
- when @ss.skip(/\./)
57
- [:DOT, "."]
58
- when text = dedup_scan(/:\w+/)
59
- [:SYMBOL, text]
60
- when text = dedup_scan(/\*\w+/)
61
- [:STAR, text]
62
- when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
63
- text.tr! "\\", ""
64
- [:LITERAL, -text]
65
- # any char
66
- when text = dedup_scan(/./)
67
- [:LITERAL, text]
58
+ when (token = STATIC_TOKENS[next_byte])
59
+ @scanner.pos += 1
60
+ @length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR
61
+ token
62
+ when @length = @scanner.skip(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
63
+ :LITERAL
64
+ when @length = @scanner.skip(/./)
65
+ :LITERAL
68
66
  end
69
67
  end
70
68
  end
@@ -116,13 +116,15 @@ module ActionDispatch
116
116
  # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
117
117
  #
118
118
  # # Sets a signed cookie, which prevents users from tampering with its value.
119
- # # It can be read using the signed method `cookies.signed[:name]`
120
119
  # cookies.signed[:user_id] = current_user.id
120
+ # # It can be read using the signed method.
121
+ # cookies.signed[:user_id] # => 123
121
122
  #
122
123
  # # Sets an encrypted cookie value before sending it to the client which
123
124
  # # prevent users from reading and tampering with its value.
124
- # # It can be read using the encrypted method `cookies.encrypted[:name]`
125
125
  # cookies.encrypted[:discount] = 45
126
+ # # It can be read using the encrypted method.
127
+ # cookies.encrypted[:discount] # => 45
126
128
  #
127
129
  # # Sets a "permanent" cookie (which expires in 20 years from now).
128
130
  # cookies.permanent[:login] = "XJ-122"
@@ -142,17 +142,30 @@ module ActionDispatch
142
142
 
143
143
  message = []
144
144
  message << " "
145
- message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
146
145
  if wrapper.has_cause?
147
- message << "\nCauses:"
146
+ message << "#{wrapper.exception_class_name} (#{wrapper.message})"
148
147
  wrapper.wrapped_causes.each do |wrapped_cause|
149
- message << "#{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
148
+ message << "Caused by: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
150
149
  end
150
+
151
+ message << "\nInformation for: #{wrapper.exception_class_name} (#{wrapper.message}):"
152
+ else
153
+ message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
151
154
  end
155
+
152
156
  message.concat(wrapper.annotated_source_code)
153
157
  message << " "
154
158
  message.concat(trace)
155
159
 
160
+ if wrapper.has_cause?
161
+ wrapper.wrapped_causes.each do |wrapped_cause|
162
+ message << "\nInformation for cause: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message}):"
163
+ message.concat(wrapped_cause.annotated_source_code)
164
+ message << " "
165
+ message.concat(wrapped_cause.exception_trace)
166
+ end
167
+ end
168
+
156
169
  log_array(logger, message, request)
157
170
  end
158
171
 
@@ -25,11 +25,12 @@ module ActionDispatch
25
25
  def initialize(app, header:)
26
26
  @app = app
27
27
  @header = header
28
+ @env_header = "HTTP_#{header.upcase.tr("-", "_")}"
28
29
  end
29
30
 
30
31
  def call(env)
31
32
  req = ActionDispatch::Request.new env
32
- req.request_id = make_request_id(req.headers[@header])
33
+ req.request_id = make_request_id(req.get_header(@env_header))
33
34
  @app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
34
35
  end
35
36