actionpack 6.0.3 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +246 -217
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller.rb +1 -0
  5. data/lib/abstract_controller/base.rb +35 -2
  6. data/lib/abstract_controller/callbacks.rb +2 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/rendering.rb +9 -9
  9. data/lib/abstract_controller/translation.rb +8 -2
  10. data/lib/action_controller.rb +2 -3
  11. data/lib/action_controller/api.rb +2 -2
  12. data/lib/action_controller/base.rb +4 -2
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/log_subscriber.rb +3 -3
  15. data/lib/action_controller/metal.rb +2 -2
  16. data/lib/action_controller/metal/conditional_get.rb +10 -2
  17. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  18. data/lib/action_controller/metal/data_streaming.rb +1 -1
  19. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  20. data/lib/action_controller/metal/exceptions.rb +33 -0
  21. data/lib/action_controller/metal/feature_policy.rb +46 -0
  22. data/lib/action_controller/metal/head.rb +7 -4
  23. data/lib/action_controller/metal/helpers.rb +11 -1
  24. data/lib/action_controller/metal/http_authentication.rb +4 -2
  25. data/lib/action_controller/metal/implicit_render.rb +1 -1
  26. data/lib/action_controller/metal/instrumentation.rb +11 -9
  27. data/lib/action_controller/metal/live.rb +1 -1
  28. data/lib/action_controller/metal/logging.rb +20 -0
  29. data/lib/action_controller/metal/mime_responds.rb +6 -2
  30. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  31. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  32. data/lib/action_controller/metal/redirecting.rb +1 -1
  33. data/lib/action_controller/metal/rendering.rb +6 -0
  34. data/lib/action_controller/metal/request_forgery_protection.rb +74 -30
  35. data/lib/action_controller/metal/rescue.rb +1 -1
  36. data/lib/action_controller/metal/strong_parameters.rb +107 -15
  37. data/lib/action_controller/renderer.rb +24 -13
  38. data/lib/action_controller/test_case.rb +62 -56
  39. data/lib/action_dispatch.rb +3 -2
  40. data/lib/action_dispatch/http/cache.rb +12 -10
  41. data/lib/action_dispatch/http/content_disposition.rb +2 -2
  42. data/lib/action_dispatch/http/content_security_policy.rb +5 -1
  43. data/lib/action_dispatch/http/feature_policy.rb +168 -0
  44. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  45. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  46. data/lib/action_dispatch/http/headers.rb +3 -2
  47. data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
  48. data/lib/action_dispatch/http/mime_type.rb +28 -15
  49. data/lib/action_dispatch/http/parameters.rb +1 -19
  50. data/lib/action_dispatch/http/request.rb +26 -8
  51. data/lib/action_dispatch/http/response.rb +17 -16
  52. data/lib/action_dispatch/http/url.rb +3 -2
  53. data/lib/action_dispatch/journey.rb +0 -2
  54. data/lib/action_dispatch/journey/formatter.rb +53 -28
  55. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  56. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  57. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  58. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  59. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  60. data/lib/action_dispatch/journey/parser.rb +13 -13
  61. data/lib/action_dispatch/journey/parser.y +1 -1
  62. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  63. data/lib/action_dispatch/journey/route.rb +7 -18
  64. data/lib/action_dispatch/journey/router.rb +26 -30
  65. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  66. data/lib/action_dispatch/middleware/actionable_exceptions.rb +9 -2
  67. data/lib/action_dispatch/middleware/cookies.rb +74 -33
  68. data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
  69. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  70. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
  71. data/lib/action_dispatch/middleware/host_authorization.rb +23 -3
  72. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
  73. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  74. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  75. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  76. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  77. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  78. data/lib/action_dispatch/middleware/stack.rb +18 -0
  79. data/lib/action_dispatch/middleware/static.rb +154 -93
  80. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  82. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  83. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  84. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
  85. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  86. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  87. data/lib/action_dispatch/railtie.rb +3 -2
  88. data/lib/action_dispatch/request/session.rb +2 -8
  89. data/lib/action_dispatch/request/utils.rb +26 -2
  90. data/lib/action_dispatch/routing/inspector.rb +8 -7
  91. data/lib/action_dispatch/routing/mapper.rb +102 -71
  92. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  93. data/lib/action_dispatch/routing/redirection.rb +3 -3
  94. data/lib/action_dispatch/routing/route_set.rb +49 -41
  95. data/lib/action_dispatch/routing/url_for.rb +1 -0
  96. data/lib/action_dispatch/system_test_case.rb +29 -24
  97. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  98. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  99. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  100. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  101. data/lib/action_dispatch/testing/assertions.rb +1 -1
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  103. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  104. data/lib/action_dispatch/testing/integration.rb +38 -27
  105. data/lib/action_dispatch/testing/test_process.rb +29 -4
  106. data/lib/action_dispatch/testing/test_request.rb +3 -3
  107. data/lib/action_pack.rb +1 -1
  108. data/lib/action_pack/gem_version.rb +3 -3
  109. metadata +20 -21
  110. data/lib/action_controller/metal/force_ssl.rb +0 -58
  111. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  112. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  113. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  114. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -3,5 +3,3 @@
