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.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -28
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller/base.rb +2 -2
  5. data/lib/abstract_controller/rendering.rb +5 -5
  6. data/lib/action_controller.rb +4 -0
  7. data/lib/action_controller/api.rb +1 -1
  8. data/lib/action_controller/api/api_rendering.rb +14 -0
  9. data/lib/action_controller/metal.rb +2 -1
  10. data/lib/action_controller/metal/conditional_get.rb +1 -1
  11. data/lib/action_controller/metal/head.rb +0 -1
  12. data/lib/action_controller/metal/mime_responds.rb +9 -4
  13. data/lib/action_controller/metal/renderers.rb +75 -32
  14. data/lib/action_controller/metal/request_forgery_protection.rb +54 -11
  15. data/lib/action_controller/metal/strong_parameters.rb +33 -10
  16. data/lib/action_controller/test_case.rb +8 -8
  17. data/lib/action_dispatch.rb +2 -1
  18. data/lib/action_dispatch/http/cache.rb +10 -2
  19. data/lib/action_dispatch/http/headers.rb +15 -1
  20. data/lib/action_dispatch/http/mime_negotiation.rb +3 -3
  21. data/lib/action_dispatch/http/mime_type.rb +38 -47
  22. data/lib/action_dispatch/http/parameters.rb +1 -1
  23. data/lib/action_dispatch/http/request.rb +1 -1
  24. data/lib/action_dispatch/http/response.rb +8 -1
  25. data/lib/action_dispatch/journey/path/pattern.rb +1 -1
  26. data/lib/action_dispatch/middleware/ssl.rb +23 -17
  27. data/lib/action_dispatch/middleware/stack.rb +9 -0
  28. data/lib/action_dispatch/middleware/static.rb +5 -1
  29. data/lib/action_dispatch/request/session.rb +3 -3
  30. data/lib/action_dispatch/routing.rb +2 -1
  31. data/lib/action_dispatch/routing/inspector.rb +22 -10
  32. data/lib/action_dispatch/routing/mapper.rb +41 -35
  33. data/lib/action_dispatch/routing/route_set.rb +11 -2
  34. data/lib/action_dispatch/testing/assertion_response.rb +49 -0
  35. data/lib/action_dispatch/testing/assertions/response.rb +14 -14
  36. data/lib/action_dispatch/testing/test_process.rb +0 -1
  37. data/lib/action_pack.rb +1 -1
  38. data/lib/action_pack/gem_version.rb +1 -1
  39. metadata +12 -9
@@ -43,7 +43,7 @@ module ActionDispatch
43
43
  #
44
44
  # {'action' => 'my_action', 'controller' => 'my_controller'}
45
45
  def path_parameters
46
- get_header(PARAMETERS_KEY) || {}
46
+ get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
47
47
  end
48
48
 
49
49
  private
@@ -259,7 +259,7 @@ module ActionDispatch
259
259
  end
260
260
 
261
261
  # Returns the IP address of client as a +String+,
262
- # usually set by the RemoteIp middleware.
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
- # it sets the charset to utf-8.
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
@@ -124,7 +124,7 @@ module ActionDispatch
124
124
  end
125
125
 
126
126
  def captures
127
- (length - 1).times.map { |i| self[i + 1] }
127
+ Array.new(length - 1) { |i| self[i + 1] }
128
128
  end
129
129
 
130
130
  def [](x)
@@ -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 requests:
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. http:// requests are permanently redirected to https://
6
- # with the same URL host, path, etc. Pass `:host` and/or `:port` to
7
- # modify the destination URL. This is always enabled.
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. Sets the `secure` flag on cookies to tell browsers they
10
- # mustn't be sent along with http:// requests. This is always enabled.
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). Tells the browser to remember
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. Pass `hsts: false` to disable.
18
+ # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
15
19
  #
16
- # Configure HSTS with `hsts: { … }`:
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
- # Disabling HSTS: To turn off HSTS, omitting the header is not enough.
30
- # Browsers will remember the original HSTS directive until it expires.
31
- # Instead, use the header to tell browsers to expire HSTS immediately.
32
- # Setting `hsts: false` is a shortcut for `hsts: { expires: 0 }`.
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.valid_encoding?
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
- # Writes given value to given key of the session.
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
- # if can't find given key in case of not setted dafault value.
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>CONTROLLER=x</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 { |route| route.defaults[:controller] == filter }
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 << <<-MESSAGE.strip_heredoc
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
- # Get all the symbol nodes followed by literals that are not the
188
- # dummy node.
189
- symbols = ast.find_all { |n|
190
- n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
191
- }.map(&:left)
192
-
193
- # Get all the symbol nodes preceded by literals.
194
- symbols.concat ast.find_all { |n|
195
- n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
196
- }.map { |n| n.right.left }
197
-
198
- symbols.each { |x|
199
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
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
- def root(path, options={})
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
- super(options)
1705
+ match_root_route(options)
1705
1706
  end
1706
1707
  end
1707
1708
  else
1708
- super(options)
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
- options = nil
285
- options = args.pop if args.last.is_a? Hash
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