actionpack 6.1.7 → 7.0.4.1

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -406
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +13 -26
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +4 -3
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +3 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +6 -6
  18. data/lib/action_controller/base.rb +5 -4
  19. data/lib/action_controller/form_builder.rb +2 -2
  20. data/lib/action_controller/log_subscriber.rb +4 -3
  21. data/lib/action_controller/metal/conditional_get.rb +39 -2
  22. data/lib/action_controller/metal/content_security_policy.rb +36 -2
  23. data/lib/action_controller/metal/cookies.rb +1 -1
  24. data/lib/action_controller/metal/data_streaming.rb +5 -13
  25. data/lib/action_controller/metal/exceptions.rb +19 -30
  26. data/lib/action_controller/metal/flash.rb +6 -2
  27. data/lib/action_controller/metal/helpers.rb +2 -2
  28. data/lib/action_controller/metal/http_authentication.rb +66 -39
  29. data/lib/action_controller/metal/instrumentation.rb +57 -52
  30. data/lib/action_controller/metal/live.rb +43 -2
  31. data/lib/action_controller/metal/mime_responds.rb +3 -3
  32. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  33. data/lib/action_controller/metal/permissions_policy.rb +19 -28
  34. data/lib/action_controller/metal/redirecting.rb +93 -18
  35. data/lib/action_controller/metal/renderers.rb +10 -11
  36. data/lib/action_controller/metal/rendering.rb +8 -8
  37. data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
  38. data/lib/action_controller/metal/rescue.rb +1 -1
  39. data/lib/action_controller/metal/streaming.rb +6 -8
  40. data/lib/action_controller/metal/strong_parameters.rb +100 -54
  41. data/lib/action_controller/metal/testing.rb +9 -2
  42. data/lib/action_controller/metal/url_for.rb +3 -3
  43. data/lib/action_controller/metal.rb +10 -13
  44. data/lib/action_controller/railtie.rb +49 -6
  45. data/lib/action_controller/renderer.rb +1 -1
  46. data/lib/action_controller/test_case.rb +28 -7
  47. data/lib/action_controller.rb +2 -5
  48. data/lib/action_dispatch/http/cache.rb +14 -7
  49. data/lib/action_dispatch/http/content_security_policy.rb +108 -35
  50. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  51. data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
  52. data/lib/action_dispatch/http/mime_type.rb +9 -11
  53. data/lib/action_dispatch/http/parameters.rb +5 -5
  54. data/lib/action_dispatch/http/permissions_policy.rb +17 -1
  55. data/lib/action_dispatch/http/request.rb +12 -21
  56. data/lib/action_dispatch/http/response.rb +3 -16
  57. data/lib/action_dispatch/http/url.rb +11 -19
  58. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  61. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  62. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  63. data/lib/action_dispatch/journey/route.rb +6 -13
  64. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  65. data/lib/action_dispatch/journey/router.rb +1 -1
  66. data/lib/action_dispatch/journey/routes.rb +3 -3
  67. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  68. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  69. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  70. data/lib/action_dispatch/middleware/cookies.rb +42 -27
  71. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  72. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  73. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  74. data/lib/action_dispatch/middleware/executor.rb +3 -0
  75. data/lib/action_dispatch/middleware/flash.rb +17 -18
  76. data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
  77. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  78. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  79. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  80. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  81. data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
  82. data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
  83. data/lib/action_dispatch/middleware/stack.rb +27 -9
  84. data/lib/action_dispatch/middleware/static.rb +2 -6
  85. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  86. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  87. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  88. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +3 -2
  89. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  91. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  92. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  93. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  94. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  95. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  97. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  98. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  99. data/lib/action_dispatch/railtie.rb +8 -2
  100. data/lib/action_dispatch/request/session.rb +43 -13
  101. data/lib/action_dispatch/routing/inspector.rb +1 -1
  102. data/lib/action_dispatch/routing/mapper.rb +59 -83
  103. data/lib/action_dispatch/routing/redirection.rb +5 -2
  104. data/lib/action_dispatch/routing/route_set.rb +17 -7
  105. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  106. data/lib/action_dispatch/routing/url_for.rb +4 -5
  107. data/lib/action_dispatch/routing.rb +5 -6
  108. data/lib/action_dispatch/system_test_case.rb +5 -5
  109. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  110. data/lib/action_dispatch/system_testing/driver.rb +35 -11
  111. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
  112. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  113. data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
  114. data/lib/action_dispatch/testing/assertions.rb +2 -5
  115. data/lib/action_dispatch/testing/integration.rb +6 -8
  116. data/lib/action_dispatch/testing/test_process.rb +3 -29
  117. data/lib/action_dispatch/testing/test_response.rb +20 -2
  118. data/lib/action_dispatch.rb +1 -0
  119. data/lib/action_pack/gem_version.rb +5 -5
  120. data/lib/action_pack/version.rb +1 -1
  121. metadata +16 -15