3
3
  require "action_dispatch/journey/router"
4
4
  require "action_dispatch/journey/gtg/builder"
5
5
  require "action_dispatch/journey/gtg/simulator"
6
- require "action_dispatch/journey/nfa/builder"
7
- require "action_dispatch/journey/nfa/simulator"
@@ -15,12 +15,53 @@ module ActionDispatch
15
15
  @cache = nil
16
16
  end
17
17
 
18
- def generate(name, options, path_parameters, parameterize = nil)
18
+ class RouteWithParams
19
+ attr_reader :params
20
+
21
+ def initialize(route, parameterized_parts, params)
22
+ @route = route
23
+ @parameterized_parts = parameterized_parts
24
+ @params = params
25
+ end
26
+
27
+ def path(_)
28
+ @route.format(@parameterized_parts)
29
+ end
30
+ end
31
+
32
+ class MissingRoute
33
+ attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys
34
+
35
+ def initialize(constraints, missing_keys, unmatched_keys, routes, name)
36
+ @constraints = constraints
37
+ @missing_keys = missing_keys
38
+ @unmatched_keys = unmatched_keys
39
+ @routes = routes
40
+ @name = name
41
+ end
42
+
43
+ def path(method_name)
44
+ raise ActionController::UrlGenerationError.new(message, routes, name, method_name)
45
+ end
46
+
47
+ def params
48
+ path("unknown")
49
+ end
50
+
51
+ def message
52
+ message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
53
+ message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
54
+ message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
55
+ message
56
+ end
57
+ end
58
+
59
+ def generate(name, options, path_parameters)
19
60
  constraints = path_parameters.merge(options)
20
61
  missing_keys = nil
21
62
 
22
63
  match_route(name, constraints) do |route|
23
- parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
64
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
24
65
 
25
66
  # Skip this route unless a name has been provided or it is a
26
67
  # standard Rails route since we can't determine whether an options
@@ -44,17 +85,13 @@ module ActionDispatch
44
85
  parameterized_parts.delete(key)
45
86
  end
46
87
 
47
- return [route.format(parameterized_parts), params]
88
+ return RouteWithParams.new(route, parameterized_parts, params)
48
89
  end
49
90
 
50
91
  unmatched_keys = (missing_keys || []) & constraints.keys
51
92
  missing_keys = (missing_keys || []) - unmatched_keys
52
93
 
53
- message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
54
- message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
55
- message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
56
-
57
- raise ActionController::UrlGenerationError, message
94
+ MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name)
58
95
  end
59
96
 
60
97
  def clear
@@ -62,7 +99,7 @@ module ActionDispatch
62
99
  end
63
100
 
64
101
  private
65
- def extract_parameterized_parts(route, options, recall, parameterize = nil)
102
+ def extract_parameterized_parts(route, options, recall)
66
103
  parameterized_parts = recall.merge(options)
67
104
 
