actionpack 7.2.3 → 8.1.3

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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +394 -119
  3. data/lib/abstract_controller/asset_paths.rb +4 -2
  4. data/lib/abstract_controller/base.rb +11 -5
  5. data/lib/abstract_controller/caching.rb +6 -3
  6. data/lib/abstract_controller/callbacks.rb +6 -0
  7. data/lib/abstract_controller/logger.rb +2 -1
  8. data/lib/abstract_controller/rendering.rb +0 -1
  9. data/lib/action_controller/api.rb +1 -0
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching.rb +1 -2
  12. data/lib/action_controller/form_builder.rb +4 -4
  13. data/lib/action_controller/log_subscriber.rb +22 -3
  14. data/lib/action_controller/metal/allow_browser.rb +12 -2
  15. data/lib/action_controller/metal/conditional_get.rb +30 -1
  16. data/lib/action_controller/metal/data_streaming.rb +5 -5
  17. data/lib/action_controller/metal/exceptions.rb +5 -0
  18. data/lib/action_controller/metal/flash.rb +1 -4
  19. data/lib/action_controller/metal/head.rb +3 -1
  20. data/lib/action_controller/metal/instrumentation.rb +1 -2
  21. data/lib/action_controller/metal/live.rb +65 -25
  22. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  23. data/lib/action_controller/metal/rate_limiting.rb +39 -9
  24. data/lib/action_controller/metal/redirecting.rb +105 -13
  25. data/lib/action_controller/metal/renderers.rb +29 -9
  26. data/lib/action_controller/metal/rendering.rb +7 -1
  27. data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
  28. data/lib/action_controller/metal/rescue.rb +9 -0
  29. data/lib/action_controller/metal/streaming.rb +5 -84
  30. data/lib/action_controller/metal/strong_parameters.rb +277 -89
  31. data/lib/action_controller/railtie.rb +33 -15
  32. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  33. data/lib/action_controller/test_case.rb +12 -2
  34. data/lib/action_dispatch/http/cache.rb +138 -11
  35. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  36. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  37. data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
  38. data/lib/action_dispatch/http/mime_types.rb +1 -0
  39. data/lib/action_dispatch/http/param_builder.rb +187 -0
  40. data/lib/action_dispatch/http/param_error.rb +26 -0
  41. data/lib/action_dispatch/http/parameters.rb +3 -3
  42. data/lib/action_dispatch/http/permissions_policy.rb +6 -0
  43. data/lib/action_dispatch/http/query_parser.rb +55 -0
  44. data/lib/action_dispatch/http/request.rb +70 -21
  45. data/lib/action_dispatch/http/response.rb +50 -16
  46. data/lib/action_dispatch/http/url.rb +110 -14
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -41
  49. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  50. data/lib/action_dispatch/journey/parser.rb +99 -196
  51. data/lib/action_dispatch/journey/route.rb +45 -31
  52. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  53. data/lib/action_dispatch/journey/router.rb +59 -81
  54. data/lib/action_dispatch/journey/routes.rb +7 -0
  55. data/lib/action_dispatch/journey/scanner.rb +44 -42
  56. data/lib/action_dispatch/journey/visitors.rb +55 -23
  57. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  58. data/lib/action_dispatch/log_subscriber.rb +7 -3
  59. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  60. data/lib/action_dispatch/middleware/debug_exceptions.rb +24 -5
  61. data/lib/action_dispatch/middleware/debug_view.rb +11 -5
  62. data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -11
  63. data/lib/action_dispatch/middleware/executor.rb +12 -2
  64. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  65. data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
  66. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  67. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  68. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  69. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  70. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  71. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  72. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  73. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  74. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  75. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  79. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  82. data/lib/action_dispatch/railtie.rb +21 -0
  83. data/lib/action_dispatch/request/session.rb +1 -0
  84. data/lib/action_dispatch/request/utils.rb +9 -3
  85. data/lib/action_dispatch/routing/inspector.rb +80 -57
  86. data/lib/action_dispatch/routing/mapper.rb +404 -223
  87. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  88. data/lib/action_dispatch/routing/redirection.rb +10 -7
  89. data/lib/action_dispatch/routing/route_set.rb +21 -12
  90. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  91. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  92. data/lib/action_dispatch/system_test_case.rb +3 -3
  93. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  94. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  95. data/lib/action_dispatch/testing/assertions/response.rb +26 -2
  96. data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
  97. data/lib/action_dispatch/testing/integration.rb +18 -7
  98. data/lib/action_dispatch.rb +14 -4
  99. data/lib/action_pack/gem_version.rb +2 -2
  100. metadata +18 -48
  101. data/lib/action_dispatch/journey/parser.y +0 -50
  102. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -13,7 +13,6 @@ module ActionDispatch
