actionpack 7.2.2.1 → 8.1.2

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +408 -95
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +4 -2
  5. data/lib/abstract_controller/base.rb +12 -17
  6. data/lib/abstract_controller/caching.rb +6 -3
  7. data/lib/abstract_controller/callbacks.rb +6 -0
  8. data/lib/abstract_controller/collector.rb +1 -1
  9. data/lib/abstract_controller/helpers.rb +1 -1
  10. data/lib/abstract_controller/logger.rb +2 -1
  11. data/lib/abstract_controller/rendering.rb +0 -1
  12. data/lib/action_controller/api.rb +1 -0
  13. data/lib/action_controller/base.rb +3 -2
  14. data/lib/action_controller/caching.rb +1 -2
  15. data/lib/action_controller/form_builder.rb +4 -4
  16. data/lib/action_controller/log_subscriber.rb +22 -3
  17. data/lib/action_controller/metal/allow_browser.rb +12 -2
  18. data/lib/action_controller/metal/conditional_get.rb +30 -1
  19. data/lib/action_controller/metal/data_streaming.rb +5 -5
  20. data/lib/action_controller/metal/exceptions.rb +5 -0
  21. data/lib/action_controller/metal/flash.rb +1 -4
  22. data/lib/action_controller/metal/head.rb +3 -1
  23. data/lib/action_controller/metal/instrumentation.rb +1 -2
  24. data/lib/action_controller/metal/live.rb +66 -26
  25. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  26. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  27. data/lib/action_controller/metal/rate_limiting.rb +39 -9
  28. data/lib/action_controller/metal/redirecting.rb +109 -16
  29. data/lib/action_controller/metal/renderers.rb +29 -9
  30. data/lib/action_controller/metal/rendering.rb +8 -2
  31. data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
  32. data/lib/action_controller/metal/rescue.rb +9 -0
  33. data/lib/action_controller/metal/streaming.rb +5 -84
  34. data/lib/action_controller/metal/strong_parameters.rb +277 -92
  35. data/lib/action_controller/railtie.rb +33 -15
  36. data/lib/action_controller/renderer.rb +0 -1
  37. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  38. data/lib/action_controller/test_case.rb +12 -2
  39. data/lib/action_dispatch/constants.rb +6 -0
  40. data/lib/action_dispatch/http/cache.rb +138 -11
  41. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  42. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  43. data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
  44. data/lib/action_dispatch/http/mime_types.rb +1 -0
  45. data/lib/action_dispatch/http/param_builder.rb +187 -0
  46. data/lib/action_dispatch/http/param_error.rb +26 -0
  47. data/lib/action_dispatch/http/parameters.rb +3 -3
  48. data/lib/action_dispatch/http/permissions_policy.rb +6 -0
  49. data/lib/action_dispatch/http/query_parser.rb +55 -0
  50. data/lib/action_dispatch/http/request.rb +73 -23
  51. data/lib/action_dispatch/http/response.rb +65 -17
  52. data/lib/action_dispatch/http/url.rb +112 -16
  53. data/lib/action_dispatch/journey/formatter.rb +8 -3
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
  56. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  57. data/lib/action_dispatch/journey/parser.rb +99 -196
  58. data/lib/action_dispatch/journey/route.rb +45 -31
  59. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  60. data/lib/action_dispatch/journey/router.rb +59 -81
  61. data/lib/action_dispatch/journey/routes.rb +7 -0
  62. data/lib/action_dispatch/journey/scanner.rb +44 -42
  63. data/lib/action_dispatch/journey/visitors.rb +55 -23
  64. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  65. data/lib/action_dispatch/log_subscriber.rb +7 -3
  66. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
  68. data/lib/action_dispatch/middleware/debug_view.rb +11 -5
  69. data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
  70. data/lib/action_dispatch/middleware/executor.rb +17 -4
  71. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
  72. data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
  73. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  74. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  75. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  76. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  78. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  79. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  84. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  89. data/lib/action_dispatch/railtie.rb +21 -0
  90. data/lib/action_dispatch/request/session.rb +1 -0
  91. data/lib/action_dispatch/request/utils.rb +9 -3
  92. data/lib/action_dispatch/routing/inspector.rb +80 -57
  93. data/lib/action_dispatch/routing/mapper.rb +409 -228
  94. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  95. data/lib/action_dispatch/routing/redirection.rb +10 -7
  96. data/lib/action_dispatch/routing/route_set.rb +21 -12
  97. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  98. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  99. data/lib/action_dispatch/system_test_case.rb +3 -3
  100. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  101. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  102. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  103. data/lib/action_dispatch/testing/assertions/response.rb +26 -2
  104. data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
  105. data/lib/action_dispatch/testing/integration.rb +16 -7
  106. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  107. data/lib/action_dispatch/testing/test_process.rb +1 -2
  108. data/lib/action_dispatch.rb +14 -4
  109. data/lib/action_pack/gem_version.rb +3 -3
  110. metadata +19 -38
  111. data/lib/action_dispatch/journey/parser.y +0 -50
  112. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -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
@@ -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