@@ -6,13 +6,13 @@ 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
15
+ @ast = Nodes::Cat.new root, DUMMY_END_NODE
16
16
  @followpos = build_followpos
17
17
  end
18
18
 
@@ -28,12 +28,12 @@ module ActionDispatch
28
28
  marked[s] = true # mark s
29
29
 
30
30
  s.group_by { |state| symbol(state) }.each do |sym, ps|
31
- u = ps.flat_map { |l| @followpos[l] }
31
+ u = ps.flat_map { |l| @followpos[l] }.uniq
32
32
  next if u.empty?
33
33
 
34
34
  from = state_id[s]
35
35
 
36
- if u.all? { |pos| pos == DUMMY }
36
+ if u.all? { |pos| pos == DUMMY_END_NODE }
37
37
  to = state_id[Object.new]
38
38
  dtrans[from, to] = sym
39
39
  dtrans.add_accepting(to)
@@ -43,9 +43,9 @@ module ActionDispatch
43
43
  to = state_id[u]
44
44
  dtrans[from, to] = sym
45
45
 
46
- if u.include?(DUMMY)
46
+ if u.include?(DUMMY_END_NODE)
47
47
  ps.each do |state|
48
- if @followpos[state].include?(DUMMY)
48
+ if @followpos[state].include?(DUMMY_END_NODE)
49
49
  dtrans.add_memo(to, state.memo)
50
50
  end
51
51
  end
@@ -66,7 +66,10 @@ module ActionDispatch
66
66
  when Nodes::Group
67
67
  true
68
68
  when Nodes::Star
69
- 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?("")
70
73
  when Nodes::Or
71
74
  node.children.any? { |c| nullable?(c) }
72
75
  when Nodes::Cat
@@ -104,7 +107,7 @@ module ActionDispatch
104
107
  def lastpos(node)
105
108
  case node
106
109
  when Nodes::Star
107
- firstpos(node.left)
110
+ lastpos(node.left)
108
111
  when Nodes::Or
109
112
  node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
110
113
  when Nodes::Cat
@@ -131,10 +134,6 @@ module ActionDispatch
131
134
  lastpos(n.left).each do |i|
132
135
  table[i] += firstpos(n.right)
133
136
  end
134
- when Nodes::Star
135
- lastpos(n).each do |i|
136
- table[i] += firstpos(n)
137
- end
138
137
  end
139
138
  end
140
139
  table
@@ -14,7 +14,7 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  class Simulator # :nodoc:
17
- INITIAL_STATE = [0].freeze
17
+ INITIAL_STATE = [ [0, nil] ].freeze
18
18
 
19
19
  attr_reader :tt
20
20
 
@@ -25,13 +25,19 @@ module ActionDispatch
25
25
  def memos(string)
26
26
  input = StringScanner.new(string)
27
27
  state = INITIAL_STATE
28
+ start_index = 0
28
29
 
29
30
  while sym = input.scan(%r([/.?]|[^/.?]+))
30
- state = tt.move(state, sym)
31
+ end_index = start_index + sym.length
32
+
33
+ state = tt.move(state, string, start_index, end_index)
34
+
35
+ start_index = end_index
31
36
  end
32
37
 
33
- acceptance_states = state.each_with_object([]) do |s, memos|
34
- memos.concat(tt.memo(s)) if tt.accepting?(s)
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)
35
41
  end
36
42
 
37
43
  acceptance_states.empty? ? yield : acceptance_states
@@ -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,22 +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 = []
48
- strings = []
51
+ next_states = []
49
52
 
