actionpack 6.1.7.5 → 7.1.3.1

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.

Potentially problematic release.


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

Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -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
@@ -174,22 +183,22 @@ module ActionDispatch
174
183
  end
175
184
 
176
185
  def offsets
177
- return @offsets if @offsets
178
-
179
- @offsets = [0]
180
-
181
- spec.find_all(&:symbol?).each do |node|
182
- node = node.to_sym
183
-
184
- if @requirements.key?(node)
185
- re = /#{Regexp.union(@requirements[node])}|/
186
- @offsets.push((re.match("").length - 1) + @offsets.last)
187
- else
188
- @offsets << @offsets.last
186
+ @offsets ||= begin
187
+ offsets = [0]
188
+
189
+ spec.find_all(&:symbol?).each do |node|
190
+ node = node.to_sym
191
+
192
+ if @requirements.key?(node)
193
+ re = /#{Regexp.union(@requirements[node])}|/
194
+ offsets.push((re.match("").length - 1) + offsets.last)
195
+ else
196
+ offsets << offsets.last
197
+ end
189
198
  end
190
- end
191
199
 
192
- @offsets
200
+ offsets
201
+ end
193
202
  end
194
203
  end
195
204
  end
@@ -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, :source_location
9
9
 
10
10
  alias :conditions :constraints
11
11
 
@@ -53,7 +53,7 @@ module ActionDispatch
53
53
  ##
54
54
  # +path+ is a path constraint.
55
55
  # +constraints+ is a hash of constraints to be applied to this route.
56
- def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false)
56
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
57
57
  @name = name
58
58
  @app = app
59
59
  @path = path
@@ -65,29 +65,23 @@ 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
+ @source_location = source_location
73
+
74
+ @ast = @path.ast.root
75
+ @path.ast.route = self
73
76
  end
74
77
 
75
78
  def eager_load!
76
79
  path.eager_load!
77
- ast
78
80
  parts
79
81
  required_defaults
80
82
  nil
81
83
  end
82
84
 
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
85
  # Needed for `bin/rails routes`. Picks up succinctly defined requirements
92
86
  # for a route, for example route
93
87
  #
@@ -98,7 +92,7 @@ module ActionDispatch
98
92
  # as requirements.
99
93
  def requirements
100
94
  @defaults.merge(path.requirements).delete_if { |_, v|
101
- /.+?/ == v
95
+ /.+?/m == v
102
96
  }
103
97
  end
104
98
 
@@ -142,7 +136,7 @@ module ActionDispatch
142
136
  end
143
137
 
144
138
  def glob?
145
- path.spec.any?(Nodes::Star)
139
+ path.ast.glob?
146
140
  end
147
141
 
148
142
  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
 
@@ -29,7 +29,7 @@ module ActionDispatch
29
29
  end
30
30
 
31
31
  def serve(req)
32
- find_routes(req).each do |match, parameters, route|
32
+ find_routes(req) do |match, parameters, route|
33
33
  set_params = req.path_parameters
34
34
  path_info = req.path_info
35
35
  script_name = req.script_name
@@ -46,24 +46,25 @@ module ActionDispatch
46
46
  }
47
47
 
48
48
  req.path_parameters = tmp_params
49
+ req.route_uri_pattern = route.path.spec.to_s
49
50
 
50
- status, headers, body = route.app.serve(req)
51
+ _, headers, _ = response = route.app.serve(req)
51
52
 
52
- if "pass" == headers["X-Cascade"]
53
+ if "pass" == headers[Constants::X_CASCADE]
53
54
  req.script_name = script_name
54
55
  req.path_info = path_info
55
56
  req.path_parameters = set_params
56
57
  next
57
58
  end
58
59
 
59
- return [status, headers, body]
60
+ return response
60
61
  end
61
62
 
62
- [404, { "X-Cascade" => "pass" }, ["Not Found"]]
63
+ [404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
63
64
  end
64
65
 
65
66
  def recognize(rails_req)
66
- find_routes(rails_req).each do |match, parameters, route|
67
+ find_routes(rails_req) do |match, parameters, route|
67
68
  unless route.path.anchored
68
69
  rails_req.script_name = match.to_s
69
70
  rails_req.path_info = match.post_match
@@ -85,7 +86,7 @@ module ActionDispatch
85
86
  private
86
87
  def partitioned_routes
87
88
  routes.partition { |r|
88
- r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
89
+ r.path.anchored && r.path.requirements_anchored?
89
90
  }
90
91
  end
91
92
 
@@ -120,14 +121,14 @@ module ActionDispatch
120
121
 
121
122
  routes.sort_by!(&:precedence)
122
123
 
123
- routes.map! { |r|
124
+ routes.each { |r|
124
125
  match_data = r.path.match(path_info)
125
126
  path_parameters = {}
126
127
  match_data.names.each_with_index { |name, i|
127
128
  val = match_data[i + 1]
128
129
  path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
129
130
  }
130
- [match_data, path_parameters, r]
131
+ yield [match_data, path_parameters, r]
131
132
  }
132
133
  end
133
134
 
@@ -9,8 +9,8 @@ module ActionDispatch
9
9
 
10
10
  attr_reader :routes, :custom_routes, :anchored_routes
11
11
 
12
- def initialize
13
- @routes = []
12
+ def initialize(routes = [])
13
+ @routes = routes
14
14
  @ast = nil
15
15
  @anchored_routes = []
16
16
  @custom_routes = []
@@ -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