aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb76
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb49
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb28
15 files changed, 189 insertions, 88 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 30ade14c26..0f7898a3f8 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -82,7 +82,7 @@ module ActionDispatch
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- super %("#{Digest::MD5.hexdigest(key)}")
+ super %(W/"#{Digest::MD5.hexdigest(key)}")
end
def etag?; etag; end
@@ -91,7 +91,7 @@ module ActionDispatch
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
- SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
def cache_control_segments
if cache_control = _cache_control
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 0152c17ed4..e9b25339dc 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -67,10 +67,10 @@ module ActionDispatch
v = if params_readable
Array(Mime[parameters[:format]])
- elsif format = format_from_path_extension
- Array(Mime[format])
elsif use_accept_header && valid_accept_header
accepts
+ elsif extension_format = format_from_path_extension
+ [extension_format]
elsif xhr?
[Mime[:js]]
else
@@ -166,7 +166,7 @@ module ActionDispatch
def format_from_path_extension
path = @env['action_dispatch.original_path'] || @env['PATH_INFO']
if match = path && path.match(/\.(\w+)\z/)
- match.captures.first
+ Mime[match.captures.first]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index c9df787351..cca7376ffa 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -43,7 +43,7 @@ module ActionDispatch
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters
- get_header(PARAMETERS_KEY) || {}
+ get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
end
private
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 29cf821090..5427425ef7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -259,7 +259,7 @@ module ActionDispatch
end
# Returns the IP address of client as a +String+,
- # usually set by the RemoteIp middleware.
+ # usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 9b11111a67..14f86c7c07 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -232,7 +232,7 @@ module ActionDispatch # :nodoc:
end
# Sets the HTTP character set. In case of nil parameter
- # it sets the charset to utf-8.
+ # it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
@@ -412,6 +412,13 @@ module ActionDispatch # :nodoc:
end
def before_sending
+ # Normally we've already committed by now, but it's possible
+ # (e.g., if the controller action tries to read back its own
+ # response) to get here before that. In that case, we must force
+ # an "early" commit: we're about to freeze the headers, so this is
+ # our last chance.
+ commit! unless committed?
+
headers.freeze
request.commit_cookie_jar! unless committed?
end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 5ee8810066..018b89a2b7 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -124,7 +124,7 @@ module ActionDispatch
end
def captures
- (length - 1).times.map { |i| self[i + 1] }
+ Array.new(length - 1) { |i| self[i + 1] }
end
def [](x)
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 47f475559a..735b5939dd 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,19 +1,23 @@
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`.
- # It does three jobs to enforce secure HTTP requests:
+ # This middleware is added to the stack when `config.force_ssl = true`, and is passed
+ # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # requests:
#
- # 1. TLS redirect. http:// requests are permanently redirected to https://
- # with the same URL host, path, etc. Pass `:host` and/or `:port` to
- # modify the destination URL. This is always enabled.
+ # 1. TLS redirect: Permanently redirects http:// requests to https://
+ # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
+ # to modify the destination URL
+ # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
+ # `redirect: false` to disable this feature.
#
- # 2. Secure cookies. Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. This is always enabled.
+ # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
+ # mustn't be sent along with http:// requests. Enabled by default. Set
+ # `config.ssl_options` with `secure_cookies: false` to disable this feature.
#
- # 3. HTTP Strict Transport Security (HSTS). Tells the browser to remember
+ # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
# this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Pass `hsts: false` to disable.
+ # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
#
- # Configure HSTS with `hsts: { … }`:
+ # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
# * `expires`: How long, in seconds, these settings will stick. Defaults to
# `180.days` (recommended). The minimum required to qualify for browser
# preload lists is `18.weeks`.
@@ -26,10 +30,10 @@ module ActionDispatch
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
#
- # Disabling HSTS: To turn off HSTS, omitting the header is not enough.
- # Browsers will remember the original HSTS directive until it expires.
- # Instead, use the header to tell browsers to expire HSTS immediately.
- # Setting `hsts: false` is a shortcut for `hsts: { expires: 0 }`.
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting `hsts: false` is a shortcut for
+ # `hsts: { expires: 0 }`.
class SSL
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
@@ -39,7 +43,7 @@ module ActionDispatch
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
@app = app
if options[:host] || options[:port]
@@ -52,6 +56,7 @@ module ActionDispatch
@redirect = redirect
end
+ @secure_cookies = secure_cookies
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -61,10 +66,11 @@ module ActionDispatch
if request.ssl?
@app.call(env).tap do |status, headers, body|
set_hsts_header! headers
- flag_cookies_as_secure! headers
+ flag_cookies_as_secure! headers if @secure_cookies
end
else
- redirect_to_https request
+ return redirect_to_https request if @redirect
+ @app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 44fc1ee736..0b4bee5462 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -14,6 +14,15 @@ module ActionDispatch
def name; klass.name; end
+ def ==(middleware)
+ case middleware
+ when Middleware
+ klass == middleware.klass
+ when Class
+ klass == middleware
+ end
+ end
+
def inspect
if klass.is_a?(Class)
klass.to_s
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index ea9ab3821d..41c220236a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# in the server's `public/` directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless path.valid_encoding?
+ return false unless valid_path?(path)
path = Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
@@ -94,6 +94,10 @@ module ActionDispatch
false
end
end
+
+ def valid_path?(path)
+ path.valid_encoding? && !path.include?("\0")
+ end
end
# This middleware will attempt to return the contents of a file's body from
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 9e7fcbd849..42890225fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -109,7 +109,7 @@ module ActionDispatch
@delegate.values
end
- # Writes given value to given key of the session.
+ # Writes given value to given key of the session.
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
@@ -148,8 +148,8 @@ module ActionDispatch
@delegate.delete key.to_s
end
- # Returns value of given key from the session, or raises +KeyError+
- # if can't find given key in case of not setted dafault value.
+ # Returns value of the given key from the session, or raises +KeyError+
+ # if can't find the given key and no default value is set.
# Returns default value if specified.
#
# session.fetch(:foo)
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index f3a5268d2e..69e6dd5215 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -65,7 +65,7 @@ module ActionDispatch
routes = collect_routes(routes_to_display)
if routes.none?
- formatter.no_routes
+ formatter.no_routes(collect_routes(@routes), filter)
return formatter.result
end
@@ -84,7 +84,8 @@ module ActionDispatch
def filter_routes(filter)
if filter
- @routes.select { |route| route.defaults[:controller] == filter }
+ filter_name = filter.underscore.sub(/_controller$/, '')
+ @routes.select { |route| route.defaults[:controller] == filter_name }
else
@routes
end
@@ -136,17 +137,27 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes
- @buffer << <<-MESSAGE.strip_heredoc
+ def no_routes(routes, filter)
+ @buffer <<
+ if routes.none?
+ <<-MESSAGE.strip_heredoc
You don't have any routes defined!
Please add some routes in config/routes.rb.
-
- For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
+ elsif missing_controller?(filter)
+ "The controller #{filter} does not exist!"
+ else
+ "No routes were found for this controller"
+ end
+ @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
private
+ def missing_controller?(controller_name)
+ [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
+ end
+
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
@@ -187,7 +198,7 @@ module ActionDispatch
def header(routes)
end
- def no_routes
+ def no_routes(*)
@buffer << <<-MESSAGE.strip_heredoc
<p>You don't have any routes defined!</p>
<ul>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 18cd205bad..afbaa45d20 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -184,26 +184,32 @@ module ActionDispatch
def build_path(ast, requirements, anchor)
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
- # Get all the symbol nodes followed by literals that are not the
- # dummy node.
- symbols = ast.find_all { |n|
- n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
- }.map(&:left)
-
- # Get all the symbol nodes preceded by literals.
- symbols.concat ast.find_all { |n|
- n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
- }.map { |n| n.right.left }
-
- symbols.each { |x|
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ # Find all the symbol nodes that are adjacent to literal nodes and alter
+ # the regexp so that Journey will partition them into custom routes.
+ ast.find_all { |node|
+ next unless node.cat?
+
+ if node.left.literal? && node.right.symbol?
+ symbol = node.right
+ elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
+ symbol = node.right.left
+ elsif node.left.symbol? && node.right.literal?
+ symbol = node.left
+ elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
+ symbol = node.left
+ else
+ next
+ end
+
+ if symbol
+ symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
+ end
}
pattern
end
private :build_path
-
private
def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
@@ -387,24 +393,6 @@ module ActionDispatch
end
module Base
- # You can specify what Rails should route "/" to with the root method:
- #
- # root to: 'pages#main'
- #
- # For options, see +match+, as +root+ uses it internally.
- #
- # You can also pass a string which will expand
- #
- # root 'pages#main'
- #
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
- # because this means it will be matched first. As this is the most popular route
- # of most Rails applications, this is beneficial.
- def root(options = {})
- name = has_named_route?(:root) ? nil : :root
- match '/', { as: name, via: :get }.merge!(options)
- end
-
# Matches a url pattern to one or more routes.
#
# You should not use the +match+ method in your router
@@ -1689,7 +1677,20 @@ to this:
@set.add_route(mapping, ast, as, anchor)
end
- def root(path, options={})
+ # You can specify what Rails should route "/" to with the root method:
+ #
+ # root to: 'pages#main'
+ #
+ # For options, see +match+, as +root+ uses it internally.
+ #
+ # You can also pass a string which will expand
+ #
+ # root 'pages#main'
+ #
+ # You should put the root route at the top of <tt>config/routes.rb</tt>,
+ # because this means it will be matched first. As this is the most popular route
+ # of most Rails applications, this is beneficial.
+ def root(path, options = {})
if path.is_a?(String)
options[:to] = path
elsif path.is_a?(Hash) and options.empty?
@@ -1701,11 +1702,11 @@ to this:
if @scope.resources?
with_scope_level(:root) do
path_scope(parent_resource.path) do
- super(options)
+ match_root_route(options)
end
end
else
- super(options)
+ match_root_route(options)
end
end
@@ -1900,6 +1901,11 @@ to this:
ensure
@scope = @scope.parent
end
+
+ def match_root_route(options)
+ name = has_named_route?(:root) ? nil : :root
+ match '/', { :as => name, :via => :get }.merge!(options)
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2bd2e53252..846b5fa1fc 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -281,8 +281,17 @@ module ActionDispatch
helper = UrlHelper.create(route, opts, route_key, url_strategy)
mod.module_eval do
define_method(name) do |*args|
- options = nil
- options = args.pop if args.last.is_a? Hash
+ last = args.last
+ options = case last
+ when Hash
+ args.pop
+ when ActionController::Parameters
+ if last.permitted?
+ args.pop.to_h
+ else
+ raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!"
+ end
+ end
helper.call self, args, options
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
new file mode 100644
index 0000000000..3fb81ff083
--- /dev/null
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ # This is a class that abstracts away an asserted response.
+ # It purposely does not inherit from Response, because it doesn't need it.
+ # That means it does not have headers or a body.
+ #
+ # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
+ # If it's a Fixnum or String, we figure out what its symbolized name.
+ # If it's a Symbol, we figure out what its corresponding code is.
+ # The resulting code will be a Fixnum, for real HTTP codes, and it will
+ # be a String for the pseudo-HTTP codes, such as:
+ # :success, :missing, :redirect and :error
+ class AssertionResponse
+ attr_reader :code, :name
+
+ GENERIC_RESPONSE_CODES = { # :nodoc:
+ success: "2XX",
+ missing: "404",
+ redirect: "3XX",
+ error: "5XX"
+ }
+
+ def initialize(code_or_name)
+ if code_or_name.is_a?(Symbol)
+ @name = code_or_name
+ @code = code_from_name(code_or_name)
+ else
+ @name = name_from_code(code_or_name)
+ @code = code_or_name
+ end
+
+ raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
+ raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
+ end
+
+ def code_and_name
+ "#{code}: #{name}"
+ end
+
+ private
+
+ def code_from_name(name)
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
+ end
+
+ def name_from_code(code)
+ GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index c138660a21..cd55b7d975 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -29,18 +28,10 @@ module ActionDispatch
def assert_response(type, message = nil)
message ||= generate_response_message(type)
- if Symbol === type
- if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send(RESPONSE_PREDICATES[type]), message
- else
- code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- if code.nil?
- raise ArgumentError, "Invalid response type :#{type}"
- end
- assert_equal code, @response.response_code, message
- end
+ if RESPONSE_PREDICATES.keys.include?(type)
+ assert @response.send(RESPONSE_PREDICATES[type]), message
else
- assert_equal type, @response.response_code, message
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
end
end
@@ -85,8 +76,9 @@ module ActionDispatch
end
end
- def generate_response_message(type, code = @response.response_code)
- "Expected response to be a <#{type}>, but was a <#{code}>"
+ def generate_response_message(expected, actual = @response.response_code)
+ "Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>"
.concat location_if_redirected
end
@@ -95,6 +87,14 @@ module ActionDispatch
location = normalize_argument_to_redirection(@response.location)
" redirect to <#{location}>"
end
+
+ def code_with_name(code_or_name)
+ if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
+ end
+
+ AssertionResponse.new(code_or_name).code_and_name
+ end
end
end
end