actionpack 8.0.4 → 8.1.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -173
  3. data/lib/abstract_controller/asset_paths.rb +4 -2
  4. data/lib/abstract_controller/base.rb +10 -2
  5. data/lib/abstract_controller/caching.rb +6 -3
  6. data/lib/abstract_controller/helpers.rb +1 -1
  7. data/lib/abstract_controller/logger.rb +2 -1
  8. data/lib/action_controller/base.rb +1 -1
  9. data/lib/action_controller/caching.rb +1 -2
  10. data/lib/action_controller/form_builder.rb +1 -1
  11. data/lib/action_controller/log_subscriber.rb +7 -0
  12. data/lib/action_controller/metal/allow_browser.rb +1 -1
  13. data/lib/action_controller/metal/conditional_get.rb +25 -0
  14. data/lib/action_controller/metal/data_streaming.rb +1 -3
  15. data/lib/action_controller/metal/exceptions.rb +5 -0
  16. data/lib/action_controller/metal/flash.rb +1 -4
  17. data/lib/action_controller/metal/head.rb +3 -1
  18. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  19. data/lib/action_controller/metal/rate_limiting.rb +22 -7
  20. data/lib/action_controller/metal/redirecting.rb +61 -5
  21. data/lib/action_controller/metal/renderers.rb +27 -6
  22. data/lib/action_controller/metal/rendering.rb +7 -1
  23. data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
  24. data/lib/action_controller/metal/rescue.rb +9 -0
  25. data/lib/action_controller/railtie.rb +2 -6
  26. data/lib/action_dispatch/http/cache.rb +111 -1
  27. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  28. data/lib/action_dispatch/http/mime_types.rb +1 -0
  29. data/lib/action_dispatch/http/param_builder.rb +28 -27
  30. data/lib/action_dispatch/http/parameters.rb +3 -3
  31. data/lib/action_dispatch/http/permissions_policy.rb +4 -0
  32. data/lib/action_dispatch/http/query_parser.rb +12 -10
  33. data/lib/action_dispatch/http/request.rb +10 -5
  34. data/lib/action_dispatch/http/response.rb +16 -3
  35. data/lib/action_dispatch/http/url.rb +99 -3
  36. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  37. data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -43
  38. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  39. data/lib/action_dispatch/journey/route.rb +45 -31
  40. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  41. data/lib/action_dispatch/journey/router.rb +59 -81
  42. data/lib/action_dispatch/journey/routes.rb +7 -0
  43. data/lib/action_dispatch/journey/visitors.rb +55 -23
  44. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  45. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  46. data/lib/action_dispatch/middleware/debug_exceptions.rb +7 -1
  47. data/lib/action_dispatch/middleware/debug_view.rb +11 -0
  48. data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -5
  49. data/lib/action_dispatch/middleware/executor.rb +12 -2
  50. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  51. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  52. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  53. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -2
  54. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  55. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  56. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  57. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +1 -0
  58. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  59. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  60. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  61. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  62. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  63. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  64. data/lib/action_dispatch/railtie.rb +10 -2
  65. data/lib/action_dispatch/routing/inspector.rb +4 -1
  66. data/lib/action_dispatch/routing/mapper.rb +323 -173
  67. data/lib/action_dispatch/routing/route_set.rb +2 -4
  68. data/lib/action_dispatch/routing/routes_proxy.rb +0 -1
  69. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  70. data/lib/action_dispatch/testing/assertions/response.rb +14 -0
  71. data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
  72. data/lib/action_dispatch/testing/integration.rb +4 -3
  73. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  74. data/lib/action_pack/gem_version.rb +3 -3
  75. metadata +11 -10
@@ -47,25 +47,27 @@ module ActionDispatch
47
47
  Array(t)
48
48
  end
49
49
 
50
- def move(t, full_string, start_index, end_index)
50
+ def move(t, full_string, token, start_index, token_matches_default)
51
51
  return [] if t.empty?
52
52
 
53
53
  next_states = []
54
54
 