68
105
  keys_to_keep = route.parts.reverse_each.drop_while { |part|
@@ -73,9 +110,11 @@ module ActionDispatch
73
110
  !keys_to_keep.include?(bad_key)
74
111
  end
75
112
 
76
- if parameterize
77
- parameterized_parts.each do |k, v|
78
- parameterized_parts[k] = parameterize.call(k, v)
113
+ parameterized_parts.each do |k, v|
114
+ if k == :controller
115
+ parameterized_parts[k] = v
116
+ else
117
+ parameterized_parts[k] = v.to_param
79
118
  end
80
119
  end
81
120
 
@@ -125,19 +164,10 @@ module ActionDispatch
125
164
  routes
126
165
  end
127
166
 
128
- module RegexCaseComparator
129
- DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
130
- DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
131
-
132
- def self.===(regex)
133
- DEFAULT_INPUT == regex
134
- end
135
- end
136
-
137
167
  # Returns an array populated with missing keys if any are present.
138
168
  def missing_keys(route, parts)
139
169
  missing_keys = nil
140
- tests = route.path.requirements
170
+ tests = route.path.requirements_for_missing_keys_check
141
171
  route.required_parts.each { |key|
142
172
  case tests[key]
143
173
  when nil
@@ -145,13 +175,8 @@ module ActionDispatch
145
175
  missing_keys ||= []
146
176
  missing_keys << key
147
177
  end
148
- when RegexCaseComparator
149
- unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
150
- missing_keys ||= []
151
- missing_keys << key
152
- end
153
178
  else
154
- unless /\A#{tests[key]}\Z/ === parts[key]
179
+ unless tests[key].match?(parts[key])
155
180
  missing_keys ||= []
156
181
  missing_keys << key
157
182
  end
@@ -13,45 +13,44 @@ module ActionDispatch
13
13
  def initialize(root)
14
14
  @root = root
15
15
  @ast = Nodes::Cat.new root, DUMMY
16
- @followpos = nil
16
+ @followpos = build_followpos
17
17
  end
18
18
 
19
19
  def transition_table
20
20
  dtrans = TransitionTable.new
21
- marked = {}
22
- state_id = Hash.new { |h, k| h[k] = h.length }
21
+ marked = {}.compare_by_identity
22
+ state_id = Hash.new { |h, k| h[k] = h.length }.compare_by_identity
23
+ dstates = [firstpos(root)]
23
24
 
24
- start = firstpos(root)
25
- dstates = [start]
26
25
  until dstates.empty?
27
26
  s = dstates.shift
28
27
  next if marked[s]
29
28
  marked[s] = true # mark s
30
29
 
31
30
  s.group_by { |state| symbol(state) }.each do |sym, ps|
32
- u = ps.flat_map { |l| followpos(l) }
31
+ u = ps.flat_map { |l| @followpos[l] }
33
32
  next if u.empty?
34
33
 
35
- if u.uniq == [DUMMY]
36
- from = state_id[s]
37
- to = state_id[Object.new]
38
- dtrans[from, to] = sym
34
+ from = state_id[s]
39
35
 
36
+ if u.all? { |pos| pos == DUMMY }
37
+ to = state_id[Object.new]
38
+ dtrans[from, to] = sym
40
39
  dtrans.add_accepting(to)
40
+
41
41
  ps.each { |state| dtrans.add_memo(to, state.memo) }
42
42
  else
43
- dtrans[state_id[s], state_id[u]] = sym
43
+ to = state_id[u]
44
+ dtrans[from, to] = sym
44
45
 
45
46
  if u.include?(DUMMY)
46
- to = state_id[u]
47
-
48
- accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
49
-
50
- accepting.each { |accepting_state|
51
- dtrans.add_memo(to, accepting_state.memo)
52
- }
47
+ ps.each do |state|
48
+ if @followpos[state].include?(DUMMY)
49
+ dtrans.add_memo(to, state.memo)
50
+ end
51
+ end
53
52
 
54
- dtrans.add_accepting(state_id[u])
53
+ dtrans.add_accepting(to)
55
54
  end
56
55
  end
57
56
 
@@ -92,7 +91,7 @@ module ActionDispatch
92
91
  firstpos(node.left)
93
92
  end
94
93
  when Nodes::Or
95
- node.children.flat_map { |c| firstpos(c) }.uniq
94
+ node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!)
96
95
  when Nodes::Unary
97
96
  firstpos(node.left)
98
97
  when Nodes::Terminal
@@ -107,7 +106,7 @@ module ActionDispatch
107
106
  when Nodes::Star
108
107
  firstpos(node.left)
109
108
  when Nodes::Or
110
- node.children.flat_map { |c| lastpos(c) }.uniq
109
+ node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
111
110
  when Nodes::Cat
112
111
  if nullable?(node.right)
113
112
  lastpos(node.left) | lastpos(node.right)
@@ -123,17 +122,9 @@ module ActionDispatch
123
122
  end
124
123
  end
125
124
 
126
- def followpos(node)
127
- followpos_table[node]
128
- end
129
-
130
125
  private
131
- def followpos_table
132
- @followpos ||= build_followpos
133
- end
134
-
135
126
  def build_followpos
136
- table = Hash.new { |h, k| h[k] = [] }
127
+ table = Hash.new { |h, k| h[k] = [] }.compare_by_identity
137
128
  @ast.each do |n|
138
129
  case n
139
130
  when Nodes::Cat
@@ -150,12 +141,7 @@ module ActionDispatch
150
141
  end
151
142
 
152
143
  def symbol(edge)
153
- case edge
154
- when Journey::Nodes::Symbol
155
- edge.regexp
156
- else
157
- edge.left
158
- end
144
+ edge.symbol? ? edge.regexp : edge.left
159
145
  end
160
146
  end
161
147
  end
@@ -14,6 +14,8 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  class Simulator # :nodoc:
17
+ INITIAL_STATE = [0].freeze
18
+
17
19
  attr_reader :tt
18
20
 
19
21
  def initialize(transition_table)
@@ -22,18 +24,17 @@ module ActionDispatch
22
24
 
23
25
  def memos(string)
24
26
  input = StringScanner.new(string)
25
- state = [0]
27
+ state = INITIAL_STATE
28
+
26
29
  while sym = input.scan(%r([/.?]|[^/.?]+))
27
30
  state = tt.move(state, sym)
28
31
  end
29
32
 
30
- acceptance_states = state.find_all { |s|
31
- tt.accepting? s
32
- }
33
-
34
- return yield if acceptance_states.empty?
33
+ acceptance_states = state.each_with_object([]) do |s, memos|
34
+ memos.concat(tt.memo(s)) if tt.accepting?(s)
35
+ end
35
36
 
36
- acceptance_states.flat_map { |x| tt.memo(x) }.compact
37
+ acceptance_states.empty? ? yield : acceptance_states
37
38
  end
38
39
  end
39
40
  end
@@ -45,16 +45,18 @@ module ActionDispatch
45
45
  return [] if t.empty?
46
46
 
47
47
  regexps = []
48
+ strings = []
48
49
 
49
- t.map { |s|
50
+ t.each { |s|
50
51
  if states = @regexp_states[s]
51
- regexps.concat states.map { |re, v| re === a ? v : nil }
52
+ states.each { |re, v| regexps << v if re.match?(a) && !v.nil? }
52
53
  end
53
54
 
54
55
  if states = @string_states[s]
55
- states[a]
56
+ strings << states[a] unless states[a].nil?
56
57
  end
57
- }.compact.concat regexps
58
+ }
59
+ strings.concat regexps
58
60
  end
59
61
 
60
62
  def as_json(options = nil)
@@ -9,17 +9,6 @@ module ActionDispatch
9
9
  " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
10
10
  }
