diff options
Diffstat (limited to 'actionpack')
37 files changed, 502 insertions, 234 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 72121668ac..04a3bf8697 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,46 @@ ## Rails 4.0.0 (unreleased) ## +* Fix error when using a non-hash query argument named "params" in `url_for`. + + Before: + + url_for(params: "") # => undefined method `reject!' for "":String + + After: + + url_for(params: "") # => http://www.example.com?params= + + *tumayun + Carlos Antonio da Silva* + +* Render every partial with a new `ActionView::PartialRenderer`. This resolves + issues when rendering nested partials. + Fix #8197 + + *Yves Senn* + +* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list + of mime types where template text is not html escaped by default. It prevents `Jack & Joe` + from rendering as `Jack & Joe` for the whitelisted mime types. The default whitelist + contains text/plain. Fix #7976 + + *Joost Baaij* + +* Fix input name when `:multiple => true` and `:index` are set. + + Before: + + check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> + + After: + + check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> + + Fix #8108 + + *Daniel Fox, Grant Hutchins & Trace Wax* + * Clear url helpers when reloading routes. *Santiago Pastorino* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 7d292ac17c..89fdd528c2 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -6,24 +6,26 @@ Gem::Specification.new do |s| s.version = version s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' + s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' - s.add_dependency('activesupport', version) - s.add_dependency('builder', '~> 3.1.0') - s.add_dependency('rack', '~> 1.4.1') - s.add_dependency('rack-test', '~> 0.6.1') - s.add_dependency('journey', '~> 2.0.0') - s.add_dependency('erubis', '~> 2.7.0') + s.add_dependency 'activesupport', version + s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'rack', '~> 1.4.1' + s.add_dependency 'rack-test', '~> 0.6.1' + s.add_dependency 'journey', '~> 2.0.0' + s.add_dependency 'erubis', '~> 2.7.0' - s.add_development_dependency('activemodel', version) - s.add_development_dependency('tzinfo', '~> 0.3.33') + s.add_development_dependency 'activemodel', version + s.add_development_dependency 'tzinfo', '~> 0.3.33' end 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 diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 8340aab4d2..6414ba3994 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class FlashTest < ActionController::TestCase class TestController < ActionController::Base @@ -217,7 +219,7 @@ end class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionKey = '_myapp_session' - SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') class TestController < ActionController::Base add_flash_types :bar @@ -291,7 +293,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest # Overwrite get to send SessionSecret in env hash def get(path, parameters = nil, env = {}) - env["action_dispatch.secret_token"] ||= SessionSecret + env["action_dispatch.key_generator"] ||= Generator super end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index b11ad633bd..c4a94264c3 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base @@ -40,8 +42,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase setup do # Used as secret in generating nonce to prevent tampering of timestamp - @secret = "session_options_secret" - @request.env["action_dispatch.secret_token"] = @secret + @secret = "4fb45da9e4ab4ddeb7580d6a35503d99" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret) end teardown do diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index fc63470174..7cc71fe6dc 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -20,26 +20,51 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "monkey", @params.fetch(:foo) { "monkey" } end - test "permitted is sticky on accessors" do + test "not permitted is sticky on accessors" do assert !@params.slice(:person).permitted? assert !@params[:person][:name].permitted? + assert !@params[:person].except(:name).permitted? - @params.each { |key, value| assert(value.permitted?) if key == :person } + @params.each { |key, value| assert(!value.permitted?) if key == "person" } assert !@params.fetch(:person).permitted? assert !@params.values_at(:person).first.permitted? end + test "permitted is sticky on accessors" do + @params.permit! + assert @params.slice(:person).permitted? + assert @params[:person][:name].permitted? + assert @params[:person].except(:name).permitted? + + @params.each { |key, value| assert(value.permitted?) if key == "person" } + + assert @params.fetch(:person).permitted? + + assert @params.values_at(:person).first.permitted? + end + + test "not permitted is sticky on mutators" do + assert !@params.delete_if { |k| k == "person" }.permitted? + assert !@params.keep_if { |k,v| k == "person" }.permitted? + end + test "permitted is sticky on mutators" do - assert !@params.delete_if { |k| k == :person }.permitted? - assert !@params.keep_if { |k,v| k == :person }.permitted? + @params.permit! + assert @params.delete_if { |k| k == "person" }.permitted? + assert @params.keep_if { |k,v| k == "person" }.permitted? end - test "permitted is sticky beyond merges" do + test "not permitted is sticky beyond merges" do assert !@params.merge(a: "b").permitted? end + test "permitted is sticky beyond merges" do + @params.permit! + assert @params.merge(a: "b").permitted? + end + test "modifying the parameters" do @params[:person][:hometown] = "Chicago" @params[:person][:family] = { brother: "Jonas" } @@ -77,7 +102,7 @@ class ParametersPermitTest < ActiveSupport::TestCase ActionController::Parameters.permit_all_parameters = false end end - + test "permitting parameters as an array" do assert_equal "32", @params[:person].permit([ :age ])[:age] end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 209f021cf7..d87e2b85b0 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -4,7 +4,7 @@ module Admin; class User; end; end module ParamsWrapperTestHelp def with_default_wrapper_options(&block) - @controller.class._wrapper_options = {:format => [:json]} + @controller.class._set_wrapper_options({:format => [:json]}) @controller.class.inherited(@controller.class) yield end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index aa33f01d02..859ed1466b 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -646,6 +646,10 @@ class TestController < ActionController::Base render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] end + def partial_collection_with_spacer_which_uses_render + render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + def partial_collection_shorthand_with_locals render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end @@ -1445,6 +1449,12 @@ class RenderTest < ActionController::TestCase assert_template :partial => '_customer' end + def test_partial_collection_with_spacer_which_uses_render + get :partial_collection_with_spacer_which_uses_render + assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body + assert_template :partial => '_customer' + end + def test_partial_collection_shorthand_with_locals get :partial_collection_shorthand_with_locals assert_equal "Bonjour: davidBonjour: mary", @response.body diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index ab1bd0e3b6..718d06ef38 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -104,7 +104,7 @@ module ShowExceptions get '/', {}, 'HTTP_ACCEPT' => 'text/json' assert_response :internal_server_error assert_equal 'text/plain', response.content_type.to_s - + ensure @app.instance_variable_set(:@exceptions_app, @exceptions_app) $stderr = STDERR end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 347b3b3b5a..ffa91d63c4 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class CookiesTest < ActionController::TestCase class TestController < ActionController::Base @@ -65,6 +67,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_encrypted_cookie + cookies.encrypted[:foo] = 'bar' + head :ok + end + def raise_data_overflow cookies.signed[:foo] = 'bye!' * 1024 head :ok @@ -146,7 +153,10 @@ class CookiesTest < ActionController::TestCase def setup super - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + @request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" @request.host = "www.nextangle.com" end @@ -296,6 +306,16 @@ class CookiesTest < ActionController::TestCase assert_equal 45, @controller.send(:cookies).signed[:user_id] end + def test_encrypted_cookie + get :set_encrypted_cookie + cookies = @controller.send :cookies + assert_not_equal 'bar', cookies[:foo] + assert_raises TypeError do + cookies.signed[:foo] + end + assert_equal 'bar', cookies.encrypted[:foo] + end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature get :set_signed_cookie assert_nil @controller.send(:cookies).signed[:non_existant_attribute] @@ -329,29 +349,29 @@ class CookiesTest < ActionController::TestCase def test_raises_argument_error_if_missing_secret assert_raise(ArgumentError, nil.inspect) { - @request.env["action_dispatch.secret_token"] = nil + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil) get :set_signed_cookie } assert_raise(ArgumentError, ''.inspect) { - @request.env["action_dispatch.secret_token"] = "" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("") get :set_signed_cookie } end def test_raises_argument_error_if_secret_is_probably_insecure assert_raise(ArgumentError, "password".inspect) { - @request.env["action_dispatch.secret_token"] = "password" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password") get :set_signed_cookie } assert_raise(ArgumentError, "secret".inspect) { - @request.env["action_dispatch.secret_token"] = "secret" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret") get :set_signed_cookie } assert_raise(ArgumentError, "12345678901234567890123456789".inspect) { - @request.env["action_dispatch.secret_token"] = "12345678901234567890123456789" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789") get :set_signed_cookie } end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index cfbf970a37..113608ecf4 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -241,6 +241,11 @@ module TestGenerationPrefix assert_equal "/something/", app_object.root_path end + test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do + RailsApplication.routes.default_url_options[:trailing_slash] = true + assert_equal "/awesome/blog/posts", engine_object.posts_path + end + test "[OBJECT] generating engine's route with url_for" do path = engine_object.url_for(:controller => "inside_engine_generating", :action => "show", diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index e2964f9071..f2bacf3e20 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' class RequestTest < ActiveSupport::TestCase def url_for(options = {}) - options.reverse_merge!(:host => 'www.example.com') + options = { host: 'www.example.com' }.merge!(options) ActionDispatch::Http::URL.url_for(options) end @@ -25,6 +25,8 @@ class RequestTest < ActiveSupport::TestCase assert_equal 'http://www.example.com/', url_for(:trailing_slash => true) assert_equal 'http://dhh:supersecret@www.example.com', url_for(:user => 'dhh', :password => 'supersecret') assert_equal 'http://www.example.com?search=books', url_for(:params => { :search => 'books' }) + assert_equal 'http://www.example.com?params=', url_for(:params => '') + assert_equal 'http://www.example.com?params=1', url_for(:params => 1) end test "remote ip" do @@ -355,7 +357,6 @@ class RequestTest < ActiveSupport::TestCase assert_equal "/of/some/uri", request.path_info end - test "host with default port" do request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80' assert_equal "rubyonrails.org", request.host_with_port @@ -577,16 +578,16 @@ class RequestTest < ActiveSupport::TestCase test "formats with accept header" do request = stub_request 'HTTP_ACCEPT' => 'text/html' request.expects(:parameters).at_least_once.returns({}) - assert_equal [ Mime::HTML ], request.formats + assert_equal [Mime::HTML], request.formats request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) - assert_equal with_set(Mime::XML), request.formats + assert_equal [Mime::XML], request.formats request = stub_request request.expects(:parameters).at_least_once.returns({ :format => :txt }) - assert_equal with_set(Mime::TEXT), request.formats + assert_equal [Mime::TEXT], request.formats request = stub_request request.expects(:parameters).at_least_once.returns({ :format => :unknown }) @@ -811,8 +812,4 @@ protected ActionDispatch::Http::URL.tld_length = tld_length ActionDispatch::Request.new(env) end - - def with_set(*args) - args - end end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 41fa036a92..1677dee524 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -1,9 +1,12 @@ require 'abstract_unit' require 'stringio' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class CookieStoreTest < ActionDispatch::IntegrationTest SessionKey = '_myapp_session' SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret) Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1') SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16)) @@ -330,7 +333,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # Overwrite get to send SessionSecret in env hash def get(path, parameters = nil, env = {}) - env["action_dispatch.secret_token"] ||= SessionSecret + env["action_dispatch.key_generator"] ||= Generator super end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 8bd8eff3c0..f9ce63fcb0 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -19,6 +19,8 @@ class DateHelperTest < ActionView::TestCase end def assert_distance_of_time_in_words(from, to=nil) + Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn) + to ||= from # 0..1 minute with :include_seconds => true @@ -121,6 +123,9 @@ class DateHelperTest < ActionView::TestCase assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true) assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false) + + ensure + Fixnum.send :public, :/ end def test_distance_in_words diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 86ba5f3b4d..ed9d303158 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -1,3 +1,4 @@ +# encoding: US-ASCII require "abstract_unit" require "logger" @@ -25,6 +26,10 @@ class TestERBTemplate < ActiveSupport::TestCase "Hello" end + def apostrophe + "l'apostrophe" + end + def partial ActionView::Template.new( "<%= @virtual_path %>", @@ -47,7 +52,7 @@ class TestERBTemplate < ActiveSupport::TestCase end end - def new_template(body = "<%= hello %>", details = {}) + def new_template(body = "<%= hello %>", details = { format: :html }) ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details)) end @@ -71,6 +76,16 @@ class TestERBTemplate < ActiveSupport::TestCase assert_equal "Hello", render end + def test_basic_template_does_html_escape + @template = new_template("<%= apostrophe %>") + assert_equal "l'apostrophe", render + end + + def test_text_template_does_not_html_escape + @template = new_template("<%= apostrophe %>", format: :text) + assert_equal "l'apostrophe", render + end + def test_raw_template @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new) assert_equal "<%= hello %>", render |