55
- tok = full_string.slice(start_index, end_index - start_index)
56
- token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
57
-
58
- t.each { |s, previous_start|
55
+ transitions_count = t.size
56
+ i = 0
57
+ while i < transitions_count
58
+ s = t[i]
59
+ previous_start = t[i + 1]
59
60
  if previous_start.nil?
60
61
  # In the simple case of a "default" param regex do this fast-path and add all
61
62
  # next states.
62
- if token_matches_default_component && states = @stdparam_states[s]
63
- states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
63
+ if token_matches_default && std_state = @stdparam_states[s]
64
+ next_states << std_state << nil
64
65
  end
65
66
 
66
67
  # When we have a literal string, we can just pull the next state
67
68
  if states = @string_states[s]
68
- next_states << [states[tok], nil].freeze unless states[tok].nil?
69
+ state = states[token]
70
+ next_states << state << nil unless state.nil?
69
71
  end
70
72
  end
71
73
 
@@ -80,19 +82,21 @@ module ActionDispatch
80
82
  previous_start
81
83
  end
82
84
 
83
- slice_length = end_index - slice_start
85
+ slice_length = start_index + token.length - slice_start
84
86
  curr_slice = full_string.slice(slice_start, slice_length)
85
87
 
86
88
  states.each { |re, v|
87
89
  # if we match, we can try moving past this
88
- next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
90
+ next_states << v << nil if !v.nil? && re.match?(curr_slice)
89
91
  }
90
92
 
91
93
  # and regardless, we must continue accepting tokens and retrying this regexp. we
92
94
  # need to remember where we started as well so we can take bigger slices.
93
- next_states << [s, slice_start].freeze
95
+ next_states << s << slice_start
94
96
  end
95
- }
97
+
98
+ i += 2
99
+ end
96
100
 
97
101
  next_states
98
102
  end
@@ -107,10 +111,10 @@ module ActionDispatch
107
111
  end
108
112
 
109
113
  {
110
- regexp_states: simple_regexp.stringify_keys,
111
- string_states: @string_states.stringify_keys,
112
- stdparam_states: @stdparam_states.stringify_keys,
113
- accepting: @accepting.stringify_keys
114
+ regexp_states: simple_regexp,
115
+ string_states: @string_states,
116
+ stdparam_states: @stdparam_states,
117
+ accepting: @accepting
114
118
  }
115
119
  end
116
120
 
@@ -163,25 +167,27 @@ module ActionDispatch
163
167
  end
164
168
 
165
169
  def []=(from, to, sym)
166
- to_mappings = states_hash_for(sym)[from] ||= {}
167
170
  case sym
171
+ when String, Symbol
172
+ to_mapping = @string_states[from] ||= {}
173
+ # account for symbols in the constraints the same as strings
174
+ to_mapping[sym.to_s] = to
168
175
  when Regexp
169
- # we must match the whole string to a token boundary
170
176
  if sym == DEFAULT_EXP
171
- sym = DEFAULT_EXP_ANCHORED
177
+ @stdparam_states[from] = to
172
178
  else