13
13
  attr_reader :memos
14
14
 
15
15
  DEFAULT_EXP = /[^.\/?]+/
16
- DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
17
16
 
18
17
  def initialize
19
18
  @stdparam_states = {}
@@ -47,25 +46,27 @@ module ActionDispatch
47
46
  Array(t)
48
47
  end
49
48
 
50
- def move(t, full_string, start_index, end_index)
49
+ def move(t, full_string, token, start_index, token_matches_default)
51
50
  return [] if t.empty?
52
51
 
53
52
  next_states = []
54
53
 
55
- tok = full_string.slice(start_index, end_index - start_index)
56
- token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
57
-
58
- t.each { |s, previous_start|
54
+ transitions_count = t.size
55
+ i = 0
56
+ while i < transitions_count
57
+ s = t[i]
58
+ previous_start = t[i + 1]
59
59
  if previous_start.nil?
60
60
  # In the simple case of a "default" param regex do this fast-path and add all
61
61
  # next states.
62
- if token_matches_default_component && states = @stdparam_states[s]
63
- states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
62
+ if token_matches_default && std_state = @stdparam_states[s]
63
+ next_states << std_state << nil
64
64
  end
65
65
 
66
66
  # When we have a literal string, we can just pull the next state
67
67
  if states = @string_states[s]
68
- next_states << [states[tok], nil].freeze unless states[tok].nil?
68
+ state = states[token]
69
+ next_states << state << nil unless state.nil?
69
70
  end
70
71
  end
71
72
 
@@ -80,19 +81,21 @@ module ActionDispatch
80
81
  previous_start
81
82
  end
82
83
 
83
- slice_length = end_index - slice_start
84
+ slice_length = start_index + token.length - slice_start
84
85
  curr_slice = full_string.slice(slice_start, slice_length)
85
86
 
86
87
  states.each { |re, v|
87
88
  # if we match, we can try moving past this
88
- next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
89
+ next_states << v << nil if !v.nil? && re.match?(curr_slice)
89
90
  }
90
91
 
91
92
  # and regardless, we must continue accepting tokens and retrying this regexp. we
92
93
  # need to remember where we started as well so we can take bigger slices.
93
- next_states << [s, slice_start].freeze
94
+ next_states << s << slice_start
94
95
  end
95
- }
96
+
97
+ i += 2
98
+ end
96
99
 
97
100
  next_states
98
101
  end
@@ -163,54 +166,43 @@ module ActionDispatch
163
166
  end
164
167
 
165
168
  def []=(from, to, sym)
166
- to_mappings = states_hash_for(sym)[from] ||= {}
167
169
  case sym
170
+ when String, Symbol
171
+ to_mapping = @string_states[from] ||= {}
172
+ # account for symbols in the constraints the same as strings
173
+ to_mapping[sym.to_s] = to
168
174
  when Regexp
169
- # we must match the whole string to a token boundary
170
175
  if sym == DEFAULT_EXP
171
- sym = DEFAULT_EXP_ANCHORED
176
+ @stdparam_states[from] = to
172
177
  else
