aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb14
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/abstract_controller/translation.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb5
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb5
-rw-r--r--actionpack/lib/action_controller/metal/live.rb5
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb8
-rw-r--r--actionpack/lib/action_controller/test_case.rb51
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb54
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb41
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb302
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb96
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing.rb92
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb71
-rw-r--r--actionpack/lib/action_pack/version.rb13
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb18
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb15
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb24
-rw-r--r--actionpack/lib/action_view/lookup_context.rb8
-rw-r--r--actionpack/lib/action_view/template.rb5
-rw-r--r--actionpack/lib/action_view/template/resolver.rb4
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb10
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/selector.rb16
40 files changed, 530 insertions, 441 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 56dc9ab7a1..af5de815bb 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -35,7 +35,7 @@ module AbstractController
end
def inherited(klass) # :nodoc:
- # define the abstract ivar on subclasses so that we don't get
+ # Define the abstract ivar on subclasses so that we don't get
# uninitialized ivar warnings
unless klass.instance_variable_defined?(:@abstract)
klass.instance_variable_set(:@abstract, false)
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 812a35735f..5ae8c6c3b0 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -29,7 +29,7 @@ module AbstractController
# helper_method :current_user, :logged_in?
#
# def current_user
- # @current_user ||= User.find_by_id(session[:user])
+ # @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
@@ -59,7 +59,7 @@ module AbstractController
# The +helper+ class method can take a series of helper module names, a block, or both.
#
# ==== Options
- # * <tt>*args</tt> - Module, Symbol, String, :all
+ # * <tt>*args</tt> - Module, Symbol, String
# * <tt>block</tt> - A block defining helper methods
#
# When the argument is a module it will be included directly in the template class.
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 91864f2a35..bac994496e 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -285,10 +285,9 @@ module AbstractController
remove_possible_method(:_layout)
prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
name_clause = if name
- <<-RUBY
- lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
- RUBY
+ default_behavior
else
<<-RUBY
super
@@ -301,6 +300,7 @@ module AbstractController
when Symbol
<<-RUBY
#{_layout}.tap do |layout|
+ return #{default_behavior} if layout.nil?
unless layout.is_a?(String) || !layout
raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
"should have returned a String, false, or nil"
@@ -308,8 +308,12 @@ module AbstractController
end
RUBY
when Proc
- define_method :_layout_from_proc, &_layout
- _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
+ define_method :_layout_from_proc, &_layout
+ <<-RUBY
+ result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
+ return #{default_behavior} if result.nil?
+ result
+ RUBY
when false
nil
when true
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index f8e4cb4384..3f34add790 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -105,8 +105,8 @@ module AbstractController
#
# If a component extends the semantics of response_body
# (as Action Controller extends it to be anything that
- # responds to the method each), this method needs to
- # overriden in order to still return a string.
+ # responds to the method each), this method needs to be
+ # overridden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index db48022b9f..02028d8e05 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -11,7 +11,7 @@ module AbstractController
def translate(*args)
key = args.first
if key.is_a?(String) && (key[0] == '.')
- key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }"
+ key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"
args[0] = key
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3d274e7dd7..7318c8b7ec 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -48,6 +48,11 @@ module ActionController
info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
end
+ def unpermitted_parameters(event)
+ unpermitted_keys = event.payload[:keys]
+ debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
+ end
+
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 2aa6b7adaf..af36ffa240 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -27,7 +27,7 @@ module ActionController
end
def visible_action?(action_name)
- action_methods.include?(action_name)
+ not hidden_actions.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index e295002b16..158d552ec7 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -29,7 +29,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -299,6 +299,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
@@ -344,7 +345,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 32e5afa335..fb664a69dd 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -14,6 +14,7 @@ module ActionController
# response.stream.write "hello world\n"
# sleep 1
# }
+ # ensure
# response.stream.close
# end
# end
@@ -97,6 +98,10 @@ module ActionController
def merge_default_headers(original, default)
Header.new self, super
end
+
+ def handle_conditional_get!
+ super unless committed?
+ end
end
def process(name)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index e4dcd3213f..23d70c9ea2 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
+require 'stringio'
module ActionController
# Raised when a required parameter is missing.
@@ -68,6 +69,8 @@ module ActionController
# ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
# in test and development environments, +false+ otherwise.
#
+ # Examples:
+ #
# params = ActionController::Parameters.new
# params.permitted? # => false
#
@@ -339,7 +342,8 @@ module ActionController
if unpermitted_keys.any?
case self.class.action_on_unpermitted_parameters
when :log
- ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}"
+ name = "unpermitted_parameters.action_controller"
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
when :raise
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
end
@@ -417,7 +421,7 @@ module ActionController
# Declaration { comment_ids: [] }.
array_of_permitted_scalars_filter(params, key)
else
- # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }.
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
if element.is_a?(Hash)
element = self.class.new(element) unless element.respond_to?(:permit)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index bba1f1e201..b9a5e78fe9 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -16,6 +16,7 @@ module ActionController
@_partials = Hash.new(0)
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
+ @_files = Hash.new(0)
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
@@ -39,6 +40,16 @@ module ActionController
@_templates[path] += 1
end
+
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ next if payload[:virtual_path] # files don't have virtual path
+
+ path = payload[:identifier]
+ if path
+ @_files[path] += 1
+ @_files[path.split("/").last] += 1
+ end
+ end
end
def teardown_subscriptions
@@ -106,7 +117,7 @@ module ActionController
end
assert matches_template, msg
when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count)
+ options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
if options.key?(:layout)
expected_layout = options[:layout]
@@ -123,6 +134,10 @@ module ActionController
end
end
+ if options[:file]
+ assert_includes @_files.keys, options[:file]
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
if defined?(@_rendered_views)
@@ -301,7 +316,7 @@ module ActionController
# assert_response :found
#
# # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
+ # assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
@@ -436,37 +451,55 @@ module ActionController
end
- # Executes a request simulating GET HTTP method and set/volley the response
+ # Simulate a GET request with the given parameters.
+ #
+ # - +action+: The controller action to call.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+, a Hash, or a String that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # - +session+: A Hash of parameters to store in the session. This may be +nil+.
+ # - +flash+: A Hash of parameters to store in the flash. This may be +nil+.
+ #
+ # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
+ # Note that the request method is not verified. The different methods are
+ # available to make the tests more expressive.
def get(action, *args)
process(action, "GET", *args)
end
- # Executes a request simulating POST HTTP method and set/volley the response
+ # Simulate a POST request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def post(action, *args)
process(action, "POST", *args)
end
- # Executes a request simulating PATCH HTTP method and set/volley the response
+ # Simulate a PATCH request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def patch(action, *args)
process(action, "PATCH", *args)
end
- # Executes a request simulating PUT HTTP method and set/volley the response
+ # Simulate a PUT request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def put(action, *args)
process(action, "PUT", *args)
end
- # Executes a request simulating DELETE HTTP method and set/volley the response
+ # Simulate a DELETE request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def delete(action, *args)
process(action, "DELETE", *args)
end
- # Executes a request simulating HEAD HTTP method and set/volley the response
+ # Simulate a HEAD request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def head(action, *args)
process(action, "HEAD", *args)
end
- # Executes a request simulating OPTIONS HTTP method and set/volley the response
+ # Simulate a OPTIONS request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def options(action, *args)
process(action, "OPTIONS", *args)
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 618e2f3033..2c88bc3b1f 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -84,8 +84,6 @@ module ActionDispatch
module Session
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
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0d6015d993..f9b278349e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -92,7 +92,7 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
- SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+ SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate]
def cache_control_segments
if cache_control = self[CACHE_CONTROL]
@@ -108,7 +108,7 @@ module ActionDispatch
cache_control_segments.each do |segment|
directive, argument = segment.split('=', 2)
- if SPESHUL_KEYS.include? directive
+ if SPECIAL_KEYS.include? directive
key = directive.tr('-', '_')
cache_control[key.to_sym] = argument || true
else
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index dc04d4577b..2666cd4b0a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,38 +1,62 @@
module ActionDispatch
module Http
class Headers
+ CGI_VARIABLES = %w(
+ CONTENT_TYPE CONTENT_LENGTH
+ HTTPS AUTH_TYPE GATEWAY_INTERFACE
+ PATH_INFO PATH_TRANSLATED QUERY_STRING
+ REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER
+ REQUEST_METHOD SCRIPT_NAME
+ SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
+ )
+ HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
+
include Enumerable
+ attr_reader :env
def initialize(env = {})
- @headers = env
+ @env = env
+ end
+
+ def [](key)
+ @env[env_name(key)]
end
- def [](header_name)
- @headers[env_name(header_name)]
+ def []=(key, value)
+ @env[env_name(key)] = value
end
- def []=(k,v); @headers[k] = v; end
- def key?(k); @headers.key? k; end
+ def key?(key); @env.key? key; end
alias :include? :key?
- def fetch(header_name, *args, &block)
- @headers.fetch env_name(header_name), *args, &block
+ def fetch(key, *args, &block)
+ @env.fetch env_name(key), *args, &block
end
def each(&block)
- @headers.each(&block)
+ @env.each(&block)
end
- private
+ def merge(headers_or_env)
+ headers = Http::Headers.new(env.dup)
+ headers.merge!(headers_or_env)
+ headers
+ end
- # Converts a HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
- def env_name(header_name)
- @headers.include?(header_name) ? header_name : cgi_name(header_name)
+ def merge!(headers_or_env)
+ headers_or_env.each do |key, value|
+ self[env_name(key)] = value
+ end
end
- def cgi_name(k)
- "HTTP_#{k.upcase.gsub(/-/, '_')}"
+ private
+ def env_name(key)
+ key = key.to_s
+ if key =~ HTTP_HEADER
+ key = key.upcase.tr('-', '_')
+ key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
+ end
+ key
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 446862aad0..246d9c121a 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -18,7 +18,7 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- encode_params(params).with_indifferent_access
+ params.with_indifferent_access
end
end
alias :params :parameters
@@ -50,40 +50,33 @@ module ActionDispatch
private
+ # Convert nested Hash to HashWithIndifferentAccess
+ # and UTF-8 encode both keys and values in nested Hash.
+ #
# TODO: Validate that the characters are UTF-8. If they aren't,
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
- def encode_params(params)
+ def normalize_encode_params(params)
if params.is_a?(String)
return params.force_encoding(Encoding::UTF_8).encode!
elsif !params.is_a?(Hash)
return params
end
+ new_hash = {}
params.each do |k, v|
- case v
- when Hash
- encode_params(v)
- when Array
- v.map! {|el| encode_params(el) }
- else
- encode_params(v)
- end
- end
- end
-
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess
- def normalize_parameters(value)
- case value
- when Hash
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- when Array
- value.map { |e| normalize_parameters(e) }
- else
- value
+ new_key = k.is_a?(String) ? k.dup.force_encoding("UTF-8").encode! : k
+ new_hash[new_key] =
+ case v
+ when Hash
+ normalize_encode_params(v)
+ when Array
+ v.map! {|el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(v)
+ end
end
+ new_hash.with_indifferent_access
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 7b04d6e851..ebd87c40b5 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -156,14 +156,29 @@ module ActionDispatch
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
end
+ # Returns the +String+ full path including params of the last URL requested.
+ #
+ # # get "/articles"
+ # request.fullpath # => "/articles"
+ #
+ # # get "/articles?page=2"
+ # request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
+ # Returns the original request URL as a +String+.
+ #
+ # # get "/articles?page=2"
+ # request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
+ # The +String+ MIME type of the request.
+ #
+ # # get "/articles"
+ # request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type.to_s
end
@@ -256,7 +271,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
+ @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -264,7 +279,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
+ @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 67cb7fbcb5..b57c84dec8 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -6,7 +6,7 @@ module ActionDispatch
# of its interface is available directly for convenience.
#
# Uploaded files are temporary files whose lifespan is one request. When
- # the object is finalized Ruby unlinks the file, so there is not need to
+ # the object is finalized Ruby unlinks the file, so there is no need to
# clean them with a separate maintenance task.
class UploadedFile
# The basename of the file in the client.
@@ -75,16 +75,16 @@ module ActionDispatch
end
module Upload # :nodoc:
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace
- # file upload hash with UploadedFile objects
- def normalize_parameters(value)
+ # Replace file upload hash with UploadedFile objects
+ # when normalize and encode parameters.
+ def normalize_encode_params(value)
if Hash === value && value.has_key?(:tempfile)
UploadedFile.new(value)
else
super
end
end
- private :normalize_parameters
+ private :normalize_encode_params
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 97ac462411..ab5399c8ea 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -59,8 +59,9 @@ module ActionDispatch
result = ""
unless options[:only_path]
+ protocol = extract_protocol(options)
unless options[:protocol] == false
- result << (options[:protocol] || "http")
+ result << protocol
result << ":" unless result.match(%r{:|//})
end
result << "//" unless result.match("//")
@@ -83,6 +84,16 @@ module ActionDispatch
end
end
+ # Extracts protocol http:// or https:// from options[:host]
+ # needs to be called whether the :protocol is being used or not
+ def extract_protocol(options)
+ if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/)
+ options[:protocol] ||= match[1]
+ options[:host] = match[2]
+ end
+ options[:protocol] || "http"
+ end
+
def host_or_subdomain_and_domain(options)
return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 36a0db6e61..5b914f293d 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/core_ext/object/blank'
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -30,7 +31,7 @@ module ActionDispatch
#
# # Sets a signed cookie, which prevents users from tampering with its 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>
+ # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -52,13 +53,13 @@ module ActionDispatch
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
- # cookies[:key] = {
+ # cookies[:name] = {
# value: 'a yummy cookie',
# expires: 1.year.from_now,
# domain: 'domain.com'
# }
#
- # cookies.delete(:key, domain: 'domain.com')
+ # cookies.delete(:name, domain: 'domain.com')
#
# The option symbols for setting cookies are:
#
@@ -69,7 +70,7 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting keys.
+ # <tt>:all</tt> again when deleting cookies.
#
# domain: nil # Does not sets cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
@@ -86,7 +87,8 @@ module ActionDispatch
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
+ SECRET_TOKEN = "action_dispatch.secret_token".freeze
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -94,8 +96,99 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ module ChainedCookieJars
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
+ #
+ # cookies.permanent[:prefers_open_id] = true
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ #
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
+ #
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
+ #
+ # 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, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
+ # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
+ # cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ else
+ SignedCookieJar.new(self, @key_generator, @options)
+ end
+ 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), nil will be returned.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # 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 ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ else
+ EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
+ # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set.
+ # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
+ def signed_or_encrypted
+ @signed_or_encrypted ||=
+ if @options[:secret_key_base].present?
+ encrypted
+ else
+ signed
+ end
+ end
+ end
+
+ module VerifyAndUpgradeLegacySignedMessage
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ end
+
+ def verify_and_upgrade_legacy_signed_message(name, signed_message)
+ @legacy_verifier.verify(signed_message).tap do |value|
+ self[name] = value
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+ end
+
class CookieJar #:nodoc:
- include Enumerable
+ include Enumerable, ChainedCookieJars
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,7 +208,10 @@ module ActionDispatch
{ 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] }
+ secret_token: env[SECRET_TOKEN],
+ secret_key_base: env[SECRET_KEY_BASE],
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ }
end
def self.build(request)
@@ -184,7 +280,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -195,10 +291,10 @@ module ActionDispatch
handle_options(options)
- if @cookies[key.to_s] != value or options[:expires]
- @cookies[key.to_s] = value
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
+ if @cookies[name.to_s] != value or options[:expires]
+ @cookies[name.to_s] = value
+ @set_cookies[name.to_s] = options
+ @delete_cookies.delete(name.to_s)
end
value
@@ -207,24 +303,24 @@ module ActionDispatch
# Removes the cookie on the client machine by setting the value to an empty string
# and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
- def delete(key, options = {})
- return unless @cookies.has_key? key.to_s
+ def delete(name, options = {})
+ return unless @cookies.has_key? name.to_s
options.symbolize_keys!
handle_options(options)
- value = @cookies.delete(key.to_s)
- @delete_cookies[key.to_s] = options
+ value = @cookies.delete(name.to_s)
+ @delete_cookies[name.to_s] = options
value
end
# Whether the given cookie is to be deleted by this CookieJar.
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
- def deleted?(key, options = {})
+ def deleted?(name, options = {})
options.symbolize_keys!
handle_options(options)
- @delete_cookies[key.to_s] == options
+ @delete_cookies[name.to_s] == options
end
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
@@ -232,59 +328,6 @@ module ActionDispatch
@cookies.each_key{ |k| delete(k, options) }
end
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # 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, @key_generator, @options)
- end
-
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # 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.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @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)
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
@@ -299,24 +342,25 @@ module ActionDispatch
self.always_write_cookie = false
private
-
def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie
end
end
class PermanentCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@options = options
end
- def [](key)
+ def [](name)
@parent_jar[name.to_s]
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -324,28 +368,13 @@ module ActionDispatch
end
options[:expires] = 20.years.from_now
- @parent_jar[key] = options
- end
-
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
+ @parent_jar[name] = options
end
end
class SignedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
@@ -355,13 +384,11 @@ module ActionDispatch
def [](name)
if signed_message = @parent_jar[name]
- @verifier.verify(signed_message)
+ verify(signed_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -370,32 +397,38 @@ module ActionDispatch
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
+ private
+ def verify(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+ end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
+ # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
+ # config.secret_token and config.secret_key_base are both set. It reads
+ # legacy cookies signed with the old dummy key generator and re-saves
+ # them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
+ def [](name)
+ if signed_message = @parent_jar[name]
+ verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ end
end
end
class EncryptedCookieJar #:nodoc:
+ include ChainedCookieJars
+
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"
+ if ActiveSupport::LegacyKeyGenerator === key_generator
+ raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ "Read the upgrade documentation to learn more about this new config option."
end
@parent_jar = parent_jar
@@ -405,16 +438,13 @@ module ActionDispatch
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
end
- def [](key)
- if encrypted_message = @parent_jar[key]
- @encryptor.decrypt_and_verify(encrypted_message)
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature,
- ActiveSupport::MessageEncryptor::InvalidMessage
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -423,24 +453,28 @@ module ActionDispatch
options[:value] = @encryptor.encrypt_and_sign(options[:value])
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
+ private
+ def decrypt_and_verify(encrypted_message)
+ @encryptor.decrypt_and_verify(encrypted_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+ end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
+ # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
+ # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # are both set. It reads legacy cookies signed with the old dummy key generator and
+ # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
+ def [](name)
+ if encrypted_or_signed_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 93a2b52996..8879291dbd 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -101,7 +101,7 @@ module ActionDispatch
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
)$)
}x
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 1e6ed624b0..b9eb8036e9 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -4,36 +4,51 @@ require 'rack/session/cookie'
module ActionDispatch
module Session
- # This cookie-based session store is the Rails default. Sessions typically
- # contain at most a user_id and flash message; both fit within the 4K cookie
- # size limit. Cookie-based sessions are dramatically faster than the
- # alternatives.
+ # This cookie-based session store is the Rails default. It is
+ # dramatically faster than the alternatives.
#
- # If you have more than 4K of session data or don't want your data to be
- # visible to the user, pick another session store.
+ # Sessions typically contain at most a user_id and flash message; both fit
+ # within the 4K cookie size limit. A CookieOverflow exception is raised if
+ # you attempt to store more than 4K of data.
#
- # CookieOverflow is raised if you attempt to store more than 4K of data.
+ # The cookie jar used for storage is automatically configured to be the
+ # best possible option given your application's configuration.
#
- # A message digest is included with the cookie to ensure data integrity:
- # a user cannot alter his +user_id+ without knowing the secret key
- # included in the hash. New apps are generated with a pregenerated secret
- # in config/environment.rb. Set your own for old apps you're upgrading.
+ # If you only have secret_token set, your cookies will be signed, but
+ # not encrypted. This means a user cannot alter his +user_id+ without
+ # knowing your app's secret key, but can easily read his +user_id+. This
+ # was the default for Rails 3 apps.
#
- # Session options:
+ # If you have secret_key_base set, your cookies will be encrypted. This
+ # goes a step further than signed cookies in that encrypted cookies cannot
+ # be altered or read by users. This is the default starting in Rails 4.
#
- # * <tt>:secret</tt>: An application-wide key string. It's important that
- # the secret is not vulnerable to a dictionary attack. Therefore, you
- # should choose a secret consisting of random numbers and letters and
- # more than 30 characters.
+ # If you have both secret_token and secret_key base set, your cookies will
+ # be encrypted, and signed cookies generated by Rails 3 will be
+ # transparently read and encrypted to provide a smooth upgrade path.
#
- # secret: '449fe2e7daee471bffae2fd8dc02313d'
+ # Configure your session store in config/initializers/session_store.rb:
#
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+ # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/initializers/secret_token.rb.
+ # Configure your secret key in config/initializers/secret_token.rb:
+ #
+ # Myapp::Application.config.secret_key_base 'secret key'
+ #
+ # To generate a secret key for an existing application, run `rake secret`.
+ #
+ # If you are upgrading an existing Rails 3 app, you should leave your
+ # existing secret_token in place and simply add the new secret_key_base.
+ # Note that you should wait to set secret_key_base until you have 100% of
+ # your userbase on Rails 4 and are reasonably sure you will not need to
+ # rollback to Rails 3. This is because cookies signed based on the new
+ # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
+ # You are free to leave your existing secret_token in place, not set the
+ # new secret_key_base, and ignore the deprecation warnings until you are
+ # reasonably sure that your upgrade is otherwise complete. Additionally,
+ # you should take care to make sure you are not relying on the ability to
+ # decode signed cookies generated by your app in external applications or
+ # Javascript before upgrading.
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
@@ -100,42 +115,7 @@ module ActionDispatch
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_using_old_secret
+ request.cookie_jar.signed_or_encrypted
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index 61690d3e50..cd3daff065 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -8,7 +8,7 @@
<h2>Failure reasons:</h2>
<ol>
<% @exception.failures.each do |route, reason| %>
- <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li>
+ <li><code><%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %></li>
<% end %>
</ol>
</p>
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d55eb8109a..550c7d0e7b 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -69,6 +69,22 @@ module ActionDispatch
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
# <tt>Routing::Mapper::Scoping#scope</tt>.
#
+ # == Non-resourceful routes
+ #
+ # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
+ #
+ # get 'post/:id' => 'posts#show'
+ # post 'post/:id' => 'posts#create_comment'
+ #
+ # If your route needs to respond to more than one HTTP method (or all methods) then using the
+ # <tt>:via</tt> option on <tt>match</tt> is preferable.
+ #
+ # match 'post/:id' => 'posts#show', via: [:get, :post]
+ #
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
+ # URL will route to the <tt>show</tt> action.
+ #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
@@ -78,7 +94,7 @@ module ActionDispatch
# Example:
#
# # In routes.rb
- # match '/login' => 'accounts#login', as: 'login'
+ # get '/login' => 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
@@ -104,9 +120,9 @@ module ActionDispatch
#
# # In routes.rb
# controller :blog do
- # match 'blog/show' => :list
- # match 'blog/delete' => :delete
- # match 'blog/edit/:id' => :edit
+ # get 'blog/show' => :list
+ # get 'blog/delete' => :delete
+ # get 'blog/edit/:id' => :edit
# end
#
# # provides named routes for show, delete, and edit
@@ -116,7 +132,7 @@ module ActionDispatch
#
# Routes can generate pretty URLs. For example:
#
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
+ # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
# year: /\d{4}/,
# month: /\d{1,2}/,
# day: /\d{1,2}/
@@ -131,7 +147,7 @@ module ActionDispatch
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /\d{5}(-\d{4})?/
# }
#
@@ -139,13 +155,13 @@ module ActionDispatch
# expression modifiers:
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
@@ -153,73 +169,21 @@ module ActionDispatch
# }
# end
#
- # Using the multiline match modifier will raise an +ArgumentError+.
+ # Using the multiline modifier will raise an +ArgumentError+.
# Encoding regular expression modifiers are silently ignored. The
# match will always use the default encoding or ASCII.
#
- # == Default route
- #
- # Consider the following route, which you will find commented out at the
- # bottom of your generated <tt>config/routes.rb</tt>:
- #
- # match ':controller(/:action(/:id))(.:format)'
- #
- # This route states that it expects requests to consist of a
- # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
- # turn is followed optionally by an <tt>:id</tt>, which in turn is followed
- # optionally by a <tt>:format</tt>.
- #
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
- # up with:
- #
- # params = { controller: 'blog',
- # action: 'edit',
- # id: '22'
- # }
- #
- # By not relying on default routes, you improve the security of your
- # application since not all controller actions, which includes actions you
- # might add at a later time, are exposed by default.
- #
- # == HTTP Methods
- #
- # Using the <tt>:via</tt> option when specifying a route allows you to
- # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
- # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
- # <tt>:any</tt>. If your route needs to respond to more than one method you
- # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
- # <tt>:any</tt> which means that the route will respond to any of the HTTP
- # methods.
- #
- # match 'post/:id' => 'posts#show', via: :get
- # match 'post/:id' => 'posts#create_comment', via: :post
- #
- # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
- # URL will route to the <tt>show</tt> action.
- #
- # === HTTP helper methods
- #
- # An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
- #
- # get 'post/:id' => 'posts#show'
- # post 'post/:id' => 'posts#create_comment'
- #
- # This syntax is less verbose and the intention is more apparent to someone else reading your code,
- # however if your route needs to respond to more than one HTTP method (or all methods) then using the
- # <tt>:via</tt> option on <tt>match</tt> is preferable.
- #
# == External redirects
#
# You can redirect any path to another path using the redirect helper in your router:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# == Unicode character routes
#
# You can specify unicode character routes in your router:
#
- # match "こんにちは" => "welcome#index"
+ # get "こんにちは" => "welcome#index"
#
# == Routing to Rack Applications
#
@@ -227,7 +191,7 @@ module ActionDispatch
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
- # match "/application.js" => Sprockets
+ # get "/application.js" => Sprockets
#
# == Reloading routes
#
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 4fbe156a72..45d2d3346e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -58,8 +58,8 @@ module ActionDispatch
@set, @scope, @path, @options = set, scope, path, options
@requirements, @conditions, @defaults = {}, {}, {}
- normalize_path!
normalize_options!
+ normalize_path!
normalize_requirements!
normalize_conditions!
normalize_defaults!
@@ -181,7 +181,8 @@ module ActionDispatch
if !via_all && options[:via].blank?
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
- "If you want to expose your action to GET, use `get` in the router:\n\n" \
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n" \
" Instead of: match \"controller#action\"\n" \
" Do: get \"controller#action\""
raise msg
@@ -588,8 +589,7 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
- options[:path] ||= args.first if args.first.is_a?(String)
+ options[:via] = method
match(*args, options, &block)
self
end
@@ -1369,18 +1369,23 @@ module ActionDispatch
paths = [path] + rest
end
- path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, options)
- options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
- end
-
options[:anchor] = true unless options.key?(:anchor)
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- paths.each { |_path| decomposed_match(_path, options.dup) }
+ paths.each do |_path|
+ route_options = options.dup
+ route_options[:path] ||= _path if _path.is_a?(String)
+
+ path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, route_options)
+ route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ end
+
+ decomposed_match(_path, route_options)
+ end
self
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 619dd22ec1..07203428d4 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -403,11 +403,19 @@ module ActionDispatch
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
+ if name && named_routes[name]
+ raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \
+ "You may have defined two routes with the same name using the `:as` option, or " \
+ "you may be overriding a route already defined by a resource with the same naming. " \
+ "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
+ "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ end
+
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name && !named_routes[name]
+ named_routes[name] = route if name
route
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index ed4e88aab6..56c31255f3 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -17,7 +17,7 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
# merged into the Rack env hash.
#
# This method returns a Response object, which one can use to
@@ -28,44 +28,44 @@ module ActionDispatch
#
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
- def get(path, parameters = nil, headers = nil)
- process :get, path, parameters, headers
+ def get(path, parameters = nil, headers_or_env = nil)
+ process :get, path, parameters, headers_or_env
end
# Performs a POST request with the given parameters. See +#get+ for more
# details.
- def post(path, parameters = nil, headers = nil)
- process :post, path, parameters, headers
+ def post(path, parameters = nil, headers_or_env = nil)
+ process :post, path, parameters, headers_or_env
end
# Performs a PATCH request with the given parameters. See +#get+ for more
# details.
- def patch(path, parameters = nil, headers = nil)
- process :patch, path, parameters, headers
+ def patch(path, parameters = nil, headers_or_env = nil)
+ process :patch, path, parameters, headers_or_env
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
- def put(path, parameters = nil, headers = nil)
- process :put, path, parameters, headers
+ def put(path, parameters = nil, headers_or_env = nil)
+ process :put, path, parameters, headers_or_env
end
# Performs a DELETE request with the given parameters. See +#get+ for
# more details.
- def delete(path, parameters = nil, headers = nil)
- process :delete, path, parameters, headers
+ def delete(path, parameters = nil, headers_or_env = nil)
+ process :delete, path, parameters, headers_or_env
end
# Performs a HEAD request with the given parameters. See +#get+ for more
# details.
- def head(path, parameters = nil, headers = nil)
- process :head, path, parameters, headers
+ def head(path, parameters = nil, headers_or_env = nil)
+ process :head, path, parameters, headers_or_env
end
# Performs a OPTIONS request with the given parameters. See +#get+ for
# more details.
- def options(path, parameters = nil, headers = nil)
- process :options, path, parameters, headers
+ def options(path, parameters = nil, headers_or_env = nil)
+ process :options, path, parameters, headers_or_env
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
@@ -74,11 +74,11 @@ module ActionDispatch
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash.
- def xml_http_request(request_method, path, parameters = nil, headers = nil)
- headers ||= {}
- headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers)
+ def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
+ headers_or_env ||= {}
+ headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ process(request_method, path, parameters, headers_or_env)
end
alias xhr :xml_http_request
@@ -95,40 +95,40 @@ module ActionDispatch
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers = nil)
- process(http_method, path, parameters, headers)
+ def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
+ process(http_method, path, parameters, headers_or_env)
follow_redirect! while redirect?
status
end
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def get_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:get, path, parameters, headers)
+ def get_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:get, path, parameters, headers_or_env)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def post_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:post, path, parameters, headers)
+ def post_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:post, path, parameters, headers_or_env)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def patch_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:patch, path, parameters, headers)
+ def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:patch, path, parameters, headers_or_env)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def put_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:put, path, parameters, headers)
+ def put_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:put, path, parameters, headers_or_env)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def delete_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:delete, path, parameters, headers)
+ def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:delete, path, parameters, headers_or_env)
end
end
@@ -268,8 +268,7 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_env = nil)
- rack_env ||= {}
+ def process(method, path, parameters = nil, headers_or_env = nil)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -300,10 +299,12 @@ module ActionDispatch
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
+ # this modifies the passed env directly
+ Http::Headers.new(env).merge!(headers_or_env || {})
session = Rack::Test::Session.new(_mock_session)
- env.merge!(rack_env)
+ env.merge!(env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 5c87a9cd7c..b5e47d78d1 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,10 +1,11 @@
module ActionPack
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActionPack as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionPack.version.segments
+ STRING = ActionPack.version.to_s
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 1bad82159a..5afe435459 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -180,7 +180,7 @@ module ActionView
# <title>My Website</title>
# <%= yield :script %>
# </head>
- # <body class="<%= content_for?(:right_col) ? 'one-column' : 'two-column' %>">
+ # <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
# <%= yield %>
# <%= yield :right_col %>
# </body>
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 3dae1fc87a..36cfb7fca7 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -433,7 +433,7 @@ module ActionView
builder = instantiate_builder(object_name, object, options)
output = capture(builder, &block)
- html_options[:multipart] = builder.multipart?
+ html_options[:multipart] ||= builder.multipart?
form_tag(options[:url] || {}, html_options) { output }
end
@@ -655,14 +655,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
@@ -1425,14 +1417,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 377819a80c..7e65ebb4e4 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -565,7 +565,7 @@ module ActionView
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = zones.grep(priority_zones)
+ priority_zones = zones.select { |z| z =~ priority_zones }
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
@@ -752,7 +752,7 @@ module ActionView
end
def prompt_text(prompt)
- prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 1adc8225f1..8abd5d6e9c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -778,7 +778,7 @@ module ActionView
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
- name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
+ name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 878d3e0eda..edff98ddaa 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -81,7 +81,7 @@ module ActionView
# # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
#
def button_to_function(name, function=nil, html_options={})
- message = "button_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
+ message = "button_to_function is deprecated and will be removed from Rails 4.1. We recommend using Unobtrusive JavaScript instead. " +
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
ActiveSupport::Deprecation.warn message
@@ -103,7 +103,7 @@ module ActionView
# # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
#
def link_to_function(name, function, html_options={})
- message = "link_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
+ message = "link_to_function is deprecated and will be removed from Rails 4.1. We recommend using Unobtrusive JavaScript instead. " +
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
ActiveSupport::Deprecation.warn message
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
index 3d597079c4..10dec66b4f 100644
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -73,27 +73,26 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
options.delete("index")
elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= options.fetch("name"){ tag_name }
+ options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id }
end
- options["name"] += "[]" if options["multiple"]
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
+ def tag_name(multiple = false)
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ def tag_name_with_index(index, multiple = false)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
def tag_id
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 775d93ed39..22059a0170 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -425,8 +425,8 @@ module ActionView
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Obfuscation
- # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
- # in order to hinder email harvesters. To take advantage of these options,
+ # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
+ # in order to hinder email harvesters. To take advantage of these options,
# install the +actionview-encoded_mail_to+ gem.
#
# ==== Examples
@@ -439,18 +439,30 @@ module ActionView
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
# subject: "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
- def mail_to(email_address, name = nil, html_options = {})
+ #
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
+ #
+ # <%= mail_to "me@domain.com" do %>
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # <% end %>
+ # # => <a href="mailto:me@domain.com">
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # </a>
+ def mail_to(email_address, name = nil, html_options = {}, &block)
email_address = ERB::Util.html_escape(email_address)
- html_options.stringify_keys!
+ html_options, name = name, nil if block_given?
+ html_options = (html_options || {}).stringify_keys
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
- content_tag "a", name || email_address.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
+
+ html_options["href"] = "mailto:#{email_address}#{extras}".html_safe
+
+ content_tag(:a, name || email_address.html_safe, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 4e4816d983..d61cc0f304 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -43,7 +43,13 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
+ register_detail(:locale) do
+ locales = [I18n.locale]
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
+ locales << I18n.default_locale
+ locales.uniq!
+ locales
+ end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
register_detail(:handlers){ Template::Handlers.extensions }
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index f73d14c79b..946db1df79 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -138,7 +138,7 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
+ ActiveSupport::Notifications.instrument("!render_template.action_view", virtual_path: @virtual_path, identifier: @identifier) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -324,7 +324,8 @@ module ActionView
end
def locals_code #:nodoc:
- @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
+ # Double assign to suppress the dreaded 'assigned but unused variable' warning
+ @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join
end
def method_name #:nodoc:
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 1a1083bf00..6b9b0a826b 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -109,7 +109,7 @@ module ActionView
@cache.clear
end
- # Normalizes the arguments and passes it on to find_template.
+ # Normalizes the arguments and passes it on to find_templates.
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details)
@@ -255,7 +255,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # This one allows you to keep files with different formats in seperated subdirectories,
+ # This one allows you to keep files with different formats in separate subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 463f192d0c..10b487f37a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -219,6 +219,7 @@ module ActionView
:@_routes,
:@controller,
:@_layouts,
+ :@_files,
:@_rendered_views,
:@method_name,
:@output_buffer,
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
index 6b4ececda2..30b6b8b141 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
@@ -77,7 +77,7 @@ module HTML
# A regular expression of the valid characters used to separate protocols like
# the ':' in 'http://foo.com'
- self.protocol_separator = /:|(&#0*58)|(&#x70)|(%|&#37;)3A/
+ self.protocol_separator = /:|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i
# Specifies a Set of HTML attributes that can have URIs.
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
@@ -121,8 +121,8 @@ module HTML
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
# gauntlet
- if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
- style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
+ if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
+ style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
return ''
end
@@ -133,7 +133,7 @@ module HTML
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
unless val.split().any? do |keyword|
!allowed_css_keywords.include?(keyword) &&
- keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
+ keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
end
clean << prop + ': ' + val + ';'
end
@@ -182,7 +182,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
index 60b6783b19..7f8609c408 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
@@ -537,7 +537,7 @@ module HTML
# Get identifier, class, attribute name, pseudo or negation.
while true
# Element identifier.
- next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
+ next if statement.sub!(/^#(\?|[\w\-]+)/) do
id = $1
if id == "?"
id = values.shift
@@ -549,7 +549,7 @@ module HTML
end
# Class name.
- next if statement.sub!(/^\.([\w\-]+)/) do |match|
+ next if statement.sub!(/^\.([\w\-]+)/) do
class_name = $1
@source << ".#{class_name}"
class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
@@ -558,7 +558,7 @@ module HTML
end
# Attribute value.
- next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
+ next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do
name, equality, value = $1, $2, $3
if value == "?"
value = values.shift
@@ -575,7 +575,7 @@ module HTML
end
# Root element only.
- next if statement.sub!(/^:root/) do |match|
+ next if statement.sub!(/^:root/) do
pseudo << lambda do |element|
element.parent.nil? || !element.parent.tag?
end
@@ -611,7 +611,7 @@ module HTML
"" # Remove
end
# First/last child (of type).
- next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
+ next if statement.sub!(/^:(first|last)-(child|of-type)/) do
reverse = $1 == "last"
of_type = $2 == "of-type"
pseudo << nth_child(0, 1, of_type, reverse)
@@ -619,7 +619,7 @@ module HTML
"" # Remove
end
# Only child (of type).
- next if statement.sub!(/^:only-(child|of-type)/) do |match|
+ next if statement.sub!(/^:only-(child|of-type)/) do
of_type = $1 == "of-type"
pseudo << only_child(of_type)
@source << ":only-#{$1}"
@@ -628,7 +628,7 @@ module HTML
# Empty: no child elements or meaningful content (whitespaces
# are ignored).
- next if statement.sub!(/^:empty/) do |match|
+ next if statement.sub!(/^:empty/) do
pseudo << lambda do |element|
empty = true
for child in element.children
@@ -644,7 +644,7 @@ module HTML
end
# Content: match the text content of the element, stripping
# leading and trailing spaces.
- next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
+ next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do
content = $1
if content == "?"
content = values.shift