173
- sym = /\A#{sym}\Z/
179
+ to_mapping = @regexp_states[from] ||= {}
180
+ # we must match the whole string to a token boundary
181
+ to_mapping[/\A#{sym}\Z/] = to
174
182
  end
175
- when Symbol
176
- # account for symbols in the constraints the same as strings
177
- sym = sym.to_s
183
+ else
184
+ raise ArgumentError, "unknown symbol: %s" % sym.class
178
185
  end
179
- to_mappings[sym] = to
180
186
  end
181
187
 
182
188
  def states
183
189
  ss = @string_states.keys + @string_states.values.flat_map(&:values)
184
- ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
190
+ ps = @stdparam_states.keys + @stdparam_states.values
185
191
  rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
186
192
  (ss + ps + rs).uniq
187
193
  end
@@ -189,28 +195,12 @@ module ActionDispatch
189
195
  def transitions
190
196
  @string_states.flat_map { |from, hash|
191
197
  hash.map { |s, to| [from, s, to] }
192
- } + @stdparam_states.flat_map { |from, hash|
193
- hash.map { |s, to| [from, s, to] }
198
+ } + @stdparam_states.map { |from, to|
199
+ [from, DEFAULT_EXP_ANCHORED, to]
194
200
  } + @regexp_states.flat_map { |from, hash|
195
201
  hash.map { |s, to| [from, s, to] }
196
202
  }
197
203
  end
198
-
199
- private
200
- def states_hash_for(sym)
201
- case sym
202
- when String, Symbol
203
- @string_states
204
- when Regexp
205
- if sym == DEFAULT_EXP
206
- @stdparam_states
207
- else
208
- @regexp_states
209
- end
210
- else
211
- raise ArgumentError, "unknown symbol: %s" % sym.class
212
- end
213
- end
214
204
  end
215
205
  end
216
206
  end
@@ -74,6 +74,7 @@ module ActionDispatch
74
74
  def initialize(left)
75
75
  @left = left
76
76
  @memo = nil
77
+ @to_s = nil
77
78
  end
78
79
 
79
80
  def each(&block)
@@ -81,7 +82,7 @@ module ActionDispatch
81
82
  end
82
83
 
83
84
  def to_s
84
- Visitors::String::INSTANCE.accept(self, "")
85
+ @to_s ||= Visitors::String::INSTANCE.accept(self, "".dup).freeze
85
86
  end
86
87
 
87
88
  def to_dot
@@ -38,29 +38,50 @@ module ActionDispatch
38
38
  def self.verb; ""; end
39
39
  end
40
40
 
41
+ class Or
42
+ attr_reader :verb
43
+
44
+ def initialize(verbs)
45
+ @verbs = verbs
46
+ @verb = @verbs.map(&:verb).join("|")
47
+ end
48
+
49
+ def call(req)
50
+ @verbs.any? { |v| v.call req }
51
+ end
52
+ end
53
+
41
54
  VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
42
55
  klass = const_get verb
43
56
  hash[verb] = klass
44
57
  hash[verb.downcase] = klass
45
58
  hash[verb.downcase.to_sym] = klass
46
59
  end
47
- end
48
60
 
49
- def self.verb_matcher(verb)
50
- VerbMatchers::VERB_TO_CLASS.fetch(verb) do
61
+ VERB_TO_CLASS.default_proc = proc do |_, verb|
51
62
  VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
52
63
  end
64
+
65
+ def self.for(verbs)
66
+ if verbs.any? { |v| VERB_TO_CLASS[v] == All }
67
+ All
68
+ elsif verbs.one?
69
+ VERB_TO_CLASS[verbs.first]
70
+ else
71
+ Or.new(verbs.map { |v| VERB_TO_CLASS[v] })
72
+ end
73
+ end
53
74
  end
54
75
 
55
76
  ##
56
77
  # +path+ is a path constraint.
57
78
  # `constraints` is a hash of constraints to be applied to this route.
58
- def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
79
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, via: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
59
80
  @name = name
60
81
  @app = app
61
82
  @path = path
62
83
 
63
- @request_method_match = request_method_match
84
+ @request_method_match = via && VerbMatchers.for(via)
64
85
  @constraints = constraints
65
86
  @defaults = defaults
66
87
  @required_defaults = nil
@@ -146,21 +167,23 @@ module ActionDispatch
146
167
  end
147
168
 
148
169
  def matches?(request)
149
- match_verb(request) &&
150
- constraints.all? { |method, value|
151
- case value
152
- when Regexp, String
153
- value === request.send(method).to_s
154
- when Array
155
- value.include?(request.send(method))
156
- when TrueClass
157
- request.send(method).present?
158
- when FalseClass
159
- request.send(method).blank?
160
- else
161
- value === request.send(method)
162
- end
163
- }
170
+ @request_method_match.call(request) && (
171
+ constraints.empty? ||
172
+ constraints.all? { |method, value|
173
+ case value
174
+ when Regexp, String
175
+ value === request.send(method).to_s
176
+ when Array
177
+ value.include?(request.send(method))
178
+ when TrueClass
179
+ request.send(method).present?
180
+ when FalseClass
181
+ request.send(method).blank?
182
+ else
183
+ value === request.send(method)
184
+ end
185
+ }
186
+ )
164
187
  end
165
188
 
166
189
  def ip
@@ -168,21 +191,12 @@ module ActionDispatch
168
191
  end
169
192
 
170
193
  def requires_matching_verb?
171
- !@request_method_match.all? { |x| x == VerbMatchers::All }
194
+ @request_method_match != VerbMatchers::All
172
195
  end
173
196
 
174
197
  def verb
175
- verbs.join("|")
198
+ @request_method_match.verb
176
199
  end
177
-
178
- private
179
- def verbs
180
- @request_method_match.map(&:verb)
181
- end
182
-
183
- def match_verb(request)
184
- @request_method_match.any? { |m| m.call request }
185
- end
186
200
  end
187
201
  end
188
202
  # :startdoc:
@@ -17,7 +17,14 @@ module ActionDispatch
17
17
  # normalize_path("") # => "/"
18
18
  # normalize_path("/%ab") # => "/%AB"
19
19
  def self.normalize_path(path)
20
- path ||= ""
20
+ return "/".dup unless path
21
+
22
+ # Fast path for the overwhelming majority of paths that don't need to be normalized
23
+ if path == "/" || (path.start_with?("/") && !path.end_with?("/") && !path.match?(%r{%|//}))
24
+ return path.dup
25
+ end
26
+
27
+ # Slow path
21
28
  encoding = path.encoding
22
29
  path = +"/#{path}"
23
30
  path.squeeze!("/")
@@ -61,11 +68,6 @@ module ActionDispatch
61
68
  escape(segment, SEGMENT)
62
69
  end
63
70
 
64
- def unescape_uri(uri)
65
- encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
66
- uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
67
- end
68
-
69
71
  private
70
72
  def escape(component, pattern)
71
73
  component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
@@ -91,14 +93,6 @@ module ActionDispatch
91
93
  def self.escape_fragment(fragment)
92
94
  ENCODER.escape_fragment(fragment.to_s)
93
95
  end
94
-
95
- # Replaces any escaped sequences with their unescaped representations.
96
- #
97
- # uri = "/topics?title=Ruby%20on%20Rails"
98
- # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
99
- def self.unescape_uri(uri)
100
- ENCODER.unescape_uri(uri)
101
- end
102
96
  end
103
97
  end
104
98
  end
@@ -2,15 +2,12 @@
2
2
 
3
3
  # :markup: markdown
4
4
 
5
+ require "cgi/escape"
6
+ require "cgi/util" if RUBY_VERSION < "3.5"
5
7
  require "action_dispatch/journey/router/utils"
6
8
  require "action_dispatch/journey/routes"
7
9
  require "action_dispatch/journey/formatter"
8
-
9
- before = $-w
10
- $-w = false
11
10
  require "action_dispatch/journey/parser"
12
- $-w = before
13
-
14
11
  require "action_dispatch/journey/route"
15
12
  require "action_dispatch/journey/path/pattern"
16
13
 
@@ -31,71 +28,78 @@ module ActionDispatch
31
28
  end
32
29
 
33
30
  def serve(req)
34
- find_routes(req) do |match, parameters, route|
35
- set_params = req.path_parameters
36
- path_info = req.path_info
37
- script_name = req.script_name
38
-
39
- unless route.path.anchored
40
- req.script_name = (script_name.to_s + match.to_s).chomp("/")
41
- req.path_info = match.post_match
42
- req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
43
- end
44
-
45
- tmp_params = set_params.merge route.defaults
46
- parameters.each_pair { |key, val|
47
- tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
48
- }
49
-
50
- req.path_parameters = tmp_params
51
- req.route_uri_pattern = route.path.spec.to_s
31
+ recognize(req) do |route, parameters|
32
+ req.path_parameters = parameters
33
+ req.route = route
52
34
 
53
35
  _, headers, _ = response = route.app.serve(req)
54
36
 
55
- if "pass" == headers[Constants::X_CASCADE]
56
- req.script_name = script_name
57
- req.path_info = path_info
58
- req.path_parameters = set_params
59
- next
60
- end
61
-
62
- return response
37
+ return response unless headers[Constants::X_CASCADE] == "pass"
63
38
  end
64
39
 
65
40
  [404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
66
41
  end
67
42
 
68
- def recognize(rails_req)
69
- find_routes(rails_req) do |match, parameters, route|
70
- unless route.path.anchored
71
- rails_req.script_name = match.to_s
72
- rails_req.path_info = match.post_match
73
- rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
74
- end
43
+ def recognize(req, &block)
44
+ req_params = req.path_parameters
45
+ path_info = req.path_info
46
+ script_name = req.script_name
75
47
 
76
- parameters = route.defaults.merge parameters
77
- yield(route, parameters)
78
- end
79
- end
48
+ routes = filter_routes(path_info)
80
49
 
81
- def visualizer
82
- tt = GTG::Builder.new(ast).transition_table
83
- groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
84
- asts = groups.values.map(&:first)
85
- tt.visualizer(asts)
86
- end
50
+ custom_routes.each { |r|
51
+ routes << r if r.path.match?(path_info)
52
+ }
87
53
 
88
- private
89
- def partitioned_routes
90
- routes.partition { |r|
91
- r.path.anchored && r.path.requirements_anchored?
92
- }
54
+ if req.head?
55
+ routes = match_head_routes(routes, req)
56
+ else
57
+ routes.select! { |r| r.matches?(req) }
93
58
  end
94
59
 
95
- def ast
96
- routes.ast
60
+ if routes.size > 1
61
+ routes.sort! do |a, b|
62
+ a.precedence <=> b.precedence
63
+ end
97
64
  end
98
65
 
66
+ routes.each do |r|
67
+ match_data = r.path.match(path_info)
68
+
69
+ path_parameters = req_params.merge r.defaults
70
+
71
+ index = 1
72
+ match_data.names.each do |name|
73
+ if val = match_data[index]
74
+ val = if val.include?("%")
75
+ CGI.unescapeURIComponent(val)
76
+ else
77
+ val
78
+ end
79
+ val.force_encoding(::Encoding::UTF_8)
80
+ path_parameters[name.to_sym] = val
81
+ end
82
+ index += 1
83
+ end
84
+
85
+ if r.path.anchored
86
+ yield(r, path_parameters)
87
+ else
88
+ req.script_name = (script_name.to_s + match_data.to_s).chomp("/")
89
+ req.path_info = match_data.post_match
90
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
91
+
92
+ yield(r, path_parameters)
93
+
94
+ req.script_name = script_name
95
+ req.path_info = path_info
96
+ end
97
+
98
+ req.path_parameters = req_params
99
+ end
100
+ end
101
+
102
+ private
99
103
  def simulator
100
104
  routes.simulator
101
105
  end
@@ -105,35 +109,9 @@ module ActionDispatch
105
109
  end
106
110
 
107
111
  def filter_routes(path)
108
- return [] unless ast
109
112
  simulator.memos(path) { [] }
110
113
  end
111
114
 
112
- def find_routes(req)
113
- path_info = req.path_info
114
- routes = filter_routes(path_info).concat custom_routes.find_all { |r|
115
- r.path.match?(path_info)
116
- }
117
-
118
- if req.head?
119
- routes = match_head_routes(routes, req)
120
- else
121
- routes.select! { |r| r.matches?(req) }
122
- end
123
-
124
- routes.sort_by!(&:precedence)
125
-
126
- routes.each { |r|
127
- match_data = r.path.match(path_info)
128
- path_parameters = {}
129
- match_data.names.each_with_index { |name, i|
130
- val = match_data[i + 1]
131
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
132
- }
133
- yield [match_data, path_parameters, r]
134
- }
135
- end
136
-
137
115
  def match_head_routes(routes, req)
138
116
  head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
139
117
  return head_routes unless head_routes.empty?
@@ -72,6 +72,13 @@ module ActionDispatch
72
72
  route
73
73
  end
74
74
 
75
+ def visualizer
76
+ tt = GTG::Builder.new(ast).transition_table
77
+ groups = anchored_routes.map(&:ast).group_by(&:to_s)
78
+ asts = groups.values.map(&:first)
79
+ tt.visualizer(asts)
80
+ end
81
+
75
82
  private
76
83
  def clear_cache!
77
84
  @ast = nil
@@ -128,8 +128,8 @@ module ActionDispatch
128
128
  def visit_DOT(n, seed); terminal(n, seed); end
129
129
 
130
130
  instance_methods(false).each do |pim|
131
- next unless pim =~ /^visit_(.*)$/
132
- DISPATCH_CACHE[$1.to_sym] = pim
131
+ next unless pim.start_with?("visit_")
132
+ DISPATCH_CACHE[pim.name.delete_prefix("visit_").to_sym] = pim
133
133
  end
134
134
  end
135
135
 
@@ -167,32 +167,64 @@ module ActionDispatch
167
167
  INSTANCE = new
168
168
  end
169
169
 
170
- class String < FunctionalVisitor # :nodoc:
171
- private
172
- def binary(node, seed)
173
- visit(node.right, visit(node.left, seed))
174
- end
175
-
176
- def nary(node, seed)
170
+ class String # :nodoc:
171
+ def accept(node, seed)
172
+ case node.type
173
+ when :DOT
174
+ seed << node.left
175
+ when :LITERAL
176
+ seed << node.left
177
+ when :SYMBOL
178
+ seed << node.left
179
+ when :SLASH
180
+ seed << node.left
181
+ when :CAT
182
+ accept(node.right, accept(node.left, seed))
183
+ when :STAR
184
+ accept(node.left, seed)
185
+ when :OR
177
186
  last_child = node.children.last
178
- node.children.inject(seed) { |s, c|
179
- string = visit(c, s)
180
- string << "|" unless last_child == c
181
- string
182
- }
183
- end
184
-
185
- def terminal(node, seed)
186
- seed + node.left
187
- end
188
-
189
- def visit_GROUP(node, seed)
190
- visit(node.left, seed.dup << "(") << ")"
187
+ node.children.each do |c|
188
+ accept(c, seed)
189
+ seed << "|" unless last_child == c
190
+ end
191
+ seed
192
+ when :GROUP
193
+ accept(node.left, seed << "(") << ")"
194
+ else
195
+ raise "Unknown node type: #{node.type}"
191
196
  end
197
+ end
192
198
 
193
- INSTANCE = new
199
+ INSTANCE = new
194
200
  end
195
201
 
202
+ # class String < FunctionalVisitor # :nodoc:
203
+ # private
204
+ # def binary(node, seed)
205
+ # visit(node.right, visit(node.left, seed))
206
+ # end
207
+ #
208
+ # def nary(node, seed)
209
+ # last_child = node.children.last
210
+ # node.children.inject(seed) { |s, c|
211
+ # string = visit(c, s)
212
+ # string << "|" unless last_child == c
213
+ # string
214
+ # }
215
+ # end
216
+ #
217
+ # def terminal(node, seed)
218
+ # seed + node.left
219
+ # end
220
+ #
221
+ # def visit_GROUP(node, seed)
222
+ # visit(node.left, seed.dup << "(") << ")"
223
+ # end
224
+ #
225
+ # INSTANCE = new
226
+ # end
227
+
196
228
  class Dot < FunctionalVisitor # :nodoc:
197
229
  def initialize
198
230
  @nodes = []
@@ -105,12 +105,10 @@ function match(input) {
105
105
  }
106
106
 
107
107
  if(stdparam_states[state] && default_re.test(token)) {
108
- for(var key in stdparam_states[state]) {
109
- var new_state = stdparam_states[state][key];
110
- highlight_edge(state, new_state);
111
- highlight_state(new_state);
112
- new_states.push([new_state, null]);
113
- }
108
+ var new_state = stdparam_states[state];
109
+ highlight_edge(state, new_state);
110
+ highlight_state(new_state);
111
+ new_states.push([new_state, null]);
114
112
  }
115
113
  }
116
114
 
@@ -610,8 +610,10 @@ module ActionDispatch
610
610
  end
611
611
 
612
612
  def check_for_overflow!(name, options)
613
- if options[:value].bytesize > MAX_COOKIE_SIZE
614
- raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
613
+ total_size = name.to_s.bytesize + options[:value].bytesize
614
+
615
+ if total_size > MAX_COOKIE_SIZE
616
+ raise CookieOverflow, "#{name} cookie overflowed with size #{total_size} bytes"
615
617
  end
616
618
  end
617
619
  end
@@ -127,6 +127,7 @@ module ActionDispatch
127
127
  trace_to_show: wrapper.trace_to_show,
128
128
  routes_inspector: routes_inspector(wrapper),
129
129
  source_extracts: wrapper.source_extracts,
130
+ exception_message_for_copy: compose_exception_message(wrapper).join("\n"),
130
131
  )
131
132
  end
132
133
 
@@ -140,6 +141,11 @@ module ActionDispatch
140
141
  return unless logger
141
142
  return if !log_rescued_responses?(request) && wrapper.rescue_response?
142
143
 
144
+ message = compose_exception_message(wrapper)
145
+ log_array(logger, message, request)
146
+ end
147
+
148
+ def compose_exception_message(wrapper)
143
149
  trace = wrapper.exception_trace
144
150
 
145
151
  message = []
@@ -168,7 +174,7 @@ module ActionDispatch
168
174
  end
169
175
  end
170
176
 
171
- log_array(logger, message, request)
177
+ message
172
178
  end
173
179
 
174
180
  def log_array(logger, lines, request)