173
- sym = /\A#{sym}\Z/
178
+ to_mapping = @regexp_states[from] ||= {}
179
+ # we must match the whole string to a token boundary
180
+ to_mapping[/\A#{sym}\Z/] = to
174
181
  end
175
- when Symbol
176
- # account for symbols in the constraints the same as strings
177
- sym = sym.to_s
182
+ else
183
+ raise ArgumentError, "unknown symbol: %s" % sym.class
178
184
  end
179
- to_mappings[sym] = to
180
185
  end
181
186
 
182
187
  def states
183
188
  ss = @string_states.keys + @string_states.values.flat_map(&:values)
184
- ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
189
+ ps = @stdparam_states.keys + @stdparam_states.values
185
190
  rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
186
191
  (ss + ps + rs).uniq
187
192
  end
188
193
 
189
194
  def transitions
195
+ # double escaped because dot evaluates escapes
196
+ default_exp_anchored = "\\\\A#{DEFAULT_EXP.source}\\\\Z"
197
+
190
198
  @string_states.flat_map { |from, hash|
191
199
  hash.map { |s, to| [from, s, to] }
192
- } + @stdparam_states.flat_map { |from, hash|
193
- hash.map { |s, to| [from, s, to] }
200
+ } + @stdparam_states.map { |from, to|
201
+ [from, default_exp_anchored, to]
194
202
  } + @regexp_states.flat_map { |from, hash|
195
- hash.map { |s, to| [from, s, to] }
203
+ hash.map { |r, to| [from, r.source.gsub("\\") { "\\\\" }, to] }
196
204
  }
197
205
  end
198
-
199
- private
200
- def states_hash_for(sym)
201
- case sym
202
- when String, Symbol
203
- @string_states
204
- when Regexp
205
- if sym == DEFAULT_EXP
206
- @stdparam_states
207
- else
208
- @regexp_states
209
- end
210
- else
211
- raise ArgumentError, "unknown symbol: %s" % sym.class
212
- end
213
- end
214
206
  end
215
207
  end
216
208
  end
@@ -74,6 +74,7 @@ module ActionDispatch
74
74
  def initialize(left)
75
75
  @left = left
76
76
  @memo = nil
77
+ @to_s = nil
77
78
  end
78
79
 
79
80
  def each(&block)
@@ -81,7 +82,7 @@ module ActionDispatch
81
82
  end
82
83
 
83
84
  def to_s
84
- Visitors::String::INSTANCE.accept(self, "")
85
+ @to_s ||= Visitors::String::INSTANCE.accept(self, "".dup).freeze
85
86
  end
86
87
 
87
88
  def to_dot
@@ -1,200 +1,103 @@
1
- #
2
- # DO NOT MODIFY!!!!
3
- # This file is automatically generated by Racc 1.4.16 from
4
- # Racc grammar file "".
1
+ # frozen_string_literal: true
5
2
 
6
- # :markup: markdown
3
+ require "action_dispatch/journey/scanner"
4
+ require "action_dispatch/journey/nodes/node"
7
5
 
8
- require 'racc/parser.rb'
9
-
10
- # :stopdoc:
11
-
12
- require "action_dispatch/journey/parser_extras"
13
6
  module ActionDispatch
14
- module Journey
15
- class Parser < Racc::Parser
16
- ##### State transition tables begin ###
17
-
18
- racc_action_table = [
19
- 13, 15, 14, 7, 19, 16, 8, 19, 13, 15,
20
- 14, 7, 17, 16, 8, 13, 15, 14, 7, 21,
21
- 16, 8, 13, 15, 14, 7, 24, 16, 8 ]
22
-
23
- racc_action_check = [
24
- 2, 2, 2, 2, 22, 2, 2, 2, 19, 19,
25
- 19, 19, 1, 19, 19, 7, 7, 7, 7, 17,
26
- 7, 7, 0, 0, 0, 0, 20, 0, 0 ]
27
-
28
- racc_action_pointer = [
29
- 20, 12, -2, nil, nil, nil, nil, 13, nil, nil,
30
- nil, nil, nil, nil, nil, nil, nil, 19, nil, 6,
31
- 20, nil, -5, nil, nil ]
32
-
33
- racc_action_default = [
34
- -19, -19, -2, -3, -4, -5, -6, -19, -10, -11,
35
- -12, -13, -14, -15, -16, -17, -18, -19, -1, -19,
36
- -19, 25, -8, -9, -7 ]
37
-
38
- racc_goto_table = [
39
- 1, 22, 18, 23, nil, nil, nil, 20 ]
40
-
41
- racc_goto_check = [
42
- 1, 2, 1, 3, nil, nil, nil, 1 ]
43
-
44
- racc_goto_pointer = [
45
- nil, 0, -18, -16, nil, nil, nil, nil, nil, nil,
46
- nil ]
47
-
48
- racc_goto_default = [
49
- nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
50
- 12 ]
51
-
52
- racc_reduce_table = [
53
- 0, 0, :racc_error,
54
- 2, 11, :_reduce_1,
55
- 1, 11, :_reduce_2,
56
- 1, 11, :_reduce_none,
57
- 1, 12, :_reduce_none,
58
- 1, 12, :_reduce_none,
59
- 1, 12, :_reduce_none,
60
- 3, 15, :_reduce_7,
61
- 3, 13, :_reduce_8,
62
- 3, 13, :_reduce_9,
63
- 1, 16, :_reduce_10,
64
- 1, 14, :_reduce_none,
65
- 1, 14, :_reduce_none,
66
- 1, 14, :_reduce_none,
67
- 1, 14, :_reduce_none,
68
- 1, 19, :_reduce_15,
69
- 1, 17, :_reduce_16,
70
- 1, 18, :_reduce_17,
71
- 1, 20, :_reduce_18 ]
72
-
73
- racc_reduce_n = 19
74
-
75
- racc_shift_n = 25
76
-
77
- racc_token_table = {
78
- false => 0,
79
- :error => 1,
80
- :SLASH => 2,
81
- :LITERAL => 3,
82
- :SYMBOL => 4,
83
- :LPAREN => 5,
84
- :RPAREN => 6,
85
- :DOT => 7,
86
- :STAR => 8,
87
- :OR => 9 }
88
-
89
- racc_nt_base = 10
90
-
91
- racc_use_result_var = false
92
-
93
- Racc_arg = [
94
- racc_action_table,
95
- racc_action_check,
96
- racc_action_default,
97
- racc_action_pointer,
98
- racc_goto_table,
99
- racc_goto_check,
100
- racc_goto_default,
101
- racc_goto_pointer,
102
- racc_nt_base,
103
- racc_reduce_table,
104
- racc_token_table,
105
- racc_shift_n,
106
- racc_reduce_n,
107
- racc_use_result_var ]
108
-
109
- Racc_token_to_s_table = [
110
- "$end",
111
- "error",
112
- "SLASH",
113
- "LITERAL",
114
- "SYMBOL",
115
- "LPAREN",
116
- "RPAREN",
117
- "DOT",
118
- "STAR",
119
- "OR",
120
- "$start",
121
- "expressions",
122
- "expression",
123
- "or",
124
- "terminal",
125
- "group",
126
- "star",
127
- "symbol",
128
- "literal",
129
- "slash",
130
- "dot" ]
131
-
132
- Racc_debug_parser = false
133
-
134
- ##### State transition tables end #####
135
-
136
- # reduce 0 omitted
137
-
138
- def _reduce_1(val, _values)
139
- Cat.new(val.first, val.last)
140
- end
141
-
142
- def _reduce_2(val, _values)
143
- val.first
144
- end
145
-
146
- # reduce 3 omitted
147
-
148
- # reduce 4 omitted
149
-
150
- # reduce 5 omitted
151
-
152
- # reduce 6 omitted
153
-
154
- def _reduce_7(val, _values)
155
- Group.new(val[1])
156
- end
157
-
158
- def _reduce_8(val, _values)
159
- Or.new([val.first, val.last])
160
- end
161
-
162
- def _reduce_9(val, _values)
163
- Or.new([val.first, val.last])
164
- end
165
-
166
- def _reduce_10(val, _values)
167
- Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP))
7
+ module Journey # :nodoc:
8
+ class Parser # :nodoc:
9
+ include Journey::Nodes
10
+
11
+ def self.parse(string)
12
+ new.parse string
13
+ end
14
+
15
+ def initialize
16
+ @scanner = Scanner.new
17
+ @next_token = nil
18
+ end
19
+
20
+ def parse(string)
21
+ @scanner.scan_setup(string)
22
+ advance_token
23
+ do_parse
24
+ end
25
+
26
+ private
27
+ def advance_token
28
+ @next_token = @scanner.next_token
29
+ end
30
+
31
+ def do_parse
32
+ parse_expressions
33
+ end
34
+
35
+ def parse_expressions
36
+ node = parse_expression
37
+
38
+ while @next_token
39
+ case @next_token
40
+ when :RPAREN
41
+ break
42
+ when :OR
43
+ node = parse_or(node)
44
+ else
45
+ node = Cat.new(node, parse_expressions)
46
+ end
47
+ end
48
+
49
+ node
50
+ end
51
+
52
+ def parse_or(lhs)
53
+ advance_token
54
+ node = parse_expression
55
+ Or.new([lhs, node])
56
+ end
57
+
58
+ def parse_expression
59
+ if @next_token == :STAR
60
+ parse_star
61
+ elsif @next_token == :LPAREN
62
+ parse_group
63
+ else
64
+ parse_terminal
65
+ end
66
+ end
67
+
68
+ def parse_star
69
+ node = Star.new(Symbol.new(@scanner.last_string, Symbol::GREEDY_EXP))
70
+ advance_token
71
+ node
72
+ end
73
+
74
+ def parse_group
75
+ advance_token
76
+ node = parse_expressions
77
+ if @next_token == :RPAREN
78
+ node = Group.new(node)
79
+ advance_token
80
+ node
81
+ else
82
+ raise ArgumentError, "missing right parenthesis."
83
+ end
84
+ end
85
+
86
+ def parse_terminal
87
+ node = case @next_token
88
+ when :SYMBOL
89
+ Symbol.new(@scanner.last_string)
90
+ when :LITERAL
91
+ Literal.new(@scanner.last_literal)
92
+ when :SLASH
93
+ Slash.new("/")
94
+ when :DOT
95
+ Dot.new(".")
96
+ end
97
+
98
+ advance_token
99
+ node
100
+ end
101
+ end
102
+ end
168
103
  end
