actionpack 4.2.11.1 → 6.1.3.2

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +81 -51
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +61 -33
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +35 -21
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,4 +1,4 @@
1
- require 'action_dispatch/journey/router/strexp'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
4
  module Journey # :nodoc:
@@ -6,15 +6,11 @@ module ActionDispatch
6
6
  class Pattern # :nodoc:
7
7
  attr_reader :spec, :requirements, :anchored
8
8
 
9
- def self.from_string string
10
- new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
11
- end
12
-
13
- def initialize(strexp)
14
- @spec = strexp.ast
15
- @requirements = strexp.requirements
16
- @separators = strexp.separators.join
17
- @anchored = strexp.anchor
9
+ def initialize(ast, requirements, separators, anchored)
10
+ @spec = ast
11
+ @requirements = requirements
12
+ @separators = separators
13
+ @anchored = anchored
18
14
 
19
15
  @names = nil
20
16
  @optional_names = nil
@@ -27,22 +23,24 @@ module ActionDispatch
27
23
  Visitors::FormatBuilder.new.accept(spec)
28
24
  end
29
25
 
26
+ def eager_load!
27
+ required_names
28
+ offsets
29
+ to_regexp
30
+ nil
31
+ end
32
+
30
33
  def ast
31
- @spec.grep(Nodes::Symbol).each do |node|
34
+ @spec.find_all(&:symbol?).each do |node|
32
35
  re = @requirements[node.to_sym]
33
36
  node.regexp = re if re
34
37
  end
35
38
 
36
- @spec.grep(Nodes::Star).each do |node|
37
- node = node.left
38
- node.regexp = @requirements[node.to_sym] || /(.+)/
39
- end
40
-
41
39
  @spec
42
40
  end
43
41
 
44
42
  def names
45
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
43
+ @names ||= spec.find_all(&:symbol?).map(&:name)
46
44
  end
47
45
 
48
46
  def required_names
@@ -50,34 +48,9 @@ module ActionDispatch
50
48
  end
51
49
 
52
50
  def optional_names
53
- @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
54
- group.grep(Nodes::Symbol)
55
- }.map { |n| n.name }.uniq
56
- end
57
-
58
- class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
59
- attr_reader :offsets
60
-
61
- def initialize(matchers)
62
- @matchers = matchers
63
- @capture_count = [0]
64
- end
65
-
66
- def visit(node)
67
- super
68
- @capture_count
69
- end
70
-
71
- def visit_SYMBOL(node)
72
- node = node.to_sym
73
-
74
- if @matchers.key?(node)
75
- re = /#{@matchers[node]}|/
76
- @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
77
- else
78
- @capture_count << (@capture_count.last || 0)
79
- end
80
- end
51
+ @optional_names ||= spec.find_all(&:group?).flat_map { |group|
52
+ group.find_all(&:symbol?)
53
+ }.map(&:name).uniq
81
54
  end
82
55
 
83
56
  class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
@@ -93,7 +66,7 @@ module ActionDispatch
93
66
  end
94
67
 
95
68
  def visit_CAT(node)
96
- [visit(node.left), visit(node.right)].join
69
+ "#{visit(node.left)}#{visit(node.right)}"
97
70
  end
98
71
 
99
72
  def visit_SYMBOL(node)
@@ -102,7 +75,7 @@ module ActionDispatch
102
75
  return @separator_re unless @matchers.key?(node)
103
76
 
104
77
  re = @matchers[node]
105
- "(#{re})"
78
+ "(#{Regexp.union(re)})"
106
79
  end
107
80
 
108
81
  def visit_GROUP(node)
@@ -119,14 +92,20 @@ module ActionDispatch
119
92
  end
120
93
 
121
94
  def visit_STAR(node)
122
- re = @matchers[node.left.to_sym] || '.+'
123
- "(#{re})"
95
+ re = @matchers[node.left.to_sym]
96
+ re ? "(#{re})" : "(.+)"
97
+ end
98
+
99
+ def visit_OR(node)
100
+ children = node.children.map { |n| visit n }
101
+ "(?:#{children.join(?|)})"
124
102
  end
125
103
  end
126
104
 
127
105
  class UnanchoredRegexp < AnchoredRegexp # :nodoc:
128
106
  def accept(node)
