actionpack 5.0.0.beta1.1 → 5.0.0.beta2
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 -28
- data/MIT-LICENSE +1 -1
- data/lib/abstract_controller/base.rb +2 -2
- data/lib/abstract_controller/rendering.rb +5 -5
- data/lib/action_controller.rb +4 -0
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/metal.rb +2 -1
- data/lib/action_controller/metal/conditional_get.rb +1 -1
- data/lib/action_controller/metal/head.rb +0 -1
- data/lib/action_controller/metal/mime_responds.rb +9 -4
- data/lib/action_controller/metal/renderers.rb +75 -32
- data/lib/action_controller/metal/request_forgery_protection.rb +54 -11
- data/lib/action_controller/metal/strong_parameters.rb +33 -10
- data/lib/action_controller/test_case.rb +8 -8
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +10 -2
- data/lib/action_dispatch/http/headers.rb +15 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +3 -3
- data/lib/action_dispatch/http/mime_type.rb +38 -47
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/request.rb +1 -1
- data/lib/action_dispatch/http/response.rb +8 -1
- data/lib/action_dispatch/journey/path/pattern.rb +1 -1
- data/lib/action_dispatch/middleware/ssl.rb +23 -17
- data/lib/action_dispatch/middleware/stack.rb +9 -0
- data/lib/action_dispatch/middleware/static.rb +5 -1
- data/lib/action_dispatch/request/session.rb +3 -3
- data/lib/action_dispatch/routing.rb +2 -1
- data/lib/action_dispatch/routing/inspector.rb +22 -10
- data/lib/action_dispatch/routing/mapper.rb +41 -35
- data/lib/action_dispatch/routing/route_set.rb +11 -2
- data/lib/action_dispatch/testing/assertion_response.rb +49 -0
- data/lib/action_dispatch/testing/assertions/response.rb +14 -14
- data/lib/action_dispatch/testing/test_process.rb +0 -1
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +12 -9
@@ -259,7 +259,7 @@ module ActionDispatch
|
|
259
259
|
end
|
260
260
|
|
261
261
|
# Returns the IP address of client as a +String+,
|
262
|
-
#
|
262
|
+
# usually set by the RemoteIp middleware.
|
263
263
|
def remote_ip
|
264
264
|
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
|
265
265
|
end
|
@@ -232,7 +232,7 @@ module ActionDispatch # :nodoc:
|
|
232
232
|
end
|
233
233
|
|
234
234
|
# Sets the HTTP character set. In case of nil parameter
|
235
|
-
#
|
235
|
+
# it sets the charset to utf-8.
|
236
236
|
#
|
237
237
|
# response.charset = 'utf-16' # => 'utf-16'
|
238
238
|
# response.charset = nil # => 'utf-8'
|
@@ -412,6 +412,13 @@ module ActionDispatch # :nodoc:
|
|
412
412
|
end
|
413
413
|
|
414
414
|
def before_sending
|
415
|
+
# Normally we've already committed by now, but it's possible
|
416
|
+
# (e.g., if the controller action tries to read back its own
|
417
|
+
# response) to get here before that. In that case, we must force
|
418
|
+
# an "early" commit: we're about to freeze the headers, so this is
|
419
|
+
# our last chance.
|
420
|
+
commit! unless committed?
|
421
|
+
|
415
422
|
headers.freeze
|
416
423
|
request.commit_cookie_jar! unless committed?
|
417
424
|
end
|
@@ -1,19 +1,23 @@
|
|
1
1
|
module ActionDispatch
|
2
|
-
# This middleware is added to the stack when `config.force_ssl = true
|
3
|
-
# It does three jobs to enforce secure HTTP
|
2
|
+
# This middleware is added to the stack when `config.force_ssl = true`, and is passed
|
3
|
+
# the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
|
4
|
+
# requests:
|
4
5
|
#
|
5
|
-
# 1. TLS redirect
|
6
|
-
# with the same URL host, path, etc.
|
7
|
-
# modify the destination URL
|
6
|
+
# 1. TLS redirect: Permanently redirects http:// requests to https://
|
7
|
+
# with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
|
8
|
+
# to modify the destination URL
|
9
|
+
# (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
|
10
|
+
# `redirect: false` to disable this feature.
|
8
11
|
#
|
9
|
-
# 2. Secure cookies
|
10
|
-
# mustn't be sent along with http:// requests.
|
12
|
+
# 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
|
13
|
+
# mustn't be sent along with http:// requests. Enabled by default. Set
|
14
|
+
# `config.ssl_options` with `secure_cookies: false` to disable this feature.
|
11
15
|
#
|
12
|
-
# 3. HTTP Strict Transport Security (HSTS)
|
16
|
+
# 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
|
13
17
|
# this site as TLS-only and automatically redirect non-TLS requests.
|
14
|
-
# Enabled by default.
|
18
|
+
# Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
|
15
19
|
#
|
16
|
-
#
|
20
|
+
# Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
|
17
21
|
# * `expires`: How long, in seconds, these settings will stick. Defaults to
|
18
22
|
# `180.days` (recommended). The minimum required to qualify for browser
|
19
23
|
# preload lists is `18.weeks`.
|
@@ -26,10 +30,10 @@ module ActionDispatch
|
|
26
30
|
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
|
27
31
|
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
|
28
32
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
+
# To turn off HSTS, omitting the header is not enough. Browsers will remember the
|
34
|
+
# original HSTS directive until it expires. Instead, use the header to tell browsers to
|
35
|
+
# expire HSTS immediately. Setting `hsts: false` is a shortcut for
|
36
|
+
# `hsts: { expires: 0 }`.
|
33
37
|
class SSL
|
34
38
|
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
|
35
39
|
# and greater than the 18-week requirement for browser preload lists.
|
@@ -39,7 +43,7 @@ module ActionDispatch
|
|
39
43
|
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
|
40
44
|
end
|
41
45
|
|
42
|
-
def initialize(app, redirect: {}, hsts: {}, **options)
|
46
|
+
def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
|
43
47
|
@app = app
|
44
48
|
|
45
49
|
if options[:host] || options[:port]
|
@@ -52,6 +56,7 @@ module ActionDispatch
|
|
52
56
|
@redirect = redirect
|
53
57
|
end
|
54
58
|
|
59
|
+
@secure_cookies = secure_cookies
|
55
60
|
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
56
61
|
end
|
57
62
|
|
@@ -61,10 +66,11 @@ module ActionDispatch
|
|
61
66
|
if request.ssl?
|
62
67
|
@app.call(env).tap do |status, headers, body|
|
63
68
|
set_hsts_header! headers
|
64
|
-
flag_cookies_as_secure! headers
|
69
|
+
flag_cookies_as_secure! headers if @secure_cookies
|
65
70
|
end
|
66
71
|
else
|
67
|
-
redirect_to_https request
|
72
|
+
return redirect_to_https request if @redirect
|
73
|
+
@app.call(env)
|
68
74
|
end
|
69
75
|
end
|
70
76
|
|
@@ -14,6 +14,15 @@ module ActionDispatch
|
|
14
14
|
|
15
15
|
def name; klass.name; end
|
16
16
|
|
17
|
+
def ==(middleware)
|
18
|
+
case middleware
|
19
|
+
when Middleware
|
20
|
+
klass == middleware.klass
|
21
|
+
when Class
|
22
|
+
klass == middleware
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
17
26
|
def inspect
|
18
27
|
if klass.is_a?(Class)
|
19
28
|
klass.to_s
|
@@ -27,7 +27,7 @@ module ActionDispatch
|
|
27
27
|
# in the server's `public/` directory (see Static#call).
|
28
28
|
def match?(path)
|
29
29
|
path = ::Rack::Utils.unescape_path path
|
30
|
-
return false unless path
|
30
|
+
return false unless valid_path?(path)
|
31
31
|
path = Rack::Utils.clean_path_info path
|
32
32
|
|
33
33
|
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
|
@@ -94,6 +94,10 @@ module ActionDispatch
|
|
94
94
|
false
|
95
95
|
end
|
96
96
|
end
|
97
|
+
|
98
|
+
def valid_path?(path)
|
99
|
+
path.valid_encoding? && !path.include?("\0")
|
100
|
+
end
|
97
101
|
end
|
98
102
|
|
99
103
|
# This middleware will attempt to return the contents of a file's body from
|
@@ -109,7 +109,7 @@ module ActionDispatch
|
|
109
109
|
@delegate.values
|
110
110
|
end
|
111
111
|
|
112
|
-
#
|
112
|
+
# Writes given value to given key of the session.
|
113
113
|
def []=(key, value)
|
114
114
|
load_for_write!
|
115
115
|
@delegate[key.to_s] = value
|
@@ -148,8 +148,8 @@ module ActionDispatch
|
|
148
148
|
@delegate.delete key.to_s
|
149
149
|
end
|
150
150
|
|
151
|
-
# Returns value of given key from the session, or raises +KeyError+
|
152
|
-
#
|
151
|
+
# Returns value of the given key from the session, or raises +KeyError+
|
152
|
+
# if can't find the given key and no default value is set.
|
153
153
|
# Returns default value if specified.
|
154
154
|
#
|
155
155
|
# session.fetch(:foo)
|
@@ -239,7 +239,8 @@ module ActionDispatch
|
|
239
239
|
#
|
240
240
|
# rails routes
|
241
241
|
#
|
242
|
-
# Target specific controllers by prefixing the command with <tt
|
242
|
+
# Target specific controllers by prefixing the command with <tt>--controller</tt> option
|
243
|
+
# - or its <tt>-c</tt> shorthand.
|
243
244
|
#
|
244
245
|
module Routing
|
245
246
|
extend ActiveSupport::Autoload
|
@@ -60,12 +60,10 @@ module ActionDispatch
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def format(formatter, filter = nil)
|
63
|
-
routes_to_display = filter_routes(filter)
|
64
|
-
|
63
|
+
routes_to_display = filter_routes(normalize_filter(filter))
|
65
64
|
routes = collect_routes(routes_to_display)
|
66
|
-
|
67
65
|
if routes.none?
|
68
|
-
formatter.no_routes
|
66
|
+
formatter.no_routes(collect_routes(@routes))
|
69
67
|
return formatter.result
|
70
68
|
end
|
71
69
|
|
@@ -82,9 +80,19 @@ module ActionDispatch
|
|
82
80
|
|
83
81
|
private
|
84
82
|
|
83
|
+
def normalize_filter(filter)
|
84
|
+
if filter.is_a?(Hash) && filter[:controller]
|
85
|
+
{ controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
|
86
|
+
elsif filter
|
87
|
+
{ controller: /#{filter}/, action: /#{filter}/ }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
85
91
|
def filter_routes(filter)
|
86
92
|
if filter
|
87
|
-
@routes.select
|
93
|
+
@routes.select do |route|
|
94
|
+
filter.any? { |default, value| route.defaults[default] =~ value }
|
95
|
+
end
|
88
96
|
else
|
89
97
|
@routes
|
90
98
|
end
|
@@ -136,14 +144,18 @@ module ActionDispatch
|
|
136
144
|
@buffer << draw_header(routes)
|
137
145
|
end
|
138
146
|
|
139
|
-
def no_routes
|
140
|
-
@buffer <<
|
147
|
+
def no_routes(routes)
|
148
|
+
@buffer <<
|
149
|
+
if routes.none?
|
150
|
+
<<-MESSAGE.strip_heredoc
|
141
151
|
You don't have any routes defined!
|
142
152
|
|
143
153
|
Please add some routes in config/routes.rb.
|
144
|
-
|
145
|
-
For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
|
146
154
|
MESSAGE
|
155
|
+
else
|
156
|
+
"No routes were found for this controller"
|
157
|
+
end
|
158
|
+
@buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
|
147
159
|
end
|
148
160
|
|
149
161
|
private
|
@@ -187,7 +199,7 @@ module ActionDispatch
|
|
187
199
|
def header(routes)
|
188
200
|
end
|
189
201
|
|
190
|
-
def no_routes
|
202
|
+
def no_routes(*)
|
191
203
|
@buffer << <<-MESSAGE.strip_heredoc
|
192
204
|
<p>You don't have any routes defined!</p>
|
193
205
|
<ul>
|
@@ -184,26 +184,32 @@ module ActionDispatch
|
|
184
184
|
def build_path(ast, requirements, anchor)
|
185
185
|
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
186
186
|
|
187
|
-
#
|
188
|
-
#
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
187
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
188
|
+
# the regexp so that Journey will partition them into custom routes.
|
189
|
+
ast.find_all { |node|
|
190
|
+
next unless node.cat?
|
191
|
+
|
192
|
+
if node.left.literal? && node.right.symbol?
|
193
|
+
symbol = node.right
|
194
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
195
|
+
symbol = node.right.left
|
196
|
+
elsif node.left.symbol? && node.right.literal?
|
197
|
+
symbol = node.left
|
198
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
199
|
+
symbol = node.left
|
200
|
+
else
|
201
|
+
next
|
202
|
+
end
|
203
|
+
|
204
|
+
if symbol
|
205
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
206
|
+
end
|
200
207
|
}
|
201
208
|
|
202
209
|
pattern
|
203
210
|
end
|
204
211
|
private :build_path
|
205
212
|
|
206
|
-
|
207
213
|
private
|
208
214
|
def add_wildcard_options(options, formatted, path_ast)
|
209
215
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
@@ -387,24 +393,6 @@ module ActionDispatch
|
|
387
393
|
end
|
388
394
|
|
389
395
|
module Base
|
390
|
-
# You can specify what Rails should route "/" to with the root method:
|
391
|
-
#
|
392
|
-
# root to: 'pages#main'
|
393
|
-
#
|
394
|
-
# For options, see +match+, as +root+ uses it internally.
|
395
|
-
#
|
396
|
-
# You can also pass a string which will expand
|
397
|
-
#
|
398
|
-
# root 'pages#main'
|
399
|
-
#
|
400
|
-
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
401
|
-
# because this means it will be matched first. As this is the most popular route
|
402
|
-
# of most Rails applications, this is beneficial.
|
403
|
-
def root(options = {})
|
404
|
-
name = has_named_route?(:root) ? nil : :root
|
405
|
-
match '/', { as: name, via: :get }.merge!(options)
|
406
|
-
end
|
407
|
-
|
408
396
|
# Matches a url pattern to one or more routes.
|
409
397
|
#
|
410
398
|
# You should not use the +match+ method in your router
|
@@ -1689,7 +1677,20 @@ to this:
|
|
1689
1677
|
@set.add_route(mapping, ast, as, anchor)
|
1690
1678
|
end
|
1691
1679
|
|
1692
|
-
|
1680
|
+
# You can specify what Rails should route "/" to with the root method:
|
1681
|
+
#
|
1682
|
+
# root to: 'pages#main'
|
1683
|
+
#
|
1684
|
+
# For options, see +match+, as +root+ uses it internally.
|
1685
|
+
#
|
1686
|
+
# You can also pass a string which will expand
|
1687
|
+
#
|
1688
|
+
# root 'pages#main'
|
1689
|
+
#
|
1690
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1691
|
+
# because this means it will be matched first. As this is the most popular route
|
1692
|
+
# of most Rails applications, this is beneficial.
|
1693
|
+
def root(path, options = {})
|
1693
1694
|
if path.is_a?(String)
|
1694
1695
|
options[:to] = path
|
1695
1696
|
elsif path.is_a?(Hash) and options.empty?
|
@@ -1701,11 +1702,11 @@ to this:
|
|
1701
1702
|
if @scope.resources?
|
1702
1703
|
with_scope_level(:root) do
|
1703
1704
|
path_scope(parent_resource.path) do
|
1704
|
-
|
1705
|
+
match_root_route(options)
|
1705
1706
|
end
|
1706
1707
|
end
|
1707
1708
|
else
|
1708
|
-
|
1709
|
+
match_root_route(options)
|
1709
1710
|
end
|
1710
1711
|
end
|
1711
1712
|
|
@@ -1900,6 +1901,11 @@ to this:
|
|
1900
1901
|
ensure
|
1901
1902
|
@scope = @scope.parent
|
1902
1903
|
end
|
1904
|
+
|
1905
|
+
def match_root_route(options)
|
1906
|
+
name = has_named_route?(:root) ? nil : :root
|
1907
|
+
match '/', { :as => name, :via => :get }.merge!(options)
|
1908
|
+
end
|
1903
1909
|
end
|
1904
1910
|
|
1905
1911
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -281,8 +281,17 @@ module ActionDispatch
|
|
281
281
|
helper = UrlHelper.create(route, opts, route_key, url_strategy)
|
282
282
|
mod.module_eval do
|
283
283
|
define_method(name) do |*args|
|
284
|
-
|
285
|
-
options =
|
284
|
+
last = args.last
|
285
|
+
options = case last
|
286
|
+
when Hash
|
287
|
+
args.pop
|
288
|
+
when ActionController::Parameters
|
289
|
+
if last.permitted?
|
290
|
+
args.pop.to_h
|
291
|
+
else
|
292
|
+
raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!"
|
293
|
+
end
|
294
|
+
end
|
286
295
|
helper.call self, args, options
|
287
296
|
end
|
288
297
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
# This is a class that abstracts away an asserted response.
|
3
|
+
# It purposely does not inherit from Response, because it doesn't need it.
|
4
|
+
# That means it does not have headers or a body.
|
5
|
+
#
|
6
|
+
# As an input to the initializer, we take a Fixnum, a String, or a Symbol.
|
7
|
+
# If it's a Fixnum or String, we figure out what its symbolized name.
|
8
|
+
# If it's a Symbol, we figure out what its corresponding code is.
|
9
|
+
# The resulting code will be a Fixnum, for real HTTP codes, and it will
|
10
|
+
# be a String for the pseudo-HTTP codes, such as:
|
11
|
+
# :success, :missing, :redirect and :error
|
12
|
+
class AssertionResponse
|
13
|
+
attr_reader :code, :name
|
14
|
+
|
15
|
+
GENERIC_RESPONSE_CODES = { # :nodoc:
|
16
|
+
success: "2XX",
|
17
|
+
missing: "404",
|
18
|
+
redirect: "3XX",
|
19
|
+
error: "5XX"
|
20
|
+
}
|
21
|
+
|
22
|
+
def initialize(code_or_name)
|
23
|
+
if code_or_name.is_a?(Symbol)
|
24
|
+
@name = code_or_name
|
25
|
+
@code = code_from_name(code_or_name)
|
26
|
+
else
|
27
|
+
@name = name_from_code(code_or_name)
|
28
|
+
@code = code_or_name
|
29
|
+
end
|
30
|
+
|
31
|
+
raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
|
32
|
+
raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def code_and_name
|
36
|
+
"#{code}: #{name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def code_from_name(name)
|
42
|
+
GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
|
43
|
+
end
|
44
|
+
|
45
|
+
def name_from_code(code)
|
46
|
+
GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|