50
- t.each { |s|
51
- if states = @regexp_states[s]
52
- states.each { |re, v| regexps << v if re.match?(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
53
68
  end
54
69
 
55
- if states = @string_states[s]
56
- strings << states[a] unless states[a].nil?
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
57
92
  end
58
93
  }
59
- strings.concat regexps
94
+
95
+ next_states
60
96
  end
61
97
 
62
98
  def as_json(options = nil)
@@ -69,9 +105,10 @@ module ActionDispatch
69
105
  end
70
106
 
71
107
  {
72
- regexp_states: simple_regexp,
73
- string_states: @string_states,
74
- accepting: @accepting
108
+ regexp_states: simple_regexp,
109
+ string_states: @string_states,
110
+ stdparam_states: @stdparam_states,
111
+ accepting: @accepting
75
112
  }
76
113
  end
77
114
 
@@ -93,7 +130,7 @@ module ActionDispatch
93
130
  states = "function tt() { return #{to_json}; }"
94
131
 
95
132
  fun_routes = paths.sample(3).map do |ast|
96
- ast.map { |n|
133
+ ast.filter_map { |n|
97
134
  case n
98
135
  when Nodes::Symbol
99
136
  case n.left
@@ -106,7 +143,7 @@ module ActionDispatch
106
143
  else
107
144
  nil
108
145
  end
109
- }.compact.join
146
+ }.join
110
147
  end
111
148
 
112
149
  stylesheets = [fsm_css]
@@ -125,18 +162,33 @@ module ActionDispatch
125
162
 
126
163
  def []=(from, to, sym)
127
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
128
177
  to_mappings[sym] = to
129
178
  end
130
179
 
131
180
  def states
132
181
  ss = @string_states.keys + @string_states.values.flat_map(&:values)
182
+ ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
133
183
  rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
134
- (ss + rs).uniq
184
+ (ss + ps + rs).uniq
135
185
  end
136
186
 
137
187
  def transitions
138
188
  @string_states.flat_map { |from, hash|
139
189
  hash.map { |s, to| [from, s, to] }
190
+ } + @stdparam_states.flat_map { |from, hash|
191
+ hash.map { |s, to| [from, s, to] }
140
192
  } + @regexp_states.flat_map { |from, hash|
141
193
  hash.map { |s, to| [from, s, to] }
142
194
  }
@@ -145,10 +197,14 @@ module ActionDispatch
145
197
  private
146
198
  def states_hash_for(sym)
147
199
  case sym
148
- when String
200
+ when String, Symbol
149
201
  @string_states
150
202
  when Regexp
151
- @regexp_states
203
+ if sym == DEFAULT_EXP
204
+ @stdparam_states
205
+ else
206
+ @regexp_states
207
+ end
152
208
  else
153
209
  raise ArgumentError, "unknown symbol: %s" % sym.class
154
210
  end
@@ -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
@@ -78,7 +138,7 @@ module ActionDispatch
78
138
  alias :symbol :regexp
79
139
  attr_reader :name
80
140
 
81
- DEFAULT_EXP = /[^\.\/\?]+/
141
+ DEFAULT_EXP = /[^.\/?]+/
82
142
  GREEDY_EXP = /(.+)/
83
143
  def initialize(left, regexp = DEFAULT_EXP)
84
144
  super(left)
@@ -86,10 +146,6 @@ module ActionDispatch
86
146
  @name = -left.tr("*:", "")
87
147
  end
88
148
 
89
- def default_regexp?
90
- regexp == DEFAULT_EXP
91
- end
92
-
93
149
  def type; :SYMBOL; end
94
150
  def symbol?; true; end
95
151
  end
@@ -104,6 +160,15 @@ module ActionDispatch
104
160
  end
105
161
 
106
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
+
107
172
  def star?; true; end
108
173
  def type; :STAR; end
109
174
 
@@ -4,15 +4,16 @@ module ActionDispatch
4
4
  module Journey # :nodoc:
5
5
  module Path # :nodoc:
6
6
  class Pattern # :nodoc:
7
- attr_reader :spec, :requirements, :anchored
7
+ attr_reader :ast, :names, :requirements, :anchored, :spec
8
8
 
9
9
  def initialize(ast, requirements, separators, anchored)
10
- @spec = ast
10
+ @ast = ast
11
+ @spec = ast.root
11
12
  @requirements = requirements
12
13
  @separators = separators
13
14
  @anchored = anchored
14
15
 
15
- @names = nil
16
+ @names = ast.names
16
17
  @optional_names = nil
17
18
  @required_names = nil
18
19
  @re = nil
@@ -27,20 +28,28 @@ module ActionDispatch
27
28
  required_names
28
29
  offsets
29
30
  to_regexp
30
- nil
31
+ @ast = nil
31
32
  end
32
33
 
33
- def ast
34
- @spec.find_all(&:symbol?).each do |node|
35
- re = @requirements[node.to_sym]
36
- node.regexp = re if re
37
- end
34
+ def requirements_anchored?
35
+ # each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal
36
+ terminals = ast.terminals
38
37
 
39
- @spec
40
- end
38
+ terminals.each_with_index { |s, index|
39
+ next if index < 1
40
+ next if s.type == :DOT || s.type == :SLASH
41
+
42
+ back = terminals[index - 1]
43
+ fwd = terminals[index + 1]
44
+
45
+ # we also don't support this yet, constraints must be regexps
46
+ return false if s.symbol? && s.regexp.is_a?(Array)
47
+
48
+ return false if back.literal?
49
+ return false if !fwd.nil? && fwd.literal?
50
+ }
41
51
 
42
- def names
43
- @names ||= spec.find_all(&:symbol?).map(&:name)
52
+ true
44
53
  end
45
54
 
46
55
  def required_names
@@ -5,7 +5,7 @@ module ActionDispatch
5
5
  module Journey
6
6
  class Route
7
7
  attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
8
- :internal, :scope_options
8
+ :internal, :scope_options, :ast
9
9
 
10
10
  alias :conditions :constraints
11
11
 
@@ -65,29 +65,22 @@ module ActionDispatch
65
65
  @_required_defaults = required_defaults
66
66
  @required_parts = nil
67
67
  @parts = nil
68
- @decorated_ast = nil
69
68
  @precedence = precedence
70
69
  @path_formatter = @path.build_formatter
71
70
  @scope_options = scope_options
72
71
  @internal = internal
72
+
73
+ @ast = @path.ast.root
74
+ @path.ast.route = self
73
75
  end
74
76
 
75
77
  def eager_load!
76
78
  path.eager_load!
77
- ast
78
79
  parts
79
80
  required_defaults
80
81
  nil
81
82
  end
82
83
 
83
- def ast
84
- @decorated_ast ||= begin
85
- decorated_ast = path.ast
86
- decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
87
- decorated_ast
88
- end
89
- end
90
-
91
84
  # Needed for `bin/rails routes`. Picks up succinctly defined requirements
92
85
  # for a route, for example route
93
86
  #
@@ -98,7 +91,7 @@ module ActionDispatch
98
91
  # as requirements.
99
92
  def requirements
100
93
  @defaults.merge(path.requirements).delete_if { |_, v|
101
- /.+?/ == v
94
+ /.+?/m == v
102
95
  }
103
96
  end
104
97
 
@@ -142,7 +135,7 @@ module ActionDispatch
142
135
  end
143
136
 
144
137
  def glob?
145
- path.spec.any?(Nodes::Star)
138
+ path.ast.glob?
146
139
  end
147
140
 
148
141
  def dispatcher?
@@ -35,7 +35,7 @@ module ActionDispatch
35
35
  US_ASCII = Encoding::US_ASCII
36
36
  UTF_8 = Encoding::UTF_8
37
37
  EMPTY = (+"").force_encoding(US_ASCII).freeze
38
- DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
38
+ DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) }
39
39
 