169
-
170
- # reduce 11 omitted
171
-
172
- # reduce 12 omitted
173
-
174
- # reduce 13 omitted
175
-
176
- # reduce 14 omitted
177
-
178
- def _reduce_15(val, _values)
179
- Slash.new(val.first)
180
- end
181
-
182
- def _reduce_16(val, _values)
183
- Symbol.new(val.first)
184
- end
185
-
186
- def _reduce_17(val, _values)
187
- Literal.new(val.first)
188
- end
189
-
190
- def _reduce_18(val, _values)
191
- Dot.new(val.first)
192
- end
193
-
194
- def _reduce_none(val, _values)
195
- val[0]
196
- end
197
-
198
- end # class Parser
199
- end # module Journey
200
- end # module ActionDispatch
@@ -38,29 +38,50 @@ module ActionDispatch
38
38
  def self.verb; ""; end
39
39
  end
40
40
 
41
+ class Or
42
+ attr_reader :verb
43
+
44
+ def initialize(verbs)
45
+ @verbs = verbs
46
+ @verb = @verbs.map(&:verb).join("|")
47
+ end
48
+
49
+ def call(req)
50
+ @verbs.any? { |v| v.call req }
51
+ end
52
+ end
53
+
41
54
  VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
42
55
  klass = const_get verb
