actionpack 5.2.1 → 7.0.2.4

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 (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
@@ -4,25 +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
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
7
+ attr_reader :ast, :names, :requirements, :anchored, :spec
18
8
 
19
9
  def initialize(ast, requirements, separators, anchored)
20
- @spec = ast
10
+ @ast = ast
11
+ @spec = ast.root
21
12
  @requirements = requirements
22
13
  @separators = separators
23
14
  @anchored = anchored
24
15
 
25
- @names = nil
16
+ @names = ast.names
26
17
  @optional_names = nil
27
18
  @required_names = nil
28
19
  @re = nil
@@ -37,25 +28,28 @@ module ActionDispatch
37
28
  required_names
38
29
  offsets
39
30
  to_regexp
40
- nil
31
+ @ast = nil
41
32
  end
42
33
 
43
- def ast
44
- @spec.find_all(&:symbol?).each do |node|
45
- re = @requirements[node.to_sym]
46
- node.regexp = re if re
47
- 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
48
37
 
49
- @spec.find_all(&:star?).each do |node|
50
- node = node.left
51
- node.regexp = @requirements[node.to_sym] || /(.+)/
52
- end
38
+ terminals.each_with_index { |s, index|
39
+ next if index < 1
40
+ next if s.type == :DOT || s.type == :SLASH
53
41
 
54
- @spec
55
- end
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
+ }
56
51
 
57
- def names
58
- @names ||= spec.find_all(&:symbol?).map(&:name)
52
+ true
59
53
  end
60
54
 
61
55
  def required_names
@@ -81,7 +75,7 @@ module ActionDispatch
81
75
  end
82
76
 
83
77
  def visit_CAT(node)
84
- [visit(node.left), visit(node.right)].join
78
+ "#{visit(node.left)}#{visit(node.right)}"
85
79
  end
86
80
 
87
81
  def visit_SYMBOL(node)
@@ -90,7 +84,7 @@ module ActionDispatch
90
84
  return @separator_re unless @matchers.key?(node)
91
85
 
92
86
  re = @matchers[node]
93
- "(#{re})"
87
+ "(#{Regexp.union(re)})"
94
88
  end
95
89
 
96
90
  def visit_GROUP(node)
@@ -107,8 +101,8 @@ module ActionDispatch
107
101
  end
108
102
 
109
103
  def visit_STAR(node)
110
- re = @matchers[node.left.to_sym] || ".+"
111
- "(#{re})"
104
+ re = @matchers[node.left.to_sym]
105
+ re ? "(#{re})" : "(.+)"
112
106
  end
113
107
 
114
108
  def visit_OR(node)
@@ -119,7 +113,8 @@ module ActionDispatch
119
113
 
120
114
  class UnanchoredRegexp < AnchoredRegexp # :nodoc:
121
115
  def accept(node)
122
- %r{\A#{visit node}}
116
+ path = visit node
117
+ path == "/" ? %r{\A/} : %r{\A#{path}(?:\b|\Z|/)}
123
118
  end
124
119
  end
125
120
 
@@ -136,6 +131,10 @@ module ActionDispatch
136
131
  Array.new(length - 1) { |i| self[i + 1] }
137
132
  end
138
133
 
134
+ def named_captures
135
+ @names.zip(captures).to_h
136
+ end
137
+
139
138
  def [](x)
140
139
  idx = @offsets[x - 1] + x
141
140
  @match[idx]
@@ -160,6 +159,10 @@ module ActionDispatch
160
159
  end
161
160
  alias :=~ :match
162
161
 
162
+ def match?(other)
163
+ to_regexp.match?(other)
164
+ end
165
+
163
166
  def source
164
167
  to_regexp.source
165
168
  end
@@ -168,8 +171,13 @@ module ActionDispatch
168
171
  @re ||= regexp_visitor.new(@separators, @requirements).accept spec
169
172
  end
170
173
 
171
- private
174
+ def requirements_for_missing_keys_check
175
+ @requirements_for_missing_keys_check ||= requirements.transform_values do |regex|
176
+ /\A#{regex}\Z/
177
+ end
178
+ end
172
179
 
180
+ private
173
181
  def regexp_visitor
174
182
  @anchored ? AnchoredRegexp : UnanchoredRegexp
175
183
  end
@@ -183,7 +191,7 @@ module ActionDispatch
183
191
  node = node.to_sym
184
192
 
185
193
  if @requirements.key?(node)
186
- re = /#{@requirements[node]}|/
194
+ re = /#{Regexp.union(@requirements[node])}|/
187
195
  @offsets.push((re.match("").length - 1) + @offsets.last)
188
196
  else
189
197
  @offsets << @offsets.last
@@ -4,15 +4,16 @@ module ActionDispatch
4
4
  # :stopdoc:
5
5
  module Journey
6
6
  class Route
7
- attr_reader :app, :path, :defaults, :name, :precedence
7
+ attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
8
+ :internal, :scope_options, :ast
8
9
 
9
- attr_reader :constraints, :internal
10
10
  alias :conditions :constraints
11
11
 
12
12
  module VerbMatchers
13
13
  VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
14
14
  VERBS.each do |v|
15
15
  class_eval <<-eoc, __FILE__, __LINE__ + 1
16
+ # frozen_string_literal: true
16
17
  class #{v}
17
18
  def self.verb; name.split("::").last; end
18
19
  def self.call(req); req.#{v.downcase}?; end
@@ -27,7 +28,7 @@ module ActionDispatch
27
28
  @verb = verb
28
29
  end
29
30
 
30
- def call(request); @verb === request.request_method; end
31
+ def call(request); @verb == request.request_method; end
31
32
  end
32
33
 
33
34
  class All
@@ -49,15 +50,10 @@ module ActionDispatch
49
50
  end
50
51
  end
51
52
 
52
- def self.build(name, app, path, constraints, required_defaults, defaults)
53
- request_method_match = verb_matcher(constraints.delete(:request_method))
54
- new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
55
- end
56
-
57
53
  ##
58
54
  # +path+ is a path constraint.
59
55
  # +constraints+ is a hash of constraints to be applied to this route.
60
- def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
56
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false)
61
57
  @name = name
62
58
  @app = app
63
59
  @path = path
@@ -69,29 +65,23 @@ module ActionDispatch
69
65
  @_required_defaults = required_defaults
70
66
  @required_parts = nil
71
67
  @parts = nil
72
- @decorated_ast = nil
73
68
  @precedence = precedence
74
69
  @path_formatter = @path.build_formatter
70
+ @scope_options = scope_options
75
71
  @internal = internal
72
+
73
+ @ast = @path.ast.root
74
+ @path.ast.route = self
76
75
  end
77
76
 
78
77
  def eager_load!
79
78
  path.eager_load!
80
- ast
81
79
  parts
82
80
  required_defaults
83
81
  nil
84
82
  end
85
83
 
86
- def ast
87
- @decorated_ast ||= begin
88
- decorated_ast = path.ast
89
- decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
90
- decorated_ast
91
- end
92
- end
93
-
94
- # Needed for `rails routes`. Picks up succinctly defined requirements
84
+ # Needed for `bin/rails routes`. Picks up succinctly defined requirements
95
85
  # for a route, for example route
96
86
  #
97
87
  # get 'photo/:id', :controller => 'photos', :action => 'show',
@@ -101,7 +91,7 @@ module ActionDispatch
101
91
  # as requirements.
102
92
  def requirements
103
93
  @defaults.merge(path.requirements).delete_if { |_, v|
104
- /.+?/ == v
94
+ /.+?/m == v
105
95
  }
106
96
  end
107
97
 
@@ -114,18 +104,11 @@ module ActionDispatch
114
104
  end
115
105
 
116
106
  def score(supplied_keys)
117
- required_keys = path.required_names
118
-
119
- required_keys.each do |k|
107
+ path.required_names.each do |k|
120
108
  return -1 unless supplied_keys.include?(k)
121
109
  end
122
110
 
123
- score = 0
124
- path.names.each do |k|
125
- score += 1 if supplied_keys.include?(k)
126
- end
127
-
128
- score + (required_defaults.length * 2)
111
+ (required_defaults.length * 2) + path.names.count { |k| supplied_keys.include?(k) }
129
112
  end
130
113
 
131
114
  def parts
@@ -152,7 +135,7 @@ module ActionDispatch
152
135
  end
153
136
 
154
137
  def glob?
155
- !path.spec.grep(Nodes::Star).empty?
138
+ path.ast.glob?
156
139
  end
157
140
 
158
141
  def dispatcher?
@@ -17,32 +17,34 @@ module ActionDispatch
17
17
  def self.normalize_path(path)
18
18
  path ||= ""
19
19
  encoding = path.encoding
20
- path = "/#{path}".dup
21
- path.squeeze!("/".freeze)
22
- path.sub!(%r{/+\Z}, "".freeze)
23
- path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
24
- path = "/".dup if path == "".freeze
20
+ path = +"/#{path}"
21
+ path.squeeze!("/")
22
+
23
+ unless path == "/"
24
+ path.delete_suffix!("/")
25
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
26
+ end
27
+
25
28
  path.force_encoding(encoding)
26
- path
27
29
  end
28
30
 
29
31
  # URI path and fragment escaping
30
32
  # https://tools.ietf.org/html/rfc3986
31
33
  class UriEncoder # :nodoc:
32
- ENCODE = "%%%02X".freeze
34
+ ENCODE = "%%%02X"
33
35
  US_ASCII = Encoding::US_ASCII
34
36
  UTF_8 = Encoding::UTF_8
35
- EMPTY = "".dup.force_encoding(US_ASCII).freeze
36
- DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
37
+ EMPTY = (+"").force_encoding(US_ASCII).freeze
38
+ DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) }
37
39
 