40
40
  ALPHA = "a-zA-Z"
41
41
  DIGIT = "0-9"
@@ -44,7 +44,7 @@ module ActionDispatch
44
44
 
45
45
  ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
46
46
 
47
- FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
47
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/.freeze
48
48
  SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
49
49
  PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
50
50
 
@@ -85,7 +85,7 @@ module ActionDispatch
85
85
  private
86
86
  def partitioned_routes
87
87
  routes.partition { |r|
88
- r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
88
+ r.path.anchored && r.path.requirements_anchored?
89
89
  }
90
90
  end
91
91
 
@@ -41,7 +41,7 @@ module ActionDispatch
41
41
  end
42
42
 
43
43
  def partition_route(route)
44
- if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
44
+ if route.path.anchored && route.path.requirements_anchored?
45
45
  anchored_routes << route
46
46
  else
47
47
  custom_routes << route
@@ -50,8 +50,8 @@ module ActionDispatch
50
50
 
51
51
  def ast
52
52
  @ast ||= begin
53
- asts = anchored_routes.map(&:ast)
54
- Nodes::Or.new(asts)
53
+ nodes = anchored_routes.map(&:ast)
54
+ Nodes::Or.new(nodes)
55
55
  end
56
56
  end
57
57
 
@@ -68,7 +68,7 @@ function highlight_state(index, color) {
68
68
  }
