actionpack 7.2.1 → 8.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -105
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/metal/conditional_get.rb +6 -3
- data/lib/action_controller/metal/http_authentication.rb +2 -2
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +19 -8
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/renderers.rb +2 -1
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +274 -73
- data/lib/action_controller/railtie.rb +1 -1
- data/lib/action_controller/test_case.rb +4 -3
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +1 -0
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/request.rb +4 -2
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/scanner.rb +40 -42
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -6
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +14 -4
- data/lib/action_dispatch/railtie.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +26 -17
- data/lib/action_dispatch/routing/route_set.rb +18 -6
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +16 -38
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -55,6 +55,8 @@ module ActionDispatch
|
|
55
55
|
METHOD
|
56
56
|
end
|
57
57
|
|
58
|
+
TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
|
59
|
+
|
58
60
|
def self.empty
|
59
61
|
new({})
|
60
62
|
end
|
@@ -282,7 +284,7 @@ module ActionDispatch
|
|
282
284
|
|
283
285
|
# Returns the content length of the request as an integer.
|
284
286
|
def content_length
|
285
|
-
return raw_post.bytesize if
|
287
|
+
return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
|
286
288
|
super.to_i
|
287
289
|
end
|
288
290
|
|
@@ -468,7 +470,7 @@ module ActionDispatch
|
|
468
470
|
def read_body_stream
|
469
471
|
if body_stream
|
470
472
|
reset_stream(body_stream) do
|
471
|
-
if
|
473
|
+
if has_header?(TRANSFER_ENCODING)
|
472
474
|
body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
|
473
475
|
else
|
474
476
|
body_stream.read(content_length)
|
@@ -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
|
-
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
@@ -7,64 +7,62 @@ require "strscan"
|
|
7
7
|
module ActionDispatch
|
8
8
|
module Journey # :nodoc:
|
9
9
|
class Scanner # :nodoc:
|
10
|
+
STATIC_TOKENS = Array.new(150)
|
11
|
+
STATIC_TOKENS[".".ord] = :DOT
|
12
|
+
STATIC_TOKENS["/".ord] = :SLASH
|
13
|
+
STATIC_TOKENS["(".ord] = :LPAREN
|
14
|
+
STATIC_TOKENS[")".ord] = :RPAREN
|
15
|
+
STATIC_TOKENS["|".ord] = :OR
|
16
|
+
STATIC_TOKENS[":".ord] = :SYMBOL
|
17
|
+
STATIC_TOKENS["*".ord] = :STAR
|
18
|
+
STATIC_TOKENS.freeze
|
19
|
+
|
20
|
+
class Scanner < StringScanner
|
21
|
+
unless method_defined?(:peek_byte) # https://github.com/ruby/strscan/pull/89
|
22
|
+
def peek_byte
|
23
|
+
string.getbyte(pos)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
10
28
|
def initialize
|
11
|
-
@
|
29
|
+
@scanner = nil
|
30
|
+
@length = nil
|
12
31
|
end
|
13
32
|
|
14
33
|
def scan_setup(str)
|
15
|
-
@
|
34
|
+
@scanner = Scanner.new(str)
|
16
35
|
end
|
17
36
|
|
18
|
-
def
|
19
|
-
@
|
20
|
-
end
|
37
|
+
def next_token
|
38
|
+
return if @scanner.eos?
|
21
39
|
|
22
|
-
|
23
|
-
|
40
|
+
until token = scan || @scanner.eos?; end
|
41
|
+
token
|
24
42
|
end
|
25
43
|
|
26
|
-
def
|
27
|
-
@
|
44
|
+
def last_string
|
45
|
+
-@scanner.string.byteslice(@scanner.pos - @length, @length)
|
28
46
|
end
|
29
47
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
token
|
48
|
+
def last_literal
|
49
|
+
last_str = @scanner.string.byteslice(@scanner.pos - @length, @length)
|
50
|
+
last_str.tr! "\\", ""
|
51
|
+
-last_str
|
35
52
|
end
|
36
53
|
|
37
54
|
private
|
38
|
-
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
|
39
|
-
# https://bugs.ruby-lang.org/issues/13077
|
40
|
-
def dedup_scan(regex)
|
41
|
-
r = @ss.scan(regex)
|
42
|
-
r ? -r : nil
|
43
|
-
end
|
44
|
-
|
45
55
|
def scan
|
56
|
+
next_byte = @scanner.peek_byte
|
46
57
|
case
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
[:OR, "|"]
|
56
|
-
when @ss.skip(/\./)
|
57
|
-
[:DOT, "."]
|
58
|
-
when text = dedup_scan(/:\w+/)
|
59
|
-
[:SYMBOL, text]
|
60
|
-
when text = dedup_scan(/\*\w+/)
|
61
|
-
[:STAR, text]
|
62
|
-
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
|
63
|
-
text.tr! "\\", ""
|
64
|
-
[:LITERAL, -text]
|
65
|
-
# any char
|
66
|
-
when text = dedup_scan(/./)
|
67
|
-
[:LITERAL, text]
|
58
|
+
when (token = STATIC_TOKENS[next_byte])
|
59
|
+
@scanner.pos += 1
|
60
|
+
@length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR
|
61
|
+
token
|
62
|
+
when @length = @scanner.skip(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
|
63
|
+
:LITERAL
|
64
|
+
when @length = @scanner.skip(/./)
|
65
|
+
:LITERAL
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
@@ -116,13 +116,15 @@ module ActionDispatch
|
|
116
116
|
# cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
|
117
117
|
#
|
118
118
|
# # Sets a signed cookie, which prevents users from tampering with its value.
|
119
|
-
# # It can be read using the signed method `cookies.signed[:name]`
|
120
119
|
# cookies.signed[:user_id] = current_user.id
|
120
|
+
# # It can be read using the signed method.
|
121
|
+
# cookies.signed[:user_id] # => 123
|
121
122
|
#
|
122
123
|
# # Sets an encrypted cookie value before sending it to the client which
|
123
124
|
# # prevent users from reading and tampering with its value.
|
124
|
-
# # It can be read using the encrypted method `cookies.encrypted[:name]`
|
125
125
|
# cookies.encrypted[:discount] = 45
|
126
|
+
# # It can be read using the encrypted method.
|
127
|
+
# cookies.encrypted[:discount] # => 45
|
126
128
|
#
|
127
129
|
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
128
130
|
# cookies.permanent[:login] = "XJ-122"
|
@@ -142,17 +142,30 @@ module ActionDispatch
|
|
142
142
|
|
143
143
|
message = []
|
144
144
|
message << " "
|
145
|
-
message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
|
146
145
|
if wrapper.has_cause?
|
147
|
-
message << "
|
146
|
+
message << "#{wrapper.exception_class_name} (#{wrapper.message})"
|
148
147
|
wrapper.wrapped_causes.each do |wrapped_cause|
|
149
|
-
message << "#{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
|
148
|
+
message << "Caused by: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
|
150
149
|
end
|
150
|
+
|
151
|
+
message << "\nInformation for: #{wrapper.exception_class_name} (#{wrapper.message}):"
|
152
|
+
else
|
153
|
+
message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
|
151
154
|
end
|
155
|
+
|
152
156
|
message.concat(wrapper.annotated_source_code)
|
153
157
|
message << " "
|
154
158
|
message.concat(trace)
|
155
159
|
|
160
|
+
if wrapper.has_cause?
|
161
|
+
wrapper.wrapped_causes.each do |wrapped_cause|
|
162
|
+
message << "\nInformation for cause: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message}):"
|
163
|
+
message.concat(wrapped_cause.annotated_source_code)
|
164
|
+
message << " "
|
165
|
+
message.concat(wrapped_cause.exception_trace)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
156
169
|
log_array(logger, message, request)
|
157
170
|
end
|
158
171
|
|
@@ -18,8 +18,8 @@ module ActionDispatch
|
|
18
18
|
# 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) requires.
|
19
19
|
# Some Rack servers simply drop preceding headers, and only report the value
|
20
20
|
# that was [given in the last
|
21
|
-
# header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-
|
22
|
-
#
|
21
|
+
# header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers).
|
22
|
+
# If you are behind multiple proxy servers (like NGINX to HAProxy to
|
23
23
|
# Unicorn) then you should test your Rack server to make sure your data is good.
|
24
24
|
#
|
25
25
|
# IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This
|
@@ -117,10 +117,9 @@ module ActionDispatch
|
|
117
117
|
# instead, so we check that too.
|
118
118
|
#
|
119
119
|
# As discussed in [this post about Rails IP
|
120
|
-
# Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.
|
121
|
-
#
|
122
|
-
#
|
123
|
-
# by the client maliciously.
|
120
|
+
# Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/),
|
121
|
+
# while the first IP in the list is likely to be the "originating" IP, it
|
122
|
+
# could also have been set by the client maliciously.
|
124
123
|
#
|
125
124
|
# In order to find the first address that is (probably) accurate, we take the
|
126
125
|
# list of IPs, remove known and trusted proxies, and then take the last address
|
@@ -25,11 +25,12 @@ module ActionDispatch
|
|
25
25
|
def initialize(app, header:)
|
26
26
|
@app = app
|
27
27
|
@header = header
|
28
|
+
@env_header = "HTTP_#{header.upcase.tr("-", "_")}"
|
28
29
|
end
|
29
30
|
|
30
31
|
def call(env)
|
31
32
|
req = ActionDispatch::Request.new env
|
32
|
-
req.request_id = make_request_id(req.
|
33
|
+
req.request_id = make_request_id(req.get_header(@env_header))
|
33
34
|
@app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
|
34
35
|
end
|
35
36
|
|
@@ -11,16 +11,26 @@ module ActionDispatch
|
|
11
11
|
#
|
12
12
|
# 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
|
13
13
|
# with the same URL host, path, etc. Enabled by default. Set
|
14
|
-
# `config.ssl_options` to modify the destination URL
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# `config.ssl_options` to modify the destination URL:
|
15
|
+
#
|
16
|
+
# config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }`
|
17
|
+
#
|
18
|
+
# Or set `redirect: false` to disable redirection.
|
17
19
|
#
|
18
20
|
# Requests can opt-out of redirection with `exclude`:
|
19
21
|
#
|
20
|
-
# config.ssl_options = { redirect: { exclude: -> request {
|
22
|
+
# config.ssl_options = { redirect: { exclude: -> request { request.path == "/up" } } }
|
21
23
|
#
|
22
24
|
# Cookies will not be flagged as secure for excluded requests.
|
23
25
|
#
|
26
|
+
# When proxying through a load balancer that terminates SSL, the forwarded
|
27
|
+
# request will appear as though it's HTTP instead of HTTPS to the application.
|
28
|
+
# This makes redirects and cookie security target HTTP instead of HTTPS.
|
29
|
+
# To make the server assume that the proxy already terminated SSL, and
|
30
|
+
# that the request really is HTTPS, set `config.assume_ssl` to `true`:
|
31
|
+
#
|
32
|
+
# config.assume_ssl = true
|
33
|
+
#
|
24
34
|
# 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
|
25
35
|
# they must not be sent along with `http://` requests. Enabled by default.
|
26
36
|
# Set `config.ssl_options` with `secure_cookies: false` to disable this
|
@@ -29,6 +29,7 @@ module ActionDispatch
|
|
29
29
|
config.action_dispatch.request_id_header = ActionDispatch::Constants::X_REQUEST_ID
|
30
30
|
config.action_dispatch.log_rescued_responses = true
|
31
31
|
config.action_dispatch.debug_exception_log_level = :fatal
|
32
|
+
config.action_dispatch.strict_freshness = false
|
32
33
|
|
33
34
|
config.action_dispatch.default_headers = {
|
34
35
|
"X-Frame-Options" => "SAMEORIGIN",
|
@@ -69,6 +70,7 @@ module ActionDispatch
|
|
69
70
|
|
70
71
|
ActionDispatch::Routing::Mapper.route_source_locations = Rails.env.development?
|
71
72
|
|
73
|
+
ActionDispatch::Http::Cache::Request.strict_freshness = app.config.action_dispatch.strict_freshness
|
72
74
|
ActionDispatch.test_app = app
|
73
75
|
end
|
74
76
|
end
|
@@ -101,7 +101,7 @@ module ActionDispatch
|
|
101
101
|
{ controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
|
102
102
|
elsif filter[:grep]
|
103
103
|
grep_pattern = Regexp.new(filter[:grep])
|
104
|
-
path = URI::
|
104
|
+
path = URI::RFC2396_PARSER.escape(filter[:grep])
|
105
105
|
normalized_path = ("/" + path).squeeze("/")
|
106
106
|
|
107
107
|
{
|