43
56
  hash[verb] = klass
44
57
  hash[verb.downcase] = klass
45
58
  hash[verb.downcase.to_sym] = klass
46
59
  end
47
- end
48
60
 
49
- def self.verb_matcher(verb)
50
- VerbMatchers::VERB_TO_CLASS.fetch(verb) do
61
+ VERB_TO_CLASS.default_proc = proc do |_, verb|
51
62
  VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
52
63
  end
64
+
65
+ def self.for(verbs)
66
+ if verbs.any? { |v| VERB_TO_CLASS[v] == All }
67
+ All
68
+ elsif verbs.one?
69
+ VERB_TO_CLASS[verbs.first]
70
+ else
71
+ Or.new(verbs.map { |v| VERB_TO_CLASS[v] })
72
+ end
73
+ end
53
74
  end
54
75
 
55
76
  ##
56
77
  # +path+ is a path constraint.
57
78
  # `constraints` is a hash of constraints to be applied to this route.
58
- def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
79
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, via: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
59
80
  @name = name
60
81
  @app = app
61
82
  @path = path
62
83
 
63
- @request_method_match = request_method_match
84
+ @request_method_match = via && VerbMatchers.for(via)
64
85
  @constraints = constraints
65
86
  @defaults = defaults
66
87
  @required_defaults = nil
