actionpack 5.2.1 → 7.0.2.4

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -6,52 +6,51 @@ module ActionDispatch
6
6
  module Journey # :nodoc:
7
7
  module GTG # :nodoc:
8
8
  class Builder # :nodoc:
9
- DUMMY = Nodes::Dummy.new
9
+ DUMMY_END_NODE = Nodes::Dummy.new
10
10
 
11
11
  attr_reader :root, :ast, :endpoints
12
12
 
13
13
  def initialize(root)
14
14
  @root = root
15
- @ast = Nodes::Cat.new root, DUMMY
16
- @followpos = nil
15
+ @ast = Nodes::Cat.new root, DUMMY_END_NODE
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] }.uniq
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_END_NODE }
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
44
-
45
- if u.include?(DUMMY)
46
- to = state_id[u]
47
-
48
- accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
43
+ to = state_id[u]
44
+ dtrans[from, to] = sym
49
45
 
50
- accepting.each { |accepting_state|
51
- dtrans.add_memo(to, accepting_state.memo)
52
- }
46
+ if u.include?(DUMMY_END_NODE)
47
+ ps.each do |state|
48
+ if @followpos[state].include?(DUMMY_END_NODE)
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
 
@@ -67,7 +66,10 @@ module ActionDispatch
67
66
  when Nodes::Group
68
67
  true
69
68
  when Nodes::Star
70
- true
69
+ # the default star regex is /(.+)/ which is NOT nullable
70
+ # but since different constraints can be provided we must
71
+ # actually check if this is the case or not.
72
+ node.regexp.match?("")
71
73
  when Nodes::Or
72
74
  node.children.any? { |c| nullable?(c) }
73
75
  when Nodes::Cat
@@ -92,7 +94,7 @@ module ActionDispatch
92
94
  firstpos(node.left)
93
95
  end
94
96
  when Nodes::Or
95
- node.children.flat_map { |c| firstpos(c) }.uniq
97
+ node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!)
96
98
  when Nodes::Unary
97
99
  firstpos(node.left)
98
100
  when Nodes::Terminal
@@ -105,9 +107,9 @@ module ActionDispatch
105
107
  def lastpos(node)
106
108
  case node
107
109
  when Nodes::Star
108
- firstpos(node.left)
110
+ lastpos(node.left)
109
111
  when Nodes::Or
110
- node.children.flat_map { |c| lastpos(c) }.uniq
112
+ node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
111
113
  when Nodes::Cat
112
114
  if nullable?(node.right)
113
115
  lastpos(node.left) | lastpos(node.right)
@@ -123,40 +125,22 @@ module ActionDispatch
123
125
  end
124
126
  end
125
127
 
126
- def followpos(node)
127
- followpos_table[node]
128
- end
129
-
130
128
  private
131
-
132
- def followpos_table
133
- @followpos ||= build_followpos
134
- end
135
-
136
129
  def build_followpos
137
- table = Hash.new { |h, k| h[k] = [] }
130
+ table = Hash.new { |h, k| h[k] = [] }.compare_by_identity
138
131
  @ast.each do |n|
139
132
  case n
140
133
  when Nodes::Cat
141
134
  lastpos(n.left).each do |i|
142
135
  table[i] += firstpos(n.right)
143
136
  end
144
- when Nodes::Star
145
- lastpos(n).each do |i|
146
- table[i] += firstpos(n)
147
- end
148
137
  end
149
138
  end
150
139
  table
151
140
  end
152
141
 
153
142
  def symbol(edge)
154
- case edge
155
- when Journey::Nodes::Symbol
156
- edge.regexp
157
- else
158
- edge.left
159
- end
143
+ edge.symbol? ? edge.regexp : edge.left
160
144
  end
161
145
  end
162
146
  end
@@ -14,6 +14,8 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  class Simulator # :nodoc:
17
+ INITIAL_STATE = [ [0, nil] ].freeze
18
+
17
19
  attr_reader :tt
18
20
 
19
21
  def initialize(transition_table)
@@ -22,18 +24,23 @@ module ActionDispatch
22
24
 
23
25
  def memos(string)
24
26
  input = StringScanner.new(string)
25
- state = [0]
27
+ state = INITIAL_STATE
28
+ start_index = 0
29
+
26
30
  while sym = input.scan(%r([/.?]|[^/.?]+))
27
- state = tt.move(state, sym)
28
- end
31
+ end_index = start_index + sym.length
29
32
 
30
- acceptance_states = state.find_all { |s|
31
- tt.accepting? s
32
- }
33
+ state = tt.move(state, string, start_index, end_index)
33
34
 
34
- return yield if acceptance_states.empty?
35
+ start_index = end_index
36
+ end
37
+
38
+ acceptance_states = state.each_with_object([]) do |s_d, memos|
39
+ s, idx = s_d
40
+ memos.concat(tt.memo(s)) if idx.nil? && tt.accepting?(s)
41
+ end
35
42
 
36
- acceptance_states.flat_map { |x| tt.memo(x) }.compact
43
+ acceptance_states.empty? ? yield : acceptance_states
37
44
  end