129
- %r{\A#{visit node}}
107
+ path = visit node
108
+ path == "/" ? %r{\A/} : %r{\A#{path}(?:\b|\Z|/)}
130
109
  end
131
110
  end
132
111
 
@@ -140,7 +119,11 @@ module ActionDispatch
140
119
  end
141
120
 
142
121
  def captures
143
- (length - 1).times.map { |i| self[i + 1] }
122
+ Array.new(length - 1) { |i| self[i + 1] }
123
+ end
124
+
125
+ def named_captures
126
+ @names.zip(captures).to_h
144
127
  end
145
128
 
146
129
  def [](x)
@@ -167,6 +150,10 @@ module ActionDispatch
167
150
  end
168
151
  alias :=~ :match
169
152
 
153
+ def match?(other)
154
+ to_regexp.match?(other)
155
+ end
156
+
170
157
  def source
171
158
  to_regexp.source
172
159
  end
@@ -175,8 +162,13 @@ module ActionDispatch
175
162
  @re ||= regexp_visitor.new(@separators, @requirements).accept spec
176
163
  end
177
164
 
178
- private
165
+ def requirements_for_missing_keys_check
166
+ @requirements_for_missing_keys_check ||= requirements.transform_values do |regex|
167
+ /\A#{regex}\Z/
168
+ end
169
+ end
179
170
 
171
+ private
180
172
  def regexp_visitor
181
173
  @anchored ? AnchoredRegexp : UnanchoredRegexp
182
174
  end
@@ -184,8 +176,20 @@ module ActionDispatch
184
176
  def offsets
185
177
  return @offsets if @offsets
186
178
 
187
- viz = RegexpOffsets.new(@requirements)
188
- @offsets = viz.accept(spec)
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
189
+ end
190
+ end
191
+
192
+ @offsets
189
193
  end
190
194
  end
191
195
  end
@@ -1,42 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
- module Journey # :nodoc:
3
- class Route # :nodoc:
4
- attr_reader :app, :path, :defaults, :name
4
+ # :stopdoc:
5
+ module Journey
6
+ class Route
7
+ attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
8
+ :internal, :scope_options
5
9
 
6
- attr_reader :constraints
7
10
  alias :conditions :constraints
8
11
 
9
- attr_accessor :precedence
12
+ module VerbMatchers
13
+ VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
14
+ VERBS.each do |v|
15
+ class_eval <<-eoc, __FILE__, __LINE__ + 1
16
+ # frozen_string_literal: true
17
+ class #{v}
18
+ def self.verb; name.split("::").last; end
19
+ def self.call(req); req.#{v.downcase}?; end
20
+ end
21
+ eoc
22
+ end
23
+
24
+ class Unknown
25
+ attr_reader :verb
26
+
27
+ def initialize(verb)
28
+ @verb = verb
29
+ end
30
+
31
+ def call(request); @verb == request.request_method; end
32
+ end
33
+
34
+ class All
35
+ def self.call(_); true; end
36
+ def self.verb; ""; end
37
+ end
38
+
39
+ VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
40
+ klass = const_get verb
41
+ hash[verb] = klass
42
+ hash[verb.downcase] = klass
43
+ hash[verb.downcase.to_sym] = klass
44
+ end
45
+ end
46
+
47
+ def self.verb_matcher(verb)
48
+ VerbMatchers::VERB_TO_CLASS.fetch(verb) do
49
+ VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
50
+ end
51
+ end
10
52
 
11
53
  ##
12
54
  # +path+ is a path constraint.
13
55
  # +constraints+ is a hash of constraints to be applied to this route.
14
- def initialize(name, app, path, constraints, defaults = {})
56
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false)
15
57
  @name = name
16
58
  @app = app
17
59
  @path = path
18
60
 
61
+ @request_method_match = request_method_match
19
62
  @constraints = constraints
20
63
  @defaults = defaults
21
64
  @required_defaults = nil
65
+ @_required_defaults = required_defaults
22
66
  @required_parts = nil
23
67
  @parts = nil
24
68
  @decorated_ast = nil
25
- @precedence = 0
69
+ @precedence = precedence
26
70
  @path_formatter = @path.build_formatter
71
+ @scope_options = scope_options
72
+ @internal = internal
73
+ end
74
+
75
+ def eager_load!
76
+ path.eager_load!
77
+ ast
78
+ parts
79
+ required_defaults
80
+ nil
27
81
  end
28
82
 
29
83
  def ast
30
84
  @decorated_ast ||= begin
31
85
  decorated_ast = path.ast
32
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
86
+ decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
33
87
  decorated_ast
34
88
  end
35
89
  end
36
90
 
37
- def requirements # :nodoc:
38
- # needed for rails `rake routes`
39
- @defaults.merge(path.requirements).delete_if { |_,v|
91
+ # Needed for `bin/rails routes`. Picks up succinctly defined requirements
92
+ # for a route, for example route
93
+ #
94
+ # get 'photo/:id', :controller => 'photos', :action => 'show',
95
+ # :id => /[A-Z]\d{5}/
96
+ #
97
+ # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/}
98
+ # as requirements.
99
+ def requirements
100
+ @defaults.merge(path.requirements).delete_if { |_, v|
40
101
  /.+?/ == v
41
102
  }
42
103
  end
@@ -49,18 +110,16 @@ module ActionDispatch
49
110
  required_parts + required_defaults.keys
50
111
  end
51
112
 
52
- def score(constraints)
53
- required_keys = path.required_names
54
- supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
55
-
56
- return -1 unless (required_keys - supplied_keys).empty?
113
+ def score(supplied_keys)
114
+ path.required_names.each do |k|
115
+ return -1 unless supplied_keys.include?(k)
116
+ end
57
117
 
58
- score = (supplied_keys & path.names).length
59
- score + (required_defaults.length * 2)
118
+ (required_defaults.length * 2) + path.names.count { |k| supplied_keys.include?(k) }
60
119
  end
61
120
 
62
121
  def parts
63
- @parts ||= segments.map { |n| n.to_sym }
122
+ @parts ||= segments.map(&:to_sym)
64
123
  end
65
124
  alias :segment_keys :parts
66
125
 
@@ -68,26 +127,22 @@ module ActionDispatch
68
127
  @path_formatter.evaluate path_options
69
128
  end
70
129
 
71
- def optional_parts
72
- path.optional_names.map { |n| n.to_sym }
73
- end
74
-
75
130
  def required_parts
76
- @required_parts ||= path.required_names.map { |n| n.to_sym }
131
+ @required_parts ||= path.required_names.map(&:to_sym)
77
132
  end
78
133
 
79
134
  def required_default?(key)
80
- (constraints[:required_defaults] || []).include?(key)
135
+ @_required_defaults.include?(key)
81
136
  end
82
137
 
83
138
  def required_defaults
84
- @required_defaults ||= @defaults.dup.delete_if do |k,_|
139
+ @required_defaults ||= @defaults.dup.delete_if do |k, _|
85
140
  parts.include?(k) || !required_default?(k)
86
141
  end
87
142
  end
88
143
 
89
144
  def glob?
90
- !path.spec.grep(Nodes::Star).empty?
145
+ path.spec.any?(Nodes::Star)
91
146
  end
92
147
 
93
148
  def dispatcher?
@@ -95,9 +150,8 @@ module ActionDispatch
95
150
  end
96
151
 
97
152
  def matches?(request)
98
- constraints.all? do |method, value|
99
- next true unless request.respond_to?(method)
100
-
153
+ match_verb(request) &&
154
+ constraints.all? { |method, value|
101
155
  case value
102
156
  when Regexp, String
103
157
  value === request.send(method).to_s
@@ -110,16 +164,30 @@ module ActionDispatch
110
164
  else
111
165
  value === request.send(method)
112
166
  end
113
- end
167
+ }
114
168
  end
115
169
 
116
170
  def ip
117
171
  constraints[:ip] || //
118
172
  end
119
173
 
174
+ def requires_matching_verb?
175
+ !@request_method_match.all? { |x| x == VerbMatchers::All }
176
+ end
177
+
120
178
  def verb
121
- constraints[:request_method] || //
179
+ verbs.join("|")
122
180
  end
181
+
182
+ private
183
+ def verbs
184
+ @request_method_match.map(&:verb)
185
+ end
186
+
187
+ def match_verb(request)
188
+ @request_method_match.any? { |m| m.call request }
189
+ end
123
190
  end
124
191
  end
192
+ # :startdoc:
125
193
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  module Journey # :nodoc:
3
5
  class Router # :nodoc:
@@ -5,7 +7,7 @@ module ActionDispatch
5
7
  # Normalizes URI path.
6
8
  #
7
9
  # Strips off trailing slash and ensures there is a leading slash.
8
- # Also converts downcase url encoded string to uppercase.
10
+ # Also converts downcase URL encoded string to uppercase.
9
11
  #
10
12
  # normalize_path("/foo") # => "/foo"
11
13
  # normalize_path("/foo/") # => "/foo"
@@ -13,27 +15,32 @@ module ActionDispatch
13
15
  # normalize_path("") # => "/"
14
16
  # normalize_path("/%ab") # => "/%AB"
15
17
  def self.normalize_path(path)
16
- path = "/#{path}".force_encoding(Encoding::UTF_8)
17
- path.squeeze!('/')
18
- path.sub!(%r{/+\Z}, '')
19
- path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
20
- path = '/' if path == ''
21
- path
18
+ path ||= ""
19
+ encoding = path.encoding
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
+
28
+ path.force_encoding(encoding)
22
29
  end
23
30
 
24
31
  # URI path and fragment escaping
25
- # http://tools.ietf.org/html/rfc3986
32
+ # https://tools.ietf.org/html/rfc3986
26
33
  class UriEncoder # :nodoc:
27
- ENCODE = "%%%02X".freeze
34
+ ENCODE = "%%%02X"
28
35
  US_ASCII = Encoding::US_ASCII
29
36
  UTF_8 = Encoding::UTF_8
30
- EMPTY = "".force_encoding(US_ASCII).freeze
31
- 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).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
32
39
 
33
- ALPHA = "a-zA-Z".freeze
34
- DIGIT = "0-9".freeze
35
- UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
36
- SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
40
+ ALPHA = "a-zA-Z"
41
+ DIGIT = "0-9"
42
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~"
43
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;="
37
44
 
38
45
  ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
39
46
 
@@ -55,12 +62,12 @@ module ActionDispatch
55
62
 
56
63
  def unescape_uri(uri)
57
64
  encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
58
- uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
65
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
59
66
  end
60
67
 
61
- protected
68
+ private
62
69
  def escape(component, pattern)
63
- component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
70
+ component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
64
71
  end
65
72
 
66
73
  def percent_encode(unsafe)
@@ -84,6 +91,10 @@ module ActionDispatch
84
91
  ENCODER.escape_fragment(fragment.to_s)
85
92
  end
86
93
 
94
+ # Replaces any escaped sequences with their unescaped representations.
95
+ #
96
+ # uri = "/topics?title=Ruby%20on%20Rails"
97
+ # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
87
98
  def self.unescape_uri(uri)
88
99
  ENCODER.unescape_uri(uri)
89
100
  end
@@ -1,31 +1,33 @@
1
- require 'action_dispatch/journey/router/utils'
2
- require 'action_dispatch/journey/router/strexp'
3
- require 'action_dispatch/journey/routes'
4
- require 'action_dispatch/journey/formatter'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/router/utils"
4
+ require "action_dispatch/journey/routes"
5
+ require "action_dispatch/journey/formatter"
5
6
 
6
7
  before = $-w
7
8
  $-w = false
8
- require 'action_dispatch/journey/parser'
9
+ require "action_dispatch/journey/parser"
9
10
  $-w = before
10
11
 
11
- require 'action_dispatch/journey/route'
12
- require 'action_dispatch/journey/path/pattern'
12
+ require "action_dispatch/journey/route"
13
+ require "action_dispatch/journey/path/pattern"
13
14
 
14
15
  module ActionDispatch
15
16
  module Journey # :nodoc:
16
17
  class Router # :nodoc:
17
- class RoutingError < ::StandardError # :nodoc:
18
- end
19
-
20
- # :nodoc:
21
- VERSION = '2.0.0'
22
-
23
18
  attr_accessor :routes
24
19
 
25
20
  def initialize(routes)
26
21
  @routes = routes
27
22
  end
28
23
 
24
+ def eager_load!
25
+ # Eagerly trigger the simulator's initialization so
26
+ # it doesn't happen during a request cycle.
27
+ simulator
28
+ nil
29
+ end
30
+
29
31
  def serve(req)
30
32
  find_routes(req).each do |match, parameters, route|
31
33
  set_params = req.path_parameters
@@ -33,16 +35,21 @@ module ActionDispatch
33
35
  script_name = req.script_name
34
36
 
35
37
  unless route.path.anchored
36
- req.script_name = (script_name.to_s + match.to_s).chomp('/')
38
+ req.script_name = (script_name.to_s + match.to_s).chomp("/")
37
39
  req.path_info = match.post_match
38
40
  req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
39
41
  end
40
42
 
41
- req.path_parameters = set_params.merge parameters
43
+ tmp_params = set_params.merge route.defaults
44
+ parameters.each_pair { |key, val|
45
+ tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
46
+ }
47
+
48
+ req.path_parameters = tmp_params
42
49
 
43
50
  status, headers, body = route.app.serve(req)
44
51
 
45
- if 'pass' == headers['X-Cascade']
52
+ if "pass" == headers["X-Cascade"]
46
53
  req.script_name = script_name
47
54
  req.path_info = path_info
48
55
  req.path_parameters = set_params
@@ -52,31 +59,34 @@ module ActionDispatch
52
59
  return [status, headers, body]
53
60
  end
54
61
 
55
- return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
62
+ [404, { "X-Cascade" => "pass" }, ["Not Found"]]
56
63
  end
57
64
 
58
65
  def recognize(rails_req)
59
66
  find_routes(rails_req).each do |match, parameters, route|
60
67
  unless route.path.anchored
61
68
  rails_req.script_name = match.to_s
62
- 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? "/"
63
71
  end
64
72
 
73
+ parameters = route.defaults.merge parameters
65
74
  yield(route, parameters)
66
75
  end
67
76
  end
68
77
 
69
78
  def visualizer
70
79
  tt = GTG::Builder.new(ast).transition_table
71
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
72
- asts = groups.values.map { |v| v.first }
80
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
81
+ asts = groups.values.map(&:first)
73
82
  tt.visualizer(asts)
74
83
  end
75
84
 
76
85
  private
77
-
78
86
  def partitioned_routes
79
- routes.partitioned_routes
87
+ routes.partition { |r|
88
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
89
+ }
80
90
  end
81
91
 
82
92
  def ast
@@ -88,7 +98,7 @@ module ActionDispatch
88
98
  end
89
99
 
90
100
  def custom_routes
91
- partitioned_routes.last
101
+ routes.custom_routes
92
102
  end
93
103
 
94
104
  def filter_routes(path)
@@ -96,24 +106,25 @@ module ActionDispatch
96
106
  simulator.memos(path) { [] }
97
107
  end
98
108
 
99
- def find_routes req
100
- routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
101
- r.path.match(req.path_info)
109
+ def find_routes(req)
110
+ path_info = req.path_info
111
+ routes = filter_routes(path_info).concat custom_routes.find_all { |r|
112
+ r.path.match?(path_info)
102
113
  }
103
114
 
104
- routes =
105
- if req.request_method == "HEAD"
106
- match_head_routes(routes, req)
107
- else
108
- match_routes(routes, req)
109
- end
115
+ if req.head?
116
+ routes = match_head_routes(routes, req)
117
+ else
118
+ routes.select! { |r| r.matches?(req) }
119
+ end
110
120
 
111
121
  routes.sort_by!(&:precedence)
112
122
 
113
123
  routes.map! { |r|
114
- match_data = r.path.match(req.path_info)
115
- path_parameters = r.defaults.dup
116
- match_data.names.zip(match_data.captures) { |name,val|
124
+ match_data = r.path.match(path_info)
125
+ path_parameters = {}
126
+ match_data.names.each_with_index { |name, i|
127
+ val = match_data[i + 1]
117
128
  path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
118
129
  }
119
130
  [match_data, path_parameters, r]
@@ -121,24 +132,17 @@ module ActionDispatch
121
132
  end
122
133
 
123
134
  def match_head_routes(routes, req)
124
- verb_specific_routes = routes.reject { |route| route.verb == // }
125
- head_routes = match_routes(verb_specific_routes, req)
126
-
127
- if head_routes.empty?
128
- begin
129
- req.request_method = "GET"
130
- match_routes(routes, req)
131
- ensure
132
- req.request_method = "HEAD"
133
- end
134
- else
135
- 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"
136
144
  end
137
145
  end
138
-
139
- def match_routes(routes, req)
140
- routes.select { |r| r.matches?(req) }
141
- end
142
146
  end
143
147
  end
144
148
  end