@@ -146,21 +167,23 @@ module ActionDispatch
146
167
  end
147
168
 
148
169
  def matches?(request)
149
- match_verb(request) &&
150
- constraints.all? { |method, value|
151
- case value
152
- when Regexp, String
153
- value === request.send(method).to_s
154
- when Array
155
- value.include?(request.send(method))
156
- when TrueClass
157
- request.send(method).present?
158
- when FalseClass
159
- request.send(method).blank?
160
- else
161
- value === request.send(method)
162
- end
163
- }
170
+ @request_method_match.call(request) && (
171
+ constraints.empty? ||
172
+ constraints.all? { |method, value|
173
+ case value
174
+ when Regexp, String
175
+ value === request.send(method).to_s
176
+ when Array
177
+ value.include?(request.send(method))
178
+ when TrueClass
179
+ request.send(method).present?
180
+ when FalseClass
181
+ request.send(method).blank?
182
+ else
183
+ value === request.send(method)
184
+ end
185
+ }
186
+ )
164
187
  end
165
188
 
166
189
  def ip
@@ -168,21 +191,12 @@ module ActionDispatch
168
191
  end
169
192
 
170
193
  def requires_matching_verb?
171
- !@request_method_match.all? { |x| x == VerbMatchers::All }
194
+ @request_method_match != VerbMatchers::All
172
195
  end
173
196
 
174
197
  def verb
175
- verbs.join("|")
198
+ @request_method_match.verb
176
199
  end
177
-
178
- private
179
- def verbs
180
- @request_method_match.map(&:verb)
181
- end
182
-
183
- def match_verb(request)
184
- @request_method_match.any? { |m| m.call request }
185
- end
186
200
  end
187
201
  end
188
202
  # :startdoc:
@@ -17,7 +17,14 @@ module ActionDispatch
17
17
  # normalize_path("") # => "/"
18
18
  # normalize_path("/%ab") # => "/%AB"
19
19
  def self.normalize_path(path)
20
- path ||= ""
20
+ return "/".dup unless path
21
+
22
+ # Fast path for the overwhelming majority of paths that don't need to be normalized
23
+ if path == "/" || (path.start_with?("/") && !path.end_with?("/") && !path.match?(%r{%|//}))
24
+ return path.dup
25
+ end
26
+
27
+ # Slow path
21
28
  encoding = path.encoding
22
29
  path = +"/#{path}"
23
30
  path.squeeze!("/")
@@ -61,11 +68,6 @@ module ActionDispatch
61
68
  escape(segment, SEGMENT)
62
69
  end
63
70
 
64
- def unescape_uri(uri)
65
- encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
66
- uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
67
- end
68
-
69
71
  private
70
72
  def escape(component, pattern)
71
73
  component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
@@ -91,14 +93,6 @@ module ActionDispatch
91
93
  def self.escape_fragment(fragment)
92
94
  ENCODER.escape_fragment(fragment.to_s)
93
95
  end
94
-
95
- # Replaces any escaped sequences with their unescaped representations.
96
- #
97
- # uri = "/topics?title=Ruby%20on%20Rails"
98
- # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
99
- def self.unescape_uri(uri)
100
- ENCODER.unescape_uri(uri)
101
- end
102
96
  end
103
97
  end
104
98
  end