38
45
  end
39
46
  end
@@ -10,11 +10,15 @@ module ActionDispatch
10
10
 
11
11
  attr_reader :memos
12
12
 
13
+ DEFAULT_EXP = /[^.\/?]+/
14
+ DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
15
+
13
16
  def initialize
14
- @regexp_states = {}
15
- @string_states = {}
16
- @accepting = {}
17
- @memos = Hash.new { |h, k| h[k] = [] }
17
+ @stdparam_states = {}
18
+ @regexp_states = {}
19
+ @string_states = {}
20
+ @accepting = {}
21
+ @memos = Hash.new { |h, k| h[k] = [] }
18
22
  end
19
23
 
20
24
  def add_accepting(state)
@@ -41,20 +45,54 @@ module ActionDispatch
41
45
  Array(t)
42
46
  end
43
47
 
44
- def move(t, a)
48
+ def move(t, full_string, start_index, end_index)
45
49
  return [] if t.empty?
46
50
 
47
- regexps = []
51
+ next_states = []
48
52
 
49
- t.map { |s|
50
- if states = @regexp_states[s]
51
- regexps.concat states.map { |re, v| re === a ? v : nil }
53
+ tok = full_string.slice(start_index, end_index - start_index)
54
+ token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
55
+
56
+ t.each { |s, previous_start|
57
+ if previous_start.nil?
58
+ # In the simple case of a "default" param regex do this fast-path
59
+ # and add all next states.
60
+ if token_matches_default_component && states = @stdparam_states[s]
61
+ states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
62
+ end
63
+
64
+ # When we have a literal string, we can just pull the next state
65
+ if states = @string_states[s]
66
+ next_states << [states[tok], nil].freeze unless states[tok].nil?
67
+ end
52
68
  end
53
69
 
54
- if states = @string_states[s]
55
- states[a]
70
+ # For regexes that aren't the "default" style, they may potentially
71
+ # not be terminated by the first "token" [./?], so we need to continue
72
+ # to attempt to match this regexp as well as any successful paths that
73
+ # continue out of it. both paths could be valid.
74
+ if states = @regexp_states[s]
75
+ slice_start = if previous_start.nil?
76
+ start_index
77
+ else
78
+ previous_start
79
+ end
80
+
81
+ slice_length = end_index - slice_start
82
+ curr_slice = full_string.slice(slice_start, slice_length)
83
+
84
+ states.each { |re, v|
85
+ # if we match, we can try moving past this
86
+ next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
87
+ }
88
+
89
+ # and regardless, we must continue accepting tokens and retrying this regexp.
90
+ # we need to remember where we started as well so we can take bigger slices.
91
+ next_states << [s, slice_start].freeze
56
92
  end
57
- }.compact.concat regexps
93
+ }
94
+
95
+ next_states
58
96
  end
59
97
 
60
98
  def as_json(options = nil)
@@ -67,9 +105,10 @@ module ActionDispatch
67
105
  end
68
106
 
69
107
  {
70
- regexp_states: simple_regexp,
71
- string_states: @string_states,
72
- accepting: @accepting
108
+ regexp_states: simple_regexp,
109
+ string_states: @string_states,
110
+ stdparam_states: @stdparam_states,
111
+ accepting: @accepting
73
112
  }
74
113
  end
75
114
 
@@ -91,7 +130,7 @@ module ActionDispatch
91
130
  states = "function tt() { return #{to_json}; }"
92
131
 
93
132
  fun_routes = paths.sample(3).map do |ast|
94
- ast.map { |n|
133
+ ast.filter_map { |n|
95
134
  case n
96
135
  when Nodes::Symbol
97
136
  case n.left
@@ -104,7 +143,7 @@ module ActionDispatch
104
143
  else
105
144
  nil
106
145
  end
107
- }.compact.join
146
+ }.join
108
147
  end
109
148
 
110
149
  stylesheets = [fsm_css]
@@ -123,31 +162,49 @@ module ActionDispatch
123
162
 
124
163
  def []=(from, to, sym)
125
164
  to_mappings = states_hash_for(sym)[from] ||= {}
165
+ case sym
166
+ when Regexp
167
+ # we must match the whole string to a token boundary
168
+ if sym == DEFAULT_EXP
169
+ sym = DEFAULT_EXP_ANCHORED
170
+ else
171
+ sym = /\A#{sym}\Z/
172
+ end
173
+ when Symbol
174
+ # account for symbols in the constraints the same as strings
175
+ sym = sym.to_s
176
+ end
126
177
  to_mappings[sym] = to
127
178
  end
128
179
 
129
180
  def states
130
181
  ss = @string_states.keys + @string_states.values.flat_map(&:values)
182
+ ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
131
183
  rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
132
- (ss + rs).uniq
184
+ (ss + ps + rs).uniq
133
185
  end
134
186
 
135
187
  def transitions