38
- ALPHA = "a-zA-Z".freeze
39
- DIGIT = "0-9".freeze
40
- UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
41
- SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
40
+ ALPHA = "a-zA-Z"
41
+ DIGIT = "0-9"
42
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~"
43
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;="
42
44
 
43
45
  ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
44
46
 
45
- FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
47
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/.freeze
46
48
  SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
47
49
  PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
48
50
 
@@ -15,9 +15,6 @@ require "action_dispatch/journey/path/pattern"
15
15
  module ActionDispatch
16
16
  module Journey # :nodoc:
17
17
  class Router # :nodoc:
18
- class RoutingError < ::StandardError # :nodoc:
19
- end
20
-
21
18
  attr_accessor :routes
22
19
 
23
20
  def initialize(routes)
@@ -43,11 +40,12 @@ module ActionDispatch
43
40
  req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
44
41
  end
45
42
 
46
- parameters = route.defaults.merge parameters.transform_values { |val|
47
- val.dup.force_encoding(::Encoding::UTF_8)
43
+ tmp_params = set_params.merge route.defaults
44
+ parameters.each_pair { |key, val|
45
+ tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
48
46
  }
49
47
 
50
- req.path_parameters = set_params.merge parameters
48
+ req.path_parameters = tmp_params
51
49
 
52
50
  status, headers, body = route.app.serve(req)
53
51
 
@@ -68,7 +66,8 @@ module ActionDispatch
68
66
  find_routes(rails_req).each do |match, parameters, route|
69
67
  unless route.path.anchored
70
68
  rails_req.script_name = match.to_s
71
- rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
69
+ rails_req.path_info = match.post_match
70
+ rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
72
71
  end
73
72
 
74
73
  parameters = route.defaults.merge parameters
@@ -84,10 +83,9 @@ module ActionDispatch
84
83
  end
85
84
 
86
85
  private
87
-
88
86
  def partitioned_routes
89
87
  routes.partition { |r|
90
- r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
88
+ r.path.anchored && r.path.requirements_anchored?
91
89
  }
92
90
  end
93
91
 
@@ -109,23 +107,24 @@ module ActionDispatch
109
107
  end
110
108
 
111
109
  def find_routes(req)
112
- routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
113
- r.path.match(req.path_info)
110
+ path_info = req.path_info
111
+ routes = filter_routes(path_info).concat custom_routes.find_all { |r|
112
+ r.path.match?(path_info)
114
113
  }
115
114
 
116
- routes =
117
- if req.head?
118
- match_head_routes(routes, req)
119
- else
120
- match_routes(routes, req)
121
- end
115
+ if req.head?
116
+ routes = match_head_routes(routes, req)
117
+ else
118
+ routes.select! { |r| r.matches?(req) }
119
+ end
122
120
 
123
121
  routes.sort_by!(&:precedence)
124
122
 
125
123
  routes.map! { |r|
126
- match_data = r.path.match(req.path_info)
124
+ match_data = r.path.match(path_info)
127
125
  path_parameters = {}
128
- match_data.names.zip(match_data.captures) { |name, val|
126
+ match_data.names.each_with_index { |name, i|
127
+ val = match_data[i + 1]
129
128
  path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
130
129
  }
131
130
  [match_data, path_parameters, r]
@@ -133,24 +132,17 @@ module ActionDispatch
133
132
  end
134
133
 
135
134
  def match_head_routes(routes, req)
136
- verb_specific_routes = routes.select(&:requires_matching_verb?)
137
- head_routes = match_routes(verb_specific_routes, req)
138
-
139
- if head_routes.empty?
140
- begin
141
- req.request_method = "GET"
142
- match_routes(routes, req)
143
- ensure
144
- req.request_method = "HEAD"
145
- end
146
- else
147
- head_routes
135
+ head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
136
+ return head_routes unless head_routes.empty?
137
+
138
+ begin
139
+ req.request_method = "GET"
140
+ routes.select! { |r| r.matches?(req) }
141
+ routes
142
+ ensure
143
+ req.request_method = "HEAD"
148
144
  end
149
145
  end
150
-
151
- def match_routes(routes, req)
152
- routes.select { |r| r.matches?(req) }
153
- end
154
146
  end
155
147
  end
156
148
  end
@@ -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,13 +50,12 @@ 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
 
58
58
  def simulator
59
- return if ast.nil?
60
59
  @simulator ||= begin
61
60
  gtg = GTG::Builder.new(ast).transition_table
62
61
  GTG::Simulator.new(gtg)
@@ -72,7 +71,6 @@ module ActionDispatch
72
71
  end
73
72
 
74
73
  private
75
-
76
74
  def clear_cache!
77
75
  @ast = nil
78
76
  @simulator = nil
@@ -33,6 +33,12 @@ module ActionDispatch
33
33
  end
34
34
 
35
35
  private
36
+ # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
37
+ # see: https://bugs.ruby-lang.org/issues/13077
38
+ def dedup_scan(regex)
39
+ r = @ss.scan(regex)
40
+ r ? -r : nil
41
+ end
36
42
 
37
43
  def scan
38
44
  case
@@ -47,15 +53,15 @@ module ActionDispatch
47
53
  [:OR, "|"]
48
54
  when @ss.skip(/\./)
49
55
  [:DOT, "."]
50
- when text = @ss.scan(/:\w+/)
56
+ when text = dedup_scan(/:\w+/)
51
57
  [:SYMBOL, text]
52
- when text = @ss.scan(/\*\w+/)
58
+ when text = dedup_scan(/\*\w+/)
53
59
  [:STAR, text]
54
60
  when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
55
61
  text.tr! "\\", ""
56
- [:LITERAL, text]
62
+ [:LITERAL, -text]
57
63
  # any char
58
- when text = @ss.scan(/./)
64
+ when text = dedup_scan(/./)
59
65
  [:LITERAL, text]
60
66
  end
61
67
  end
@@ -40,7 +40,7 @@ module ActionDispatch
40
40
  @parameters.each do |index|
41
41
  param = parts[index]
42
42
  value = hash[param.name]
43
- return "".freeze unless value
43
+ return "" if value.nil?
44
44
  parts[index] = param.escape value
45
45
  end
46
46
 
@@ -59,7 +59,6 @@ module ActionDispatch
59
59
  end
60
60
 
61
61
  private
62
-
63
62
  def visit(node)
64
63
  send(DISPATCH_CACHE[node.type], node)
65
64
  end
@@ -168,7 +167,6 @@ module ActionDispatch
168
167
 
169
168
  class String < FunctionalVisitor # :nodoc:
170
169
  private
171
-
172
170
  def binary(node, seed)
173
171
  visit(node.right, visit(node.left, seed))
174
172
  end
@@ -214,7 +212,6 @@ module ActionDispatch
214
212
  end
215
213
 
216
214
  private
217
-
218
215
  def binary(node, seed)
219
216
  seed.last.concat node.children.map { |c|
220
217
  "#{node.object_id} -> #{c.object_id};"
@@ -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">
@@ -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"