11
11
 
12
- # memo_nodes = memos.values.flatten.map { |n|
13
- # label = n
14
- # if Journey::Route === n
15
- # label = "#{n.verb.source} #{n.path.spec}"
16
- # end
17
- # " #{n.object_id} [label=\"#{label}\", shape=box];"
18
- # }
19
- # memo_edges = memos.flat_map { |k, memos|
20
- # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
21
- # }.uniq
22
-
23
12
  <<-eodot
24
13
  digraph nfa {
25
14
  rankdir=LR;
@@ -79,9 +79,10 @@ module ActionDispatch
79
79
  attr_reader :name
80
80
 
81
81
  DEFAULT_EXP = /[^\.\/\?]+/
82
- def initialize(left)
83
- super
84
- @regexp = DEFAULT_EXP
82
+ GREEDY_EXP = /(.+)/
83
+ def initialize(left, regexp = DEFAULT_EXP)
84
+ super(left)
85
+ @regexp = regexp
85
86
  @name = -left.tr("*:", "")
86
87
  end
87
88
 
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # DO NOT MODIFY!!!!
3
- # This file is automatically generated by Racc 1.4.14
3
+ # This file is automatically generated by Racc 1.4.16
4
4
  # from Racc grammar file "".
5
5
  #
6
6
 
@@ -135,11 +135,11 @@ Racc_debug_parser = false
135
135
  # reduce 0 omitted
136
136
 
137
137
  def _reduce_1(val, _values)
138
- Cat.new(val.first, val.last)
138
+ Cat.new(val.first, val.last)
139
139
  end
140
140
 
141
141
  def _reduce_2(val, _values)
142
- val.first
142
+ val.first
143
143
  end
144
144
 
145
145
  # reduce 3 omitted
@@ -151,19 +151,19 @@ end
151
151
  # reduce 6 omitted
152
152
 
153
153
  def _reduce_7(val, _values)
154
- Group.new(val[1])
154
+ Group.new(val[1])
155
155
  end
156
156
 
157
157
  def _reduce_8(val, _values)
158
- Or.new([val.first, val.last])
158
+ Or.new([val.first, val.last])
159
159
  end
160
160
 
161
161
  def _reduce_9(val, _values)
162
- Or.new([val.first, val.last])
162
+ Or.new([val.first, val.last])
163
163
  end
164
164
 
165
165
  def _reduce_10(val, _values)
166
- Star.new(Symbol.new(val.last))
166
+ Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP))
167
167
  end