136
188
  @string_states.flat_map { |from, hash|
137
189
  hash.map { |s, to| [from, s, to] }
190
+ } + @stdparam_states.flat_map { |from, hash|
191
+ hash.map { |s, to| [from, s, to] }
138
192
  } + @regexp_states.flat_map { |from, hash|
139
193
  hash.map { |s, to| [from, s, to] }
140
194
  }
141
195
  end
142
196
 
143
197
  private
144
-
145
198
  def states_hash_for(sym)
146
199
  case sym
147
- when String
200
+ when String, Symbol
148
201
  @string_states
149
202
  when Regexp
150
- @regexp_states
203
+ if sym == DEFAULT_EXP
204
+ @stdparam_states
205
+ else
206
+ @regexp_states
207
+ end
151
208
  else
152
209
  raise ArgumentError, "unknown symbol: %s" % sym.class
153
210
  end
@@ -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;
@@ -4,6 +4,66 @@ require "action_dispatch/journey/visitors"
4
4
 
5
5
  module ActionDispatch
6
6
  module Journey # :nodoc:
7
+ class Ast # :nodoc:
8
+ attr_reader :names, :path_params, :tree, :wildcard_options, :terminals
9
+ alias :root :tree
10
+
11
+ def initialize(tree, formatted)
12
+ @tree = tree
13
+ @path_params = []
14
+ @names = []
15
+ @symbols = []
16
+ @stars = []
17
+ @terminals = []
18
+ @wildcard_options = {}
19
+
20
+ visit_tree(formatted)
21
+ end
22
+
23
+ def requirements=(requirements)
24
+ # inject any regexp requirements for `star` nodes so they can be
25
+ # determined nullable, which requires knowing if the regex accepts an
26
+ # empty string.
27
+ (symbols + stars).each do |node|
28
+ re = requirements[node.to_sym]
29
+ node.regexp = re if re
30
+ end
31
+ end
32
+
33
+ def route=(route)
34
+ terminals.each { |n| n.memo = route }
35
+ end
36
+
37
+ def glob?
38
+ stars.any?
39
+ end
40
+
41
+ private
42
+ attr_reader :symbols, :stars
43
+
44
+ def visit_tree(formatted)
45
+ tree.each do |node|
46
+ if node.symbol?
47
+ path_params << node.to_sym
48
+ names << node.name
49
+ symbols << node
50
+ elsif node.star?
51
+ stars << node
52
+
53
+ if formatted != false
54
+ # Add a constraint for wildcard route to make it non-greedy and
55
+ # match the optional format part of the route by default.
56
+ wildcard_options[node.name.to_sym] ||= /.+?/m
57
+ end
58
+ end
59
+
60
+ if node.terminal?
61
+ terminals << node
62
+ end
63
+ end
64
+ end
65
+ end
66
+
7
67
  module Nodes # :nodoc:
8
68
  class Node # :nodoc:
9
69
  include Enumerable
@@ -32,7 +92,7 @@ module ActionDispatch
32
92
  end
33
93
 
34
94
  def name
35
- left.tr "*:".freeze, "".freeze
95
+ -left.tr("*:", "")
36
96
  end
37
97
 
38
98
  def type
@@ -65,12 +125,12 @@ module ActionDispatch
65
125
  def literal?; false; end
66
126
  end
67
127
 
68
- %w{ Symbol Slash Dot }.each do |t|
69
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
70
- class #{t} < Terminal;
71
- def type; :#{t.upcase}; end
72
- end
73
- eoruby
128
+ class Slash < Terminal # :nodoc:
129
+ def type; :SLASH; end
130
+ end
131
+
132
+ class Dot < Terminal # :nodoc:
133
+ def type; :DOT; end
74
134
  end
75
135
 
76
136
  class Symbol < Terminal # :nodoc:
@@ -78,17 +138,15 @@ module ActionDispatch
78
138
  alias :symbol :regexp
79
139
  attr_reader :name
80
140
 
81
- DEFAULT_EXP = /[^\.\/\?]+/
82
- def initialize(left)
83
- super
84
- @regexp = DEFAULT_EXP
85
- @name = left.tr "*:".freeze, "".freeze
86
- end
87
-
88
- def default_regexp?
89
- regexp == DEFAULT_EXP
141
+ DEFAULT_EXP = /[^.\/?]+/
142
+ GREEDY_EXP = /(.+)/
143
+ def initialize(left, regexp = DEFAULT_EXP)
144
+ super(left)
145
+ @regexp = regexp
146
+ @name = -left.tr("*:", "")
90
147
  end
91
148
 
149
+ def type; :SYMBOL; end
92
150
  def symbol?; true; end
93
151
  end
94
152
 
@@ -102,6 +160,15 @@ module ActionDispatch
102
160
  end
103
161
 
104
162
  class Star < Unary # :nodoc:
163
+ attr_accessor :regexp
164
+
165
+ def initialize(left)
166
+ super(left)
167
+
168
+ # By default wildcard routes are non-greedy and must match something.
169
+ @regexp = /.+?/m
170
+ end
171
+
105
172
  def star?; true; end
106
173
  def type; :STAR; end
107
174
 
@@ -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