69
69
 
70
70
  function highlight_finish(index) {
71
- svg_nodes[index].select('polygon')
71
+ svg_nodes[index].select('ellipse')
72
72
  .style("fill", "while")
73
73
  .transition().duration(500)
74
74
  .style("fill", "blue");
@@ -76,54 +76,79 @@ function highlight_finish(index) {
76
76
 
77
77
  function match(input) {
78
78
  reset_graph();
79
- var table = tt();
80
- var states = [0];
81
- var regexp_states = table['regexp_states'];
82
- var string_states = table['string_states'];
83
- var accepting = table['accepting'];
79
+ var table = tt();
80
+ var states = [[0, null]];
81
+ var regexp_states = table['regexp_states'];
82
+ var string_states = table['string_states'];
83
+ var stdparam_states = table['stdparam_states'];
84
+ var accepting = table['accepting'];
85
+ var default_re = new RegExp("^[^.\/?]+$");
86
+ var start_index = 0;
84
87
 
85
88
  highlight_state(0);
86
89
 
87
90
  tokenize(input, function(token) {
91
+ var end_index = start_index + token.length;
92
+
88
93
  var new_states = [];
89
94
  for(var key in states) {
90
- var state = states[key];
95
+ var state_parts = states[key];
96
+ var state = state_parts[0];
97
+ var previous_start = state_parts[1];
98
+
99
+ if(previous_start == null) {
100
+ if(string_states[state] && string_states[state][token]) {
101
+ var new_state = string_states[state][token];
102
+ highlight_edge(state, new_state);
103
+ highlight_state(new_state);
104
+ new_states.push([new_state, null]);
105
+ }
91
106
 
92
- if(string_states[state] && string_states[state][token]) {
93
- var new_state = string_states[state][token];
94
- highlight_edge(state, new_state);
95
- highlight_state(new_state);
96
- new_states.push(new_state);
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
+ }
114
+ }
97
115
  }
98
116
 
99
117
  if(regexp_states[state]) {
118
+ var slice_start = previous_start != null ? previous_start : start_index;
119
+
100
120
  for(var key in regexp_states[state]) {
101
121
  var re = new RegExp("^" + key + "$");
102
- if(re.test(token)) {
122
+
123
+ var accumulation = input.slice(slice_start, end_index);
124
+
125
+ if(re.test(accumulation)) {
103
126
  var new_state = regexp_states[state][key];
104
127
  highlight_edge(state, new_state);
105
128
  highlight_state(new_state);
106
- new_states.push(new_state);
129
+ new_states.push([new_state, null]);
107
130
  }
131
+
132
+ // retry the same regexp with the accumulated data either way
133
+ new_states.push([state, slice_start]);
108
134
  }
109
135
  }
110
136
  }
111
137
 
112
- if(new_states.length == 0) {
113
- return;
114
- }
115
138
  states = new_states;
139
+ start_index = end_index;
116
140
  });
117
141
 
118
142
  for(var key in states) {
119
- var state = states[key];
143
+ var state_parts = states[key];
144
+ var state = state_parts[0];
145
+ var slice_start = state_parts[1];
146
+
147
+ // we must ignore ones that are still accepting more data
148
+ if (slice_start != null) continue;
149
+
120
150
  if(accepting[state]) {
121
- for(var mkey in svg_edges[state]) {
122
- if(!regexp_states[mkey] && !string_states[mkey]) {
123
- highlight_edge(state, mkey);
124
- highlight_finish(mkey);
125
- }
126
- }
151
+ highlight_finish(state);
127
152
  } else {
128
153
  highlight_state(state, "red");
129
154
  }
@@ -8,7 +8,7 @@
8
8
  <%= style %>
9
9
  <% end %>
10
10
  </style>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div id="wrapper">
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "erb"
4
4
  require "uri"
5
- require "action_dispatch/http/request"
6
5
  require "active_support/actionable_error"
7
6
 
8
7
  module ActionDispatch