aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb15
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb6
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb160
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb8
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb23
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb110
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb48
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb13
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/path_set.rb22
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb4
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb5
23 files changed, 333 insertions, 192 deletions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 5705ab590c..02ac111392 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -29,13 +29,14 @@ module AbstractController
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
def _normalize_callback_options(options)
- if only = options[:only]
- only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:if] = Array(options[:if]) << only
- end
- if except = options[:except]
- except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:unless] = Array(options[:unless]) << except
+ _normalize_callback_option(options, :only, :if)
+ _normalize_callback_option(options, :except, :unless)
+ end
+
+ def _normalize_callback_option(options, from, to) # :nodoc:
+ if from = options[from]
+ from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
+ options[to] = Array(options[to]) << from
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index c1b3994035..12da273af9 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -170,7 +170,7 @@ module AbstractController
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
#
# class WeblogController < ActionController::Base
- # layout "weblog_standard", :except => :rss
+ # layout "weblog_standard", except: :rss
#
# # ...
#
@@ -180,7 +180,7 @@ module AbstractController
# be rendered directly, without wrapping a layout around the rendered view.
#
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
- # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ # #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>.
#
# == Using a different layout in the action render call
#
@@ -192,7 +192,7 @@ module AbstractController
# layout "weblog_standard"
#
# def help
- # render :action => "help", :layout => "help"
+ # render action: "help", layout: "help"
# end
# end
#
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 9b3bf99fc3..971c4189c8 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,4 +1,5 @@
require "action_controller/log_subscriber"
+require "action_controller/metal/params_wrapper"
module ActionController
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 6d46586367..d3b5bafee1 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -249,9 +249,9 @@ module ActionController
end
def secret_token(request)
- secret = request.env["action_dispatch.secret_token"]
- raise "You must set config.secret_token in your app's config" if secret.blank?
- secret
+ key_generator = request.env["action_dispatch.key_generator"]
+ http_auth_salt = request.env["action_dispatch.http_auth_salt"]
+ key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 09abc999c1..a475d4bdff 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
+require 'active_support/core_ext/struct'
require 'action_dispatch/http/mime_types'
module ActionController
@@ -72,12 +73,99 @@ module ActionController
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+ require 'mutex_m'
+
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
+ include Mutex_m
+
+ def self.from_hash(hash)
+ name = hash[:name]
+ format = Array(hash[:format])
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
+ new name, format, include, exclude, nil, nil
+ end
+
+ def initialize(name, format, include, exclude, klass, model) # nodoc
+ super
+ @include_set = include
+ @name_set = name
+ end
+
+ def model
+ super || synchronize { super || self.model = _default_wrap_model }
+ end
+
+ def include
+ return super if @include_set
+
+ m = model
+ synchronize do
+ return super if @include_set
+
+ @include_set = true
+
+ unless super || exclude
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
+ self.include = m.attribute_names
+ end
+ end
+ end
+ end
+
+ def name
+ return super if @name_set
+
+ m = model
+ synchronize do
+ return super if @name_set
+
+ @name_set = true
+
+ unless super || klass.anonymous?
+ self.name = m ? m.to_s.demodulize.underscore :
+ klass.controller_name.singularize
+ end
+ end
+ end
+
+ private
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
+ return nil if klass.anonymous?
+ model_name = klass.name.sub(/Controller$/, '').classify
+
+ begin
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ end
+ end until model_klass
+
+ model_klass
+ end
+ end
+
included do
class_attribute :_wrapper_options
- self._wrapper_options = { :format => [] }
+ self._wrapper_options = Options.from_hash(format: [])
end
module ClassMethods
+ def _set_wrapper_options(options)
+ self._wrapper_options = Options.from_hash(options)
+ end
+
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
# would use to determine the attribute names from.
#
@@ -119,68 +207,24 @@ module ActionController
model = name_or_model_or_options
end
- _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
+ opts.model = model
+ opts.klass = self
+
+ self._wrapper_options = opts
end
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
def inherited(klass)
- if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
+ if klass._wrapper_options.format.any?
+ params = klass._wrapper_options.dup
+ params.klass = klass
+ klass._wrapper_options = params
end
super
end
-
- protected
-
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singularize name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
- return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').classify
-
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
-
- model_klass
- end
-
- def _set_wrapper_defaults(options, model=nil)
- options = options.dup
-
- unless options[:include] || options[:exclude]
- model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
- options[:include] = model.attribute_names
- end
- end
-
- unless options[:name] || self.anonymous?
- model ||= _default_wrap_model
- options[:name] = model ? model.to_s.demodulize.underscore :
- controller_name.singularize
- end
-
- options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array(options[:format])
-
- self._wrapper_options = options
- end
end
# Performs parameters wrapping upon the request. Will be called automatically
@@ -205,20 +249,20 @@ module ActionController
# Returns the wrapper key which will use to stored wrapped parameters.
def _wrapper_key
- _wrapper_options[:name]
+ _wrapper_options.name
end
# Returns the list of enabled formats.
def _wrapper_formats
- _wrapper_options[:format]
+ _wrapper_options.format
end
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options[:include]
+ value = if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
- exclude = _wrapper_options[:exclude] || []
+ exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index a50f0ca8c1..265ce5d6f3 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -121,11 +121,11 @@ module ActionController #:nodoc:
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
- secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
- host = request.host
- secure = request.ssl?
+ key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
+ host = request.host
+ secure = request.ssl?
- new(secret, host, secure)
+ new(key_generator, host, secure)
end
def write(*)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 04dc1d37f7..da640502a2 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -65,7 +65,6 @@ module ActionController
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
cattr_accessor :permit_all_parameters, instance_accessor: false
- attr_accessor :permitted # :nodoc:
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
@@ -260,7 +259,9 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- self.class.new(super)
+ self.class.new(super).tap do |new_instance|
+ new_instance.instance_variable_set :@permitted, @permitted
+ end
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 0ec355246e..1d716a3248 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -81,10 +81,12 @@ module ActionDispatch
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 2b5d3d85bf..f56f09c5b3 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -288,18 +288,23 @@ module Mime
@@html_types.include?(to_sym) || @string =~ /html/
end
+
private
- def method_missing(method, *args)
- if method.to_s.ends_with? '?'
- method[0..-2].downcase.to_sym == to_sym
- else
- super
- end
- end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- method.to_s.ends_with? '?'
+ def to_ary; end
+ def to_a; end
+
+ def method_missing(method, *args)
+ if method.to_s.ends_with? '?'
+ method[0..-2].downcase.to_sym == to_sym
+ else
+ super
end
+ end
+
+ def respond_to_missing?(method, include_private = false) #:nodoc:
+ method.to_s.ends_with? '?'
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 8aa02ec482..bced7d84c0 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -8,14 +8,16 @@ module ActionDispatch
class << self
def extract_domain(host, tld_length = @@tld_length)
- return nil unless named_host?(host)
- host.split('.').last(1 + tld_length).join('.')
+ host.split('.').last(1 + tld_length).join('.') if named_host?(host)
end
def extract_subdomains(host, tld_length = @@tld_length)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
+ if named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length + 2)]
+ else
+ []
+ end
end
def extract_subdomain(host, tld_length = @@tld_length)
@@ -23,15 +25,13 @@ module ActionDispatch
end
def url_for(options = {})
- path = ""
- path << options.delete(:script_name).to_s.chomp("/")
+ path = options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
- params = options[:params] || {}
- params.reject! {|k,v| v.to_param.nil? }
+ params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
+ params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
-
result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
result << "?#{params.to_query}" unless params.empty?
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index eaf922595a..2f148752cb 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/message_verifier'
module ActionDispatch
class Request < Rack::Request
@@ -27,7 +28,7 @@ module ActionDispatch
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
+ # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
# # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
@@ -79,7 +80,11 @@ module ActionDispatch
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
class Cookies
- HTTP_HEADER = "Set-Cookie".freeze
+ HTTP_HEADER = "Set-Cookie".freeze
+ GENERATOR_KEY = "action_dispatch.key_generator".freeze
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
# Raised when storing more than 4K of session data.
@@ -103,21 +108,28 @@ module ActionDispatch
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
- secret = request.env[TOKEN_KEY]
+ env = request.env
+ key_generator = env[GENERATOR_KEY]
+ options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT],
+ encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT],
+ encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT],
+ token_key: env[TOKEN_KEY] }
+
host = request.host
secure = request.ssl?
- new(secret, host, secure).tap do |hash|
+ new(key_generator, host, secure, options).tap do |hash|
hash.update(request.cookies)
end
end
- def initialize(secret = nil, host = nil, secure = false)
- @secret = secret
+ def initialize(key_generator, host = nil, secure = false, options = {})
+ @key_generator = key_generator
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
+ @options = options
@cookies = {}
end
@@ -220,7 +232,7 @@ module ActionDispatch
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
- @permanent ||= PermanentCookieJar.new(self, @secret)
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -228,7 +240,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
#
@@ -237,7 +249,28 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||= SignedCookieJar.new(self, @secret)
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
+ def signed_using_old_secret #:nodoc:
+ @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options)
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
+ # will be raised.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
end
def write(headers)
@@ -261,8 +294,10 @@ module ActionDispatch
end
class PermanentCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar, secret)
- @parent_jar, @secret = parent_jar, secret
+ def initialize(parent_jar, key_generator, options = {})
+ @parent_jar = parent_jar
+ @key_generator = key_generator
+ @options = options
end
def []=(key, options)
@@ -283,11 +318,11 @@ module ActionDispatch
class SignedCookieJar < CookieJar #:nodoc:
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
- SECRET_MIN_LENGTH = 30 # Characters
- def initialize(parent_jar, secret)
- ensure_secret_secure(secret)
+ def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:signed_cookie_salt])
@verifier = ActiveSupport::MessageVerifier.new(secret)
end
@@ -314,26 +349,41 @@ module ActionDispatch
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
+ end
+
+ class EncryptedCookieJar < SignedCookieJar #:nodoc:
+ def initialize(parent_jar, key_generator, options = {})
+ if ActiveSupport::DummyKeyGenerator === key_generator
+ raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
+ "Set config.secret_key_base in config/initializers/secret_token.rb"
+ end
+
+ @parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
+ sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ end
- protected
-
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_token = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ @encryptor.decrypt_and_verify(encrypted_message)
end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature,
+ ActiveSupport::MessageVerifier::InvalidMessage
+ nil
+ end
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
end
+ options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ @parent_jar[key] = options
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 3f28ea75ef..ce5f89ee5b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -57,8 +57,7 @@ module ActionDispatch
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
- request = ActionDispatch::Request.new(env)
- if data = request.cookie_jar.signed[@key]
+ if data = get_cookie(env)
data.stringify_keys!
end
data || {}
@@ -72,8 +71,51 @@ module ActionDispatch
end
def set_cookie(env, session_id, cookie)
+ cookie_jar(env)[@key] = cookie
+ end
+
+ def get_cookie(env)
+ cookie_jar(env)[@key]
+ end
+
+ def cookie_jar(env)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.signed
+ end
+ end
+
+ class EncryptedCookieStore < CookieStore
+
+ private
+
+ def cookie_jar(env)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.encrypted
+ end
+ end
+
+ # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
+ # To use this CookieStore set
+ #
+ # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
+ #
+ # in your config/initializers/session_store.rb
+ #
+ # You will also need to add
+ #
+ # Myapp::Application.config.secret_key_base = 'some secret'
+ #
+ # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
+ class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
+ private
+
+ def get_cookie(env)
+ signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
+ end
+
+ def signed_using_old_secret_cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed[@key] = cookie
+ request.cookie_jar.signed_using_old_secret
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0de10695e0..2b37a8d026 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -28,7 +28,7 @@ module ActionDispatch
def call(env)
begin
- response = @app.call(env)
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 284dd180db..98c87d9b2d 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -13,6 +13,10 @@ module ActionDispatch
config.action_dispatch.rescue_responses = { }
config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = false
+ config.action_dispatch.http_auth_salt = 'http authentication'
+ config.action_dispatch.signed_cookie_salt = 'signed cookie'
+ config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
+ config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 045299281c..d6fe436b68 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -299,7 +299,7 @@ module ActionDispatch
# and +:action+ to the controller's action. A pattern can also map
# wildcard segments (globs) to params:
#
- # match 'songs/*category/:title' => 'songs#show'
+ # match 'songs/*category/:title', to: 'songs#show'
#
# # 'songs/rock/classic/stairway-to-heaven' sets
# # params[:category] = 'rock/classic'
@@ -315,10 +315,14 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
- # match 'photos/:id' => PhotoRackApp
+ # match 'photos/:id', to: lambda {|hash| [200, {}, "Coming soon"] }
+ # match 'photos/:id', to: PhotoRackApp
# # Yes, controller actions are just rack endpoints
- # match 'photos/:id' => PhotosController.action(:show)
+ # match 'photos/:id', to: PhotosController.action(:show)
+ #
+ # Because request various HTTP verbs with a single action has security
+ # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
+ # instead +match+
#
# === Options
#
@@ -336,7 +340,7 @@ module ActionDispatch
# [:module]
# The namespace for :controller.
#
- # match 'path' => 'c#a', module: 'sekret', controller: 'posts'
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
# #=> Sekret::PostsController
#
# See <tt>Scoping#namespace</tt> for its scope equivalent.
@@ -347,8 +351,9 @@ module ActionDispatch
# [:via]
# Allowed HTTP verb(s) for route.
#
- # match 'path' => 'c#a', via: :get
- # match 'path' => 'c#a', via: [:get, :post]
+ # match 'path', to: 'c#a', via: :get
+ # match 'path', to: 'c#a', via: [:get, :post]
+ # match 'path', to: 'c#a', via: :all
#
# [:to]
# Points to a +Rack+ endpoint. Can be an object that responds to
@@ -364,14 +369,14 @@ module ActionDispatch
# <tt>resource(s)</tt> block. For example:
#
# resource :bar do
- # match 'foo' => 'c#a', on: :member, via: [:get, :post]
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
# end
#
# Is equivalent to:
#
# resource :bar do
# member do
- # match 'foo' => 'c#a', via: [:get, :post]
+ # match 'foo', to: 'c#a', via: [:get, :post]
# end
# end
#
@@ -384,7 +389,7 @@ module ActionDispatch
# class Blacklist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path' => 'c#a', constraints: Blacklist.new
+ # match 'path', to: 'c#a', constraints: Blacklist.new
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
@@ -393,7 +398,7 @@ module ActionDispatch
# Sets defaults for parameters
#
# # Sets params[:format] to 'jpg' by default
- # match 'path' => 'c#a', defaults: { format: 'jpg' }
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
#
@@ -402,7 +407,7 @@ module ActionDispatch
# false, the pattern matches any request prefixed with the given path.
#
# # Matches any request starting with 'path'
- # match 'path' => 'c#a', anchor: false
+ # match 'path', to: 'c#a', anchor: false
#
# [:format]
# Allows you to specify the default value for optional +format+
@@ -491,9 +496,7 @@ module ActionDispatch
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
- prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
- prefix = '' if prefix == '/'
- prefix
+ _routes.url_helpers.send("#{name}_path", prefix_options)
end
end
end
@@ -501,7 +504,7 @@ module ActionDispatch
module HttpHelpers
# Define a route that only recognizes HTTP GET.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
# get 'bacon', to: 'food#bacon'
def get(*args, &block)
@@ -509,7 +512,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP POST.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
# post 'bacon', to: 'food#bacon'
def post(*args, &block)
@@ -517,7 +520,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PATCH.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
# patch 'bacon', to: 'food#bacon'
def patch(*args, &block)
@@ -525,7 +528,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PUT.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
# put 'bacon', to: 'food#bacon'
def put(*args, &block)
@@ -533,7 +536,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP DELETE.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
# delete 'broccoli', to: 'food#broccoli'
def delete(*args, &block)
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 1ed5eb1dff..d70063d0e9 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -98,11 +98,11 @@ module ActionDispatch
# Redirect any path to another path:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# You can also use interpolation in the supplied redirect argument:
#
- # match 'docs/:article', to: redirect('/wiki/%{article}')
+ # get 'docs/:article', to: redirect('/wiki/%{article}')
#
# Alternatively you can use one of the other syntaxes:
#
@@ -111,25 +111,25 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', to: redirect { |params, request|
+ # get 'jokes/:number', to: redirect { |params, request|
# path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# }
#
# Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
- # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
+ # the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
#
- # match 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
- # match 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
+ # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
+ # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
- # match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
+ # get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.extract_options!
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 305bafc0c5..79dff7d121 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,5 +1,4 @@
require 'uri'
-require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'
require 'action_controller/metal/exceptions'
@@ -37,17 +36,19 @@ module ActionDispatch
#
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
- def assert_recognizes(expected_options, path, extras={}, message=nil)
+ def assert_recognizes(expected_options, path, extras={}, msg=nil)
request = recognized_request_for(path, extras)
expected_options = expected_options.clone
expected_options.stringify_keys!
- # FIXME: minitest does object diffs, do we need to have our own?
- message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>",
- request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
- assert_equal(expected_options, request.path_parameters, message)
+ msg = message(msg, "") {
+ sprintf("The recognized options <%s> did not match <%s>, difference:",
+ request.path_parameters, expected_options)
+ }
+
+ assert_equal(expected_options, request.path_parameters, msg)
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 459f95bb73..6e51ba66a5 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -129,7 +129,7 @@ module ActionView
minutes_with_offset = distance_in_minutes
end
remainder = (minutes_with_offset % 525600)
- distance_in_years = (minutes_with_offset / 525600)
+ distance_in_years = (minutes_with_offset.div 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index b7b3db959e..46ebe60ec2 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -152,7 +152,7 @@ module ActionView
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
- # In case if you don't want the helper to generate this hidden field you can specify <tt>include_blank: false</tt> option.
+ # In case if you don't want the helper to generate this hidden field you can specify <tt>include_hidden: false</tt> option.
#
def select(object, method, choices, options = {}, html_options = {})
Tags::Select.new(object, method, self, choices, options, html_options).render
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 7f42d6e9c3..e298751062 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -52,7 +52,7 @@ module ActionView
# <%= form_tag('/posts') do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
- # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
+ # # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form>
#
# <%= form_tag('/posts', remote: true) %>
# # => <form action="/posts" method="post" data-remote="true">
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index bbb1af8154..d9c76366f8 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -5,6 +5,8 @@ module ActionView #:nodoc:
attr_reader :paths
+ delegate :[], :include?, :pop, :size, :each, to: :paths
+
def initialize(paths = [])
@paths = typecast paths
end
@@ -14,30 +16,10 @@ module ActionView #:nodoc:
self
end
- def [](i)
- paths[i]
- end
-
def to_ary
paths.dup
end
- def include?(item)
- paths.include? item
- end
-
- def pop
- paths.pop
- end
-
- def size
- paths.size
- end
-
- def each(&block)
- paths.each(&block)
- end
-
def compact
PathSet.new paths.compact
end
diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb
index bf1b5a7d22..dfef43bc9d 100644
--- a/actionpack/lib/action_view/renderer/renderer.rb
+++ b/actionpack/lib/action_view/renderer/renderer.rb
@@ -44,11 +44,11 @@ module ActionView
private
def _template_renderer #:nodoc:
- @_template_renderer ||= TemplateRenderer.new(@lookup_context)
+ TemplateRenderer.new(@lookup_context)
end
def _partial_renderer #:nodoc:
- @_partial_renderer ||= PartialRenderer.new(@lookup_context)
+ PartialRenderer.new(@lookup_context)
end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index aa8eac7846..731d8f9dab 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -47,6 +47,10 @@ module ActionView
class_attribute :erb_implementation
self.erb_implementation = Erubis
+ # Do not escape templates of these mime types.
+ class_attribute :escape_whitelist
+ self.escape_whitelist = ["text/plain"]
+
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def self.call(template)
@@ -78,6 +82,7 @@ module ActionView
self.class.erb_implementation.new(
erb,
+ :escape => (self.class.escape_whitelist.include? template.type),
:trim => (self.class.erb_trim_mode == "-")
).src
end