168
168
 
169
169
  # reduce 11 omitted
@@ -175,19 +175,19 @@ end
175
175
  # reduce 14 omitted
176
176
 
177
177
  def _reduce_15(val, _values)
178
- Slash.new(val.first)
178
+ Slash.new(val.first)
179
179
  end
180
180
 
181
181
  def _reduce_16(val, _values)
182
- Symbol.new(val.first)
182
+ Symbol.new(val.first)
183
183
  end
184
184
 
185
185
  def _reduce_17(val, _values)
186
- Literal.new(val.first)
186
+ Literal.new(val.first)
187
187
  end
188
188
 
189
189
  def _reduce_18(val, _values)
190
- Dot.new(val.first)
190
+ Dot.new(val.first)
191
191
  end
192
192
 
193
193
  def _reduce_none(val, _values)
@@ -195,5 +195,5 @@ def _reduce_none(val, _values)
195
195
  end
196
196
 
197
197
  end # class Parser
198
- end # module Journey
199
- end # module ActionDispatch
198
+ end # module Journey
199
+ end # module ActionDispatch
@@ -21,7 +21,7 @@ rule
21
21
  | expression OR or { Or.new([val.first, val.last]) }
22
22
  ;
23
23
  star
24
- : STAR { Star.new(Symbol.new(val.last)) }
24
+ : STAR { Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP)) }
25
25
  ;
26
26
  terminal
27
27
  : symbol
@@ -6,16 +6,6 @@ module ActionDispatch
6
6
  class Pattern # :nodoc:
7
7
  attr_reader :spec, :requirements, :anchored
8
8
 
9
- def self.from_string(string)
10
- build(string, {}, "/.?", true)
11
- end
12
-
13
- def self.build(path, requirements, separators, anchored)
14
- parser = Journey::Parser.new
15
- ast = parser.parse path
16
- new ast, requirements, separators, anchored
17
- end
18
-
19
9
  def initialize(ast, requirements, separators, anchored)
20
10
  @spec = ast
21
11
  @requirements = requirements
@@ -46,11 +36,6 @@ module ActionDispatch
46
36
  node.regexp = re if re
47
37
  end
48
38
 
49
- @spec.find_all(&:star?).each do |node|
50
- node = node.left
51
- node.regexp = @requirements[node.to_sym] || /(.+)/
52
- end
53
-
54
39
  @spec
55
40
  end
56
41
 
@@ -81,7 +66,7 @@ module ActionDispatch
81
66
  end
82
67
 
83
68
  def visit_CAT(node)
84
- [visit(node.left), visit(node.right)].join
69
+ "#{visit(node.left)}#{visit(node.right)}"
85
70
  end
86
71
 
87
72
  def visit_SYMBOL(node)
@@ -107,8 +92,8 @@ module ActionDispatch
107
92
  end
108
93
 
109
94
  def visit_STAR(node)
110
- re = @matchers[node.left.to_sym] || ".+"
111
- "(#{re})"
95
+ re = @matchers[node.left.to_sym]
96
+ re ? "(#{re})" : "(.+)"
112
97
  end
113
98
 
114
99
  def visit_OR(node)
@@ -165,6 +150,10 @@ module ActionDispatch
165
150
  end
166
151
  alias :=~ :match
167
152
 
153
+ def match?(other)
154
+ to_regexp.match?(other)
155
+ end
156
+
168
157
  def source
169
158
  to_regexp.source
170
159
  end
@@ -173,6 +162,12 @@ module ActionDispatch
173
162
  @re ||= regexp_visitor.new(@separators, @requirements).accept spec
174
163
  end
175
164
 
165
+ def requirements_for_missing_keys_check
166
+ @requirements_for_missing_keys_check ||= requirements.transform_values do |regex|
167
+ /\A#{regex}\Z/
168
+ end
169
+ end
170
+
176
171
  private
177
172
  def regexp_visitor
178
173
  @anchored ? AnchoredRegexp : UnanchoredRegexp