aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md131
-rw-r--r--actionpack/actionpack.gemspec2
-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
-rw-r--r--actionpack/test/abstract/callbacks_test.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract/layouts_test.rb16
-rw-r--r--actionpack/test/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb35
-rw-r--r--actionpack/test/controller/base_test.rb12
-rw-r--r--actionpack/test/controller/flash_test.rb3
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb11
-rw-r--r--actionpack/test/controller/integration_test.rb15
-rw-r--r--actionpack/test/controller/layout_test.rb24
-rw-r--r--actionpack/test/controller/live_stream_test.rb15
-rw-r--r--actionpack/test/controller/localized_templates_test.rb9
-rw-r--r--actionpack/test/controller/new_base/render_partial_test.rb22
-rw-r--r--actionpack/test/controller/new_base/render_test.rb24
-rw-r--r--actionpack/test/controller/output_escaping_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb19
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb16
-rw-r--r--actionpack/test/controller/test_case_test.rb22
-rw-r--r--actionpack/test/dispatch/cookies_test.rb154
-rw-r--r--actionpack/test/dispatch/header_test.rb128
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb2
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb21
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb88
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb3
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb8
-rw-r--r--actionpack/test/fixtures/multipart/bracketed_utf8_param5
-rw-r--r--actionpack/test/fixtures/multipart/single_utf8_param5
-rw-r--r--actionpack/test/fixtures/public/images/rails.pngbin1787 -> 0 bytes
-rw-r--r--actionpack/test/fixtures/test/change_priority.html.erb (renamed from actionpack/test/fixtures/test/change_priorty.html.erb)0
-rw-r--r--actionpack/test/template/debug_helper_test.rb8
-rw-r--r--actionpack/test/template/form_helper_test.rb14
-rw-r--r--actionpack/test/template/form_options_helper_test.rb59
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb15
-rw-r--r--actionpack/test/template/render_test.rb2
-rw-r--r--actionpack/test/template/url_helper_test.rb25
79 files changed, 1320 insertions, 576 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 3fc3e06160..913edbd8df 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,113 @@
## Rails 4.0.0 (unreleased) ##
+* Fix explicit names on multiple file fields. If a file field tag has
+ the multiple option, it is turned into an array field (appending `[]`),
+ but if an explicit name is passed to `file_field` the `[]` is not
+ appended.
+ Fixes #9830.
+
+ *Ryan McGeary*
+
+* Add block support for the `mail_to` helper, similar to the `link_to` helper.
+
+ *Sam Pohlenz*
+
+* Automatically configure cookie-based sessions to be encrypted if
+ `secret_key_base` is set, falling back to signed if only `secret_token`
+ is set. Automatically upgrade existing signed cookie-based sessions from
+ Rails 3.x to be encrypted if both `secret_key_base` and `secret_token`
+ are set, or signed with the new key generator if only `secret_token` is
+ set. This leaves only the `config.session_store :cookie_store` option and
+ removes the two new options introduced in 4.0.0.beta1:
+ `encrypted_cookie_store` and `upgrade_signature_to_encryption_cookie_store`.
+
+ *Trevor Turk*
+
+* Ensure consistent fallback to the default layout lookup for layouts set
+ using symbols or procs that return `nil`.
+
+ All of the following layouts will result in the default layout lookup:
+
+ layout nil
+
+ layout proc { nil }
+
+ layout :returns_nil
+ def returns_nil
+ nil
+ end
+
+ Previously symbols and procs which returned `nil` resulted in no layout which
+ differed from the `layout nil` behavior. To get the "no layout" behavior just
+ return `false` instead of `nil` for `layout`.
+
+ *Chris Nicola*
+
+* Create `UpgradeLegacySignedCookieJar` to transparently upgrade existing signed
+ cookies generated by Rails 3.x to avoid invalidating them when upgrading to Rails 4.x.
+
+ *Trevor Turk + Neeraj Singh*
+
+* Raise an `ArgumentError` when a clashing named route is defined.
+
+ *Trevor Turk*
+
+* Allow default url options to accept host with protocol such as `http://`
+
+ config.action_mailer.default_url_options = { host: "http://mydomain.com" }
+
+ *Richard Schneeman*
+
+* Ensure that digest authentication responds with a 401 status when a basic
+ header is received.
+
+ *Brad Dunbar*
+
+* Include I18n locale fallbacks in view lookup.
+ Fixes GH#3512.
+
+ *Juan Barreneche*
+
+* Integration and functional tests allow headers and rack env
+ variables to be passed when performing requests.
+ Fixes #6513.
+
+ Example:
+
+ # integration test
+ get "/success", {}, "HTTP_REFERER" => "http://test.com/",
+ "Accepts" => "text/plain, text/html"
+
+ # functional test
+ @request.headers["Accepts"] = "text/plain, text/html"
+
+ *Yves Senn*
+
+* Http::Headers respects headers that are not prefixed with HTTP_
+
+ *Yves Senn*
+
+* Fix incorrectly appended square brackets to a multiple select box
+ if an explicit name has been given and it already ends with "[]"
+
+ Before:
+
+ select(:category, [], {}, multiple: true, name: "post[category][]")
+ # => <select name="post[category][][]" ...>
+
+ After:
+
+ select(:category, [], {}, multiple: true, name: "post[category][]")
+ # => <select name="post[category][]" ...>
+
+ *Olek Janiszewski*
+
+* Fixed regression when using `assert_template` to verify files sent using
+ `render file: 'README.md'`.
+ Fixes #9464.
+
+ *Justin Coyne*
+
* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in
a boolean statement.
Fixes #9360.
@@ -39,6 +147,7 @@
*Thierry Zires*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser*
@@ -231,12 +340,12 @@
Client-IP and Remote-Addr headers, in that order. Document the rationale
for that decision, and describe the options that can be passed to the
RemoteIp middleware to change it.
- Fix #7979
+ Fixes #7979.
*André Arko*, *Steve Klabnik*, *Alexey Gaziev*
* Do not append second slash to `root_url` when using `trailing_slash: true`
- Fix #8700
+ Fixes #8700.
Before:
@@ -264,7 +373,7 @@
* Do not append `charset=` parameter when `head` is called with a
`:content_type` option.
- Fix #8661.
+ Fixes #8661.
*Yves Senn*
@@ -422,7 +531,7 @@
* Render every partial with a new `ActionView::PartialRenderer`. This resolves
issues when rendering nested partials.
- Fix #8197.
+ Fixes #8197.
*Yves Senn*
@@ -430,7 +539,7 @@
of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
contains `text/plain`.
- Fix #7976.
+ Fixes #7976.
*Joost Baaij*
@@ -446,7 +555,7 @@
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.
+ Fixes #8108.
*Daniel Fox, Grant Hutchins & Trace Wax*
@@ -469,7 +578,7 @@
*Josh Peek*
* `assert_template` can be used to assert on the same template with different locals
- Fix #3675.
+ Fixes #3675.
*Yves Senn*
@@ -480,7 +589,7 @@
* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch*
* Warn when the `:locals` option is passed to `assert_template` outside of a view test case
- Fix #3415.
+ Fixes #3415.
*Yves Senn*
@@ -504,12 +613,12 @@
* Rename internal variables on `ActionController::TemplateAssertions` to prevent
naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore.
- Fix #7459.
+ Fixes #7459.
*Yves Senn*
* `resource` and `resources` don't modify the passed options hash.
- Fix #7777.
+ Fixes #7777.
*Yves Senn*
@@ -573,7 +682,7 @@
*Guillermo Iguaran*
* Log now displays the correct status code when an exception is raised.
- Fix #7646.
+ Fixes #7646.
*Yves Senn*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 03eeb841ee..cc8351a489 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
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 'tzinfo', '~> 0.3.37'
end
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
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 1090af3060..8cba049485 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -259,7 +259,7 @@ module AbstractController
end
class TestCallbacksWithArgs < ActiveSupport::TestCase
- test "callbacks still work when invoking process with multiple args" do
+ test "callbacks still work when invoking process with multiple arguments" do
controller = CallbacksWithArgs.new
controller.process(:index, " Howdy!")
assert_equal "Hello world Howdy!", controller.response_body
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index c14d24905b..5709ad0378 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -42,7 +42,7 @@ module AbstractController
end
end
- test "generated methods call custom with args received" do
+ test "generated methods call custom with arguments received" do
collector = MyCollector.new
collector.html
collector.text(:foo)
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 558a45b87f..92baad4523 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -72,13 +72,21 @@ module AbstractControllerTests
end
class WithProc < Base
- layout proc { |c| "overwrite" }
+ layout proc { "overwrite" }
def index
render :template => ActionView::Template::Text.new("Hello proc!")
end
end
+ class WithProcReturningNil < Base
+ layout proc { nil }
+
+ def index
+ render template: ActionView::Template::Text.new("Hello nil!")
+ end
+ end
+
class WithZeroArityProc < Base
layout proc { "overwrite" }
@@ -249,6 +257,12 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
+ test "when layout is specified as a proc and the proc retuns nil, don't use a layout" do
+ controller = WithProcReturningNil.new
+ controller.process(:index)
+ assert_equal "Hello nil!", controller.response_body
+ end
+
test "when layout is specified as a proc without parameters it works just the same" do
controller = WithZeroArityProc.new
controller.process(:index)
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 7157bccfb3..8213997f4e 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -332,7 +332,7 @@ end
module ActionDispatch
module RoutingVerbs
- def get(uri_or_host, path = nil, port = nil)
+ def get(uri_or_host, path = nil)
host = uri_or_host.host unless path
path ||= uri_or_host.path
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 5d727b3811..22a410db94 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -96,6 +96,14 @@ class ActionPackAssertionsController < ActionController::Base
raise "post" if request.post?
render :text => "request method: #{request.env['REQUEST_METHOD']}"
end
+
+ def render_file_absolute_path
+ render :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def render_file_relative_path
+ render :file => 'README.rdoc'
+ end
end
# Used to test that assert_response includes the exception message
@@ -142,6 +150,16 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert_tag :content => "/action_pack_assertions/flash_me"
end
+ def test_render_file_absolute_path
+ get :render_file_absolute_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
+ def test_render_file_relative_path
+ get :render_file_relative_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
def test_get_request
assert_raise(RuntimeError) { get :raise_exception_on_get }
get :raise_exception_on_post
@@ -441,6 +459,23 @@ class AssertTemplateTest < ActionController::TestCase
assert_template :partial => '_partial'
end
+ def test_file_with_absolute_path_success
+ get :render_file_absolute_path
+ assert_template :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def test_file_with_relative_path_success
+ get :render_file_relative_path
+ assert_template :file => 'README.rdoc'
+ end
+
+ def test_with_file_failure
+ get :render_file_absolute_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :file => 'test/hello_world'
+ end
+ end
+
def test_with_nil_passes_when_no_template_rendered
get :nothing
assert_template nil
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 9b42e7631f..d4f18d55a6 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -68,6 +68,12 @@ class RecordIdentifierWithoutDeprecationController < ActionController::Base
include ActionView::RecordIdentifier
end
+class ActionMissingController < ActionController::Base
+ def action_missing(action)
+ render :text => "Response for #{action}"
+ end
+end
+
class ControllerClassTests < ActiveSupport::TestCase
def test_controller_path
@@ -186,6 +192,12 @@ class PerformActionTest < ActionController::TestCase
assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
end
+
+ def test_action_missing_should_work
+ use_controller ActionMissingController
+ get :arbitrary_action
+ assert_equal "Response for arbitrary_action", @response.body
+ end
end
class UrlOptionsTest < ActionController::TestCase
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 9d4356f546..3b874a739a 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
@@ -219,7 +218,7 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+ Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
class TestController < ActionController::Base
add_flash_types :bar
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 537de7a2dd..9f1c168209 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
@@ -43,7 +42,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
end
teardown do
@@ -249,6 +248,14 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
+ test "when sent a basic auth header, returns Unauthorized" do
+ @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq='
+
+ get :display
+
+ assert_response :unauthorized
+ end
+
private
def encode_credentials(options)
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 72b882539c..c3bdf74d93 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -573,6 +573,21 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
def test_generate_url_without_controller
assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
end
+
+ def test_pass_headers
+ get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"
+
+ assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
+ assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
+ end
+
+ def test_pass_env
+ get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com"
+
+ assert_equal "http://test.com", @request.env["HTTP_HOST"]
+ assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
+ end
+
end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 71bcfd664e..34304cf640 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -94,6 +94,18 @@ class HasOwnLayoutController < LayoutTest
layout 'item'
end
+class HasNilLayoutSymbol < LayoutTest
+ layout :nilz
+
+ def nilz
+ nil
+ end
+end
+
+class HasNilLayoutProc < LayoutTest
+ layout proc { nil }
+end
+
class PrependsViewPathController < LayoutTest
def hello
prepend_view_path File.dirname(__FILE__) + '/../fixtures/layout_tests/alt/'
@@ -142,6 +154,18 @@ class LayoutSetInResponseTest < ActionController::TestCase
assert_template :layout => "layouts/item"
end
+ def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutSymbol.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
+ def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutProc.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
def test_layout_only_exception_when_included
@controller = OnlyLayoutController.new
get :hello
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 3b1a07d7af..5755444a65 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -48,6 +48,10 @@ module ActionController
end
response.stream.close
end
+
+ def with_stale
+ render :text => 'stale' if stale?(:etag => "123")
+ end
end
tests TestController
@@ -117,5 +121,16 @@ module ActionController
assert_equal 'zomg', response.body
assert response.stream.closed?, 'stream should be closed'
end
+
+ def test_stale_without_etag
+ get :with_stale
+ assert_equal 200, @response.status.to_i
+ end
+
+ def test_stale_with_etag
+ @request.if_none_match = Digest::MD5.hexdigest("123")
+ get :with_stale
+ assert_equal 304, @response.status.to_i
+ end
end
end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index bac1d02977..6b02eedaed 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -25,4 +25,13 @@ class LocalizedTemplatesTest < ActionController::TestCase
ensure
I18n.locale = old_locale
end
+
+ def test_use_fallback_locales
+ I18n.locale = :"de-AT"
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
end
diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb
index 2f1aa22208..9e5022c9f4 100644
--- a/actionpack/test/controller/new_base/render_partial_test.rb
+++ b/actionpack/test/controller/new_base/render_partial_test.rb
@@ -5,14 +5,14 @@ module RenderPartial
class BasicController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new(
- "render_partial/basic/_basic.html.erb" => "BasicPartial!",
- "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
- "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
- "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
- "render_partial/basic/_final.json.erb" => "{ final: json }",
- "render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
- "render_partial/basic/_overriden.html.erb" => "ParentPartial!",
- "render_partial/child/_overriden.html.erb" => "OverridenPartial!"
+ "render_partial/basic/_basic.html.erb" => "BasicPartial!",
+ "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
+ "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
+ "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
+ "render_partial/basic/_final.json.erb" => "{ final: json }",
+ "render_partial/basic/overridden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overridden' %><%= @test_unchanged %>",
+ "render_partial/basic/_overridden.html.erb" => "ParentPartial!",
+ "render_partial/child/_overridden.html.erb" => "OverriddenPartial!"
)]
def html_with_json_inside_json
@@ -24,7 +24,7 @@ module RenderPartial
render :action => "basic"
end
- def overriden
+ def overridden
@test_unchanged = 'hello'
end
end
@@ -55,8 +55,8 @@ module RenderPartial
end
test "partial from child controller gets picked" do
- get :overriden
- assert_response("goodbyeOverridenPartial!goodbye")
+ get :overridden
+ assert_response("goodbyeOverriddenPartial!goodbye")
end
end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index cc7f12ac6d..5635e16234 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -7,10 +7,10 @@ module Render
"render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>",
"render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>",
"render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>",
- "render/blank_render/overriden_with_own_view_paths_appended.html.erb" => "parent content",
- "render/blank_render/overriden_with_own_view_paths_prepended.html.erb" => "parent content",
- "render/blank_render/overriden.html.erb" => "parent content",
- "render/child_render/overriden.html.erb" => "child content"
+ "render/blank_render/overridden_with_own_view_paths_appended.html.erb" => "parent content",
+ "render/blank_render/overridden_with_own_view_paths_prepended.html.erb" => "parent content",
+ "render/blank_render/overridden.html.erb" => "parent content",
+ "render/child_render/overridden.html.erb" => "child content"
)]
def index
@@ -25,13 +25,13 @@ module Render
render :action => "access_action_name"
end
- def overriden_with_own_view_paths_appended
+ def overridden_with_own_view_paths_appended
end
- def overriden_with_own_view_paths_prepended
+ def overridden_with_own_view_paths_prepended
end
- def overriden
+ def overridden
end
private
@@ -49,8 +49,8 @@ module Render
end
class ChildRenderController < BlankRenderController
- append_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_appended.html.erb" => "child content")
- prepend_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_prepended.html.erb" => "child content")
+ append_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_appended.html.erb" => "child content")
+ prepend_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_prepended.html.erb" => "child content")
end
class RenderTest < Rack::TestCase
@@ -114,17 +114,17 @@ module Render
class TestViewInheritance < Rack::TestCase
test "Template from child controller gets picked over parent one" do
- get "/render/child_render/overriden"
+ get "/render/child_render/overridden"
assert_body "child content"
end
test "Template from child controller with custom view_paths prepended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_prepended"
+ get "/render/child_render/overridden_with_own_view_paths_prepended"
assert_body "child content"
end
test "Template from child controller with custom view_paths appended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_appended"
+ get "/render/child_render/overridden_with_own_view_paths_appended"
assert_body "child content"
end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
index 43a8c05cda..c3c549fbfc 100644
--- a/actionpack/test/controller/output_escaping_test.rb
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -11,8 +11,6 @@ class OutputEscapingTest < ActiveSupport::TestCase
end
test "escapeHTML shouldn't touch explicitly safe strings" do
- # TODO this seems easier to compose and reason about, but
- # this should be verified
assert_equal "<", ERB::Util.h("<".html_safe)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 93e94f0f48..f735564305 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -715,17 +715,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def setup_request_method_routes_for(method)
rs.draw do
- match '/match' => 'books#get', :via => :get
- match '/match' => 'books#post', :via => :post
- match '/match' => 'books#put', :via => :put
- match '/match' => 'books#patch', :via => :patch
- match '/match' => 'books#delete', :via => :delete
+ match '/match' => "books##{method}", :via => method.to_sym
end
end
%w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
- setup_request_method_routes_for(request_method)
+ setup_request_method_routes_for(request_method.downcase)
params = rs.recognize_path("/match", :method => request_method)
assert_equal request_method.downcase, params[:action]
end
@@ -908,12 +904,13 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal set.routes.first, set.named_routes[:hello]
end
- def test_earlier_named_routes_take_precedence
- set.draw do
- get '/hello/world' => 'a#b', :as => 'hello'
- get '/hello' => 'a#b', :as => 'hello'
+ def test_duplicate_named_route_raises_rather_than_pick_precedence
+ assert_raise ArgumentError do
+ set.draw do
+ get '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello' => 'a#b', :as => 'hello'
+ end
end
- assert_equal set.routes.first, set.named_routes[:hello]
end
def setup_named_route_test
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 888791b874..69bf4b7720 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -47,7 +47,7 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenController < ShowExceptionsController
+ class ShowExceptionsOverriddenController < ShowExceptionsController
private
def show_detailed_exceptions?
@@ -55,15 +55,15 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
+ class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
test 'show error page' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '0'}
assert_equal "500 error fixture\n", body
end
test 'show diagnostics message' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '1'}
assert_match(/boom/, body)
end
@@ -71,7 +71,7 @@ module ShowExceptions
class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
def test_render_json_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/json'
assert_response :internal_server_error
assert_equal 'application/json', response.content_type.to_s
@@ -79,7 +79,7 @@ module ShowExceptions
end
def test_render_xml_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
assert_response :internal_server_error
assert_equal 'application/xml', response.content_type.to_s
@@ -87,7 +87,7 @@ module ShowExceptions
end
def test_render_fallback_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
assert_response :internal_server_error
assert_equal 'text/html', response.content_type.to_s
@@ -96,7 +96,7 @@ module ShowExceptions
class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest
def test_render_failsafe_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
@exceptions_app = @app.instance_variable_get(:@exceptions_app)
@app.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index df31338f09..38b9794b4d 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -57,6 +57,10 @@ class TestCaseTest < ActionController::TestCase
render :text => request.protocol
end
+ def test_headers
+ render text: request.headers.env.to_json
+ end
+
def test_html_output
render :text => <<HTML
<html>
@@ -626,6 +630,24 @@ XML
assert_equal 2004, page[:year]
end
+ test "set additional HTTP headers" do
+ @request.headers['Referer'] = "http://nohost.com/home"
+ @request.headers['Content-Type'] = "application/rss+xml"
+ get :test_headers
+ parsed_env = JSON.parse(@response.body)
+ assert_equal "http://nohost.com/home", parsed_env["HTTP_REFERER"]
+ assert_equal "application/rss+xml", parsed_env["CONTENT_TYPE"]
+ end
+
+ test "set additional env variables" do
+ @request.headers['HTTP_REFERER'] = "http://example.com/about"
+ @request.headers['CONTENT_TYPE'] = "application/json"
+ get :test_headers
+ parsed_env = JSON.parse(@response.body)
+ assert_equal "http://example.com/about", parsed_env["HTTP_REFERER"]
+ assert_equal "application/json", parsed_env["CONTENT_TYPE"]
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 5ada5a7603..91ac13e7c6 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,6 +1,14 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
require 'active_support/key_generator'
+require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -67,11 +75,21 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def get_signed_cookie
+ cookies.signed[:user_id]
+ head :ok
+ end
+
def set_encrypted_cookie
cookies.encrypted[:foo] = 'bar'
head :ok
end
+ def get_encrypted_cookie
+ cookies.encrypted[:foo]
+ head :ok
+ end
+
def set_invalid_encrypted_cookie
cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf'
head :ok
@@ -330,12 +348,17 @@ class CookiesTest < ActionController::TestCase
assert response.headers["Set-Cookie"] =~ /user_name=david/
end
- def test_permanent_cookie
+ def test_set_permanent_cookie
get :set_permanent_cookie
assert_match(/Jamie/, @response.headers["Set-Cookie"])
assert_match(%r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"])
end
+ def test_read_permanent_cookie
+ get :set_permanent_cookie
+ assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
+ end
+
def test_signed_cookie
get :set_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
@@ -394,33 +417,148 @@ class CookiesTest < ActionController::TestCase
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.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.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
end
+ def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
+ end
+
+ def test_signed_or_encrypted_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "user_id=45"
+ get :get_signed_cookie
+
+ assert_equal nil, @controller.send(:cookies).signed[:user_id]
+ assert_equal nil, @response.cookies["user_id"]
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "foo=baz"
+ get :get_encrypted_cookie
+
+ assert_equal nil, @controller.send(:cookies).encrypted[:foo]
+ assert_equal nil, @response.cookies["foo"]
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
@@ -669,3 +807,5 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+end
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index 42432510c3..9e37b96951 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -1,41 +1,137 @@
-require 'abstract_unit'
+require "abstract_unit"
class HeaderTest < ActiveSupport::TestCase
- def setup
+ setup do
@headers = ActionDispatch::Http::Headers.new(
- "HTTP_CONTENT_TYPE" => "text/plain"
+ "CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"
)
end
- def test_each
+ test "#new does not normalize the data" do
+ headers = ActionDispatch::Http::Headers.new(
+ "Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com")
+
+ assert_equal({"Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com"}, headers.env)
+ end
+
+ test "#env returns the headers as env variables" do
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#each iterates through the env variables" do
headers = []
@headers.each { |pair| headers << pair }
- assert_equal [["HTTP_CONTENT_TYPE", "text/plain"]], headers
+ assert_equal [["CONTENT_TYPE", "text/plain"],
+ ["HTTP_REFERER", "/some/page"]], headers
+ end
+
+ test "set new headers" do
+ @headers["Host"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
+ end
+
+ test "headers can contain numbers" do
+ @headers["Content-MD5"] = "Q2hlY2sgSW50ZWdyaXR5IQ=="
+
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["Content-MD5"]
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["HTTP_CONTENT_MD5"]
+ end
+
+ test "set new env variables" do
+ @headers["HTTP_HOST"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
end
- def test_setter
- @headers['foo'] = "bar"
- assert_equal "bar", @headers['foo']
+ test "key?" do
+ assert @headers.key?("CONTENT_TYPE")
+ assert @headers.include?("CONTENT_TYPE")
end
- def test_key?
- assert @headers.key?('HTTP_CONTENT_TYPE')
- assert @headers.include?('HTTP_CONTENT_TYPE')
+ test "fetch with block" do
+ assert_equal "omg", @headers.fetch("notthere") { "omg" }
end
- def test_fetch_with_block
- assert_equal 'omg', @headers.fetch('notthere') { 'omg' }
+ test "accessing http header" do
+ assert_equal "/some/page", @headers["Referer"]
+ assert_equal "/some/page", @headers["referer"]
+ assert_equal "/some/page", @headers["HTTP_REFERER"]
end
- test "content type" do
+ test "accessing special header" do
assert_equal "text/plain", @headers["Content-Type"]
assert_equal "text/plain", @headers["content-type"]
assert_equal "text/plain", @headers["CONTENT_TYPE"]
- assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
end
test "fetch" do
assert_equal "text/plain", @headers.fetch("content-type", nil)
- assert_equal "not found", @headers.fetch('not-found', 'not found')
+ assert_equal "not found", @headers.fetch("not-found", "not found")
+ end
+
+ test "#merge! headers with mutation" do
+ @headers.merge!("Host" => "http://example.test",
+ "Content-Type" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.test",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#merge! env with mutation" do
+ @headers.merge!("HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "merge without mutation" do
+ combined = @headers.merge("HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, combined.env)
+
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "env variables with . are not modified" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge! "rack.input" => "",
+ "rack.request.cookie_hash" => "",
+ "action_dispatch.logger" => ""
+
+ assert_equal(["action_dispatch.logger",
+ "rack.input",
+ "rack.request.cookie_hash"], headers.env.keys.sort)
+ end
+
+ test "symbols are treated as strings" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge!(:SERVER_NAME => "example.com",
+ "HTTP_REFERER" => "/",
+ :Host => "test.com")
+ assert_equal "example.com", headers["SERVER_NAME"]
+ assert_equal "/", headers[:HTTP_REFERER]
+ assert_equal "test.com", headers["HTTP_HOST"]
+ end
+
+ test "headers directly modifies the passed environment" do
+ env = {"HTTP_REFERER" => "/"}
+ headers = ActionDispatch::Http::Headers.new(env)
+ headers['Referer'] = "http://example.com/"
+ headers.merge! "CONTENT_TYPE" => "text/plain"
+ assert_equal({"HTTP_REFERER"=>"http://example.com/",
+ "CONTENT_TYPE"=>"text/plain"}, env)
end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index e2a9ba782d..6a2eb7da9f 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -75,7 +75,7 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, Mime::Type.parse(accept)
end
- test "parse arbitarry media type parameters" do
+ test "parse arbitrary media type parameters" do
accept = 'multipart/form-data; boundary="simple boundary"'
expect = [Mime::MULTIPART_FORM]
assert_equal expect, Mime::Type.parse(accept)
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 3b008fdff0..e5e28c28be 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -21,7 +21,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
mount SprocketsApp, :at => "/sprockets"
mount SprocketsApp => "/shorthand"
- mount FakeEngine, :at => "/fakeengine"
+ mount FakeEngine, :at => "/fakeengine", :as => :fake
mount FakeEngine, :at => "/getfake", :via => :get
scope "/its_a" do
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 399f15199c..3c30a705e9 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -1,13 +1,15 @@
+# encoding: utf-8
require 'abstract_unit'
class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
class << self
- attr_accessor :last_request_parameters
+ attr_accessor :last_request_parameters, :last_parameters
end
def parse
self.class.last_request_parameters = request.request_parameters
+ self.class.last_parameters = request.parameters
head :ok
end
@@ -30,6 +32,23 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param'))
end
+ test "parse single utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ parse_multipart('single_utf8_param'), "request.request_parameters")
+ assert_equal(
+ 'Iñtërnâtiônàlizætiøn_value',
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
+ test "parse bracketed utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
+ 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
+ parse_multipart('bracketed_utf8_param'), "request.request_parameters")
+ assert_equal(
+ {'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
test "parses text file" do
params = parse_multipart('text_file')
assert_equal %w(file foo), params.keys.sort
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index e9b59f55a7..9169658c22 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -164,7 +164,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
return
end
- object.each do |k,v|
+ object.each_value do |v|
case v
when Hash
assert_utf8(v)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2bf7056ff7..29703dd5b1 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1102,6 +1102,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#index', @response.body
end
+ def test_scope_with_format_option
+ draw do
+ get "direct/index", as: :no_format_direct, format: false
+
+ scope format: false do
+ get "scoped/index", as: :no_format_scoped
+ end
+ end
+
+ assert_equal "/direct/index", no_format_direct_path
+ assert_equal "/direct/index?format=html", no_format_direct_path(format: "html")
+
+ assert_equal "/scoped/index", no_format_scoped_path
+ assert_equal "/scoped/index?format=html", no_format_scoped_path(format: "html")
+
+ get '/scoped/index'
+ assert_equal "scoped#index", @response.body
+
+ get '/scoped/index.html'
+ assert_equal "Not Found", @response.body
+ end
+
def test_index
draw do
get '/info' => 'projects#info', :as => 'info'
@@ -1112,6 +1134,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#info', @response.body
end
+ def test_match_with_many_paths_containing_a_slash
+ draw do
+ get 'get/first', 'get/second', 'get/third', :to => 'get#show'
+ end
+
+ get '/get/first'
+ assert_equal 'get#show', @response.body
+
+ get '/get/second'
+ assert_equal 'get#show', @response.body
+
+ get '/get/third'
+ assert_equal 'get#show', @response.body
+ end
+
def test_match_shorthand_with_no_scope
draw do
get 'account/overview'
@@ -1134,6 +1171,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'account#shorthand', @response.body
end
+ def test_match_shorthand_with_multiple_paths_inside_namespace
+ draw do
+ namespace :proposals do
+ put 'activate', 'inactivate'
+ end
+ end
+
+ put '/proposals/activate'
+ assert_equal 'proposals#activate', @response.body
+
+ put '/proposals/inactivate'
+ assert_equal 'proposals#inactivate', @response.body
+ end
+
def test_match_shorthand_inside_namespace_with_controller
draw do
namespace :api do
@@ -2577,22 +2628,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raises(ActionController::UrlGenerationError){ list_todo_path(:list_id => '2', :id => '1') }
end
- def test_named_routes_collision_is_avoided_unless_explicitly_given_as
- draw do
- scope :as => "routes" do
- get "/c/:id", :as => :collision, :to => "collision#show"
- get "/collision", :to => "collision#show"
- get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
- end
- end
-
- assert_equal "/c/1", routes_collision_path(1)
- assert_equal "/fc/1", routes_forced_collision_path(1)
- end
-
def test_redirect_argument_error
routes = Class.new { include ActionDispatch::Routing::Redirection }.new
assert_raises(ArgumentError) { routes.redirect Object.new }
@@ -2604,9 +2639,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/c/:id", :as => :collision, :to => "collision#show"
get "/collision", :to => "collision#show"
get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
end
end
@@ -2657,6 +2689,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_duplicate_route_name_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ get '/duplicate', :to => 'duplicate#show', :as => 'collision'
+ end
+ end
+ end
+
+ def test_duplicate_route_name_via_resources_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ resources :collisions
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ end
+ end
+ end
+
def test_nested_route_in_nested_resource
draw do
resources :posts, :only => [:index, :show] do
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index d8bf22dec8..e99ff46edf 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -1,12 +1,11 @@
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)
+ Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index e56e8ddc57..4123529092 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -48,6 +48,14 @@ module TestUrlGeneration
https!
assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
end
+
+ test "extracting protocol from host when protocol not present" do
+ assert_equal "httpz://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: nil)
+ end
+
+ test "formatting host when protocol is present" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://")
+ end
end
end
diff --git a/actionpack/test/fixtures/multipart/bracketed_utf8_param b/actionpack/test/fixtures/multipart/bracketed_utf8_param
new file mode 100644
index 0000000000..976ca44a45
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/bracketed_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/single_utf8_param b/actionpack/test/fixtures/multipart/single_utf8_param
new file mode 100644
index 0000000000..b86f62d1e1
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/single_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/public/images/rails.png b/actionpack/test/fixtures/public/images/rails.png
deleted file mode 100644
index b8441f182e..0000000000
--- a/actionpack/test/fixtures/public/images/rails.png
+++ /dev/null
Binary files differ
diff --git a/actionpack/test/fixtures/test/change_priorty.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb
index 5618977d05..5618977d05 100644
--- a/actionpack/test/fixtures/test/change_priorty.html.erb
+++ b/actionpack/test/fixtures/test/change_priority.html.erb
diff --git a/actionpack/test/template/debug_helper_test.rb b/actionpack/test/template/debug_helper_test.rb
new file mode 100644
index 0000000000..42d06bd9ff
--- /dev/null
+++ b/actionpack/test/template/debug_helper_test.rb
@@ -0,0 +1,8 @@
+require 'active_record_unit'
+
+class DebugHelperTest < ActionView::TestCase
+ def test_debug
+ company = Company.new(name: "firebase")
+ assert_match "&nbsp; name: firebase", debug(company)
+ end
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 268bab6ad2..1ff320224d 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -361,6 +361,16 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, file_field("user", "avatar")
end
+ def test_file_field_with_multiple_behavior
+ expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true)
+ end
+
+ def test_file_field_with_multiple_behavior_and_explicit_name
+ expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom")
+ end
+
def test_hidden_field
assert_dom_equal(
'<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
@@ -2791,8 +2801,8 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_html_options_adds_options_to_form_tag
- form_for(@post, html: { id: 'some_form', class: 'some_class' }) do |f| end
- expected = whole_form("/posts/123", "some_form", "some_class", method: "patch")
+ form_for(@post, html: { id: 'some_form', class: 'some_class', multipart: true }) do |f| end
+ expected = whole_form("/posts/123", "some_form", "some_class", method: "patch", multipart: "multipart/form-data")
assert_dom_equal expected, output_buffer
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 04cdd068c8..94ae8549f7 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -575,6 +575,14 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_multiple_and_with_explicit_name_ending_with_brackets
+ output_buffer = select(:post, :category, [], {include_hidden: false}, multiple: true, name: 'post[category][]')
+ assert_dom_equal(
+ "<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true)
assert_dom_equal(
@@ -1087,12 +1095,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_priority_zones_as_regexp
@firm = Firm.new("D")
- priority_zones = /A|D/
@fake_timezones.each_with_index do |tz, i|
- priority_zones.stubs(:===).with(tz).returns(i.zero? || i == 3)
+ tz.stubs(:=~).returns(i.zero? || i == 3)
end
- html = time_zone_select("firm", "time_zone", priority_zones)
+ html = time_zone_select("firm", "time_zone", /A|D/)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"D\" selected=\"selected\">D</option>" +
@@ -1104,11 +1111,32 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
+ def test_time_zone_select_with_priority_zones_as_regexp_using_grep_finds_no_zones
+ @firm = Firm.new("D")
+
+ priority_zones = /A|D/
+ @fake_timezones.each do |tz|
+ priority_zones.stubs(:===).with(tz).raises(Exception)
+ end
+
+ html = time_zone_select("firm", "time_zone", priority_zones)
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
def test_time_zone_select_with_default_time_zone_and_nil_value
@firm = Firm.new()
@firm.time_zone = nil
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\" selected=\"selected\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -1119,16 +1147,17 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_select_with_default_time_zone_and_value
- @firm = Firm.new('D')
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
+ @firm = Firm.new('D')
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
end
def test_options_for_select_with_element_attributes
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index d9b57776c9..b1c1b83807 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -200,6 +200,7 @@ class SanitizerTest < ActionController::TestCase
%(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
%(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
%(<IMG SRC=" &#14; javascript:alert('XSS');">),
+ %(<IMG SRC="javascript&#x3a;alert('XSS');">),
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
assert_sanitized img_hack, "<img>"
@@ -279,6 +280,11 @@ class SanitizerTest < ActionController::TestCase
assert_equal '', sanitize_css(raw)
end
+ def test_should_sanitize_across_newlines
+ raw = %(\nwidth:\nexpression(alert('XSS'));\n)
+ assert_equal '', sanitize_css(raw)
+ end
+
def test_should_sanitize_img_vbscript
assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
end
@@ -299,6 +305,15 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
end
+ def test_x03a
+ assert_sanitized %(<a href="javascript&#x3a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3a;//legit">), %(<a href="http://legit">)
+ assert_sanitized %(<a href="javascript&#x3A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3A;//legit">), %(<a href="http://legit">)
+ end
+
protected
def assert_sanitized(input, expected = nil)
@sanitizer ||= HTML::WhiteListSanitizer.new
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 8111e58527..2237d747be 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -61,7 +61,7 @@ module RenderTestCases
def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names
@view.lookup_context.formats = [:html]
- assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priorty")
+ assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priority")
end
def test_render_template_with_a_missing_partial_of_another_format
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 5d87c96605..9b4c419807 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -51,7 +51,6 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- # TODO: missing test cases
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com")
end
@@ -539,6 +538,22 @@ class UrlHelperTest < ActiveSupport::TestCase
assert mail_to("david@loudthinking.com").html_safe?
end
+ def test_mail_to_with_block
+ assert_dom_equal %{<a href="mailto:me@example.com"><span>Email me</span></a>},
+ mail_to('me@example.com') { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_with_block_and_options
+ assert_dom_equal %{<a class="special" href="mailto:me@example.com?cc=ccaddress%40example.com"><span>Email me</span></a>},
+ mail_to('me@example.com', cc: "ccaddress@example.com", class: "special") { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_does_not_modify_html_options_hash
+ options = { class: 'special' }
+ mail_to 'me@example.com', 'ME!', options
+ assert_equal({ class: 'special' }, options)
+ end
+
def protect_against_forgery?
self.request_forgery
end
@@ -597,7 +612,7 @@ class UrlHelperControllerTest < ActionController::TestCase
render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>"
end
- def show_overriden_url_for
+ def show_overridden_url_for
render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>"
end
@@ -634,8 +649,8 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
- def test_overriden_url_for_shows_only_path
- get :show_overriden_url_for
+ def test_overridden_url_for_shows_only_path
+ get :show_overridden_url_for
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
@@ -685,7 +700,7 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal 'ok', @response.body
end
- def test_url_helper_can_be_overriden
+ def test_url_helper_can_be_overridden
get :override_url_helper
assert_equal '/url_helper_controller_test/url_helper/override_url_helper/override', @response.body
end