diff options
Diffstat (limited to 'actionpack')
48 files changed, 588 insertions, 306 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 8b78e0f801..f5527450c7 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,7 +1,99 @@ -* Introduce `BasicRendering` which is the most basic rendering implementation. It - allows to `render :text` and `render :nothing` without depending on Action View. +* Don't let strong parameters mutate the given hash via `fetch` - *Łukasz Strzałkowski* + Create a new instance if the given parameter is a `Hash` instead of + passing it to the `convert_hashes_to_parameters` method since it is + overriding its default value. + + *Brendon Murphy*, *Doug Cole* + +* Add `params` option to `button_to` form helper, which renders the given hash + as hidden form fields. + + *Andy Waite* + +* Make assets helpers work in the controllers like it works in the views. + + Example: + + # config/application.rb + config.asset_host = 'http://mycdn.com' + + ActionController::Base.helpers.asset_path('fallback.png') + # => http://mycdn.com/assets/fallback.png + + Fixes #10051. + + *Tima Maslyuchenko* + +* Respect `SCRIPT_NAME` when using `redirect` with a relative path + + Example: + + # application routes.rb + mount BlogEngine => '/blog' + + # engine routes.rb + get '/admin' => redirect('admin/dashboard') + + This now redirects to the path `/blog/admin/dashboard`, whereas before it would've + generated an invalid url because there would be no slash between the host name and + the path. It also allows redirects to work where the application is deployed to a + subdirectory of a website. + + Fixes #7977. + + *Andrew White* + +* Fixing repond_with working directly on the options hash + This fixes an issue where the respond_with worked directly with the given + options hash, so that if a user relied on it after calling respond_with, + the hash wouldn't be the same. + + Fixes #12029. + + *bluehotdog* + +* Fix `ActionDispatch::RemoteIp::GetIp#calculate_ip` to only check for spoofing + attacks if both `HTTP_CLIENT_IP` and `HTTP_X_FORWARDED_FOR` are set. + + Fixes #10844. + + *Tamir Duberstein* + +* Strong parameters should permit nested number as key. + + Fixes #12293. + + *kennyj* + +* Fix regex used to detect URI schemes in `redirect_to` to be consistent with + RFC 3986. + + *Derek Prior* + +* Fix incorrect `assert_redirected_to` failure message for protocol-relative + URLs. + + *Derek Prior* + +* Fix an issue where router can't recognize downcased url encoding path. + + Fixes #12269. + + *kennyj* + +* Fix custom flash type definition. Misusage of the `_flash_types` class variable + caused an error when reloading controllers with custom flash types. + + Fixes #12057. + + *Ricardo de Cillo* + +* Do not break params filtering on `nil` values. + + Fixes #12149. + + *Vasiliy Ermolovich* * Separate Action View completely from Action Pack. @@ -14,21 +106,21 @@ * Fix an issue where :if and :unless controller action procs were being run before checking for the correct action in the :only and :unless options. - Fixes #11799 + Fixes #11799. *Nicholas Jakobsen* * Fix an issue where `assert_dom_equal` and `assert_dom_not_equal` were ignoring the passed failure message argument. - Fixes #11751 + Fixes #11751. *Ryan McGeary* * Allow REMOTE_ADDR, HTTP_HOST and HTTP_USER_AGENT to be overridden from the environment passed into `ActionDispatch::TestRequest.new`. - Fixes #11590 + Fixes #11590. *Andrew White* @@ -43,7 +135,7 @@ * Skip routes pointing to a redirect or mounted application when generating urls using an options hash as they aren't relevant and generate incorrect urls. - Fixes #8018 + Fixes #8018. *Andrew White* @@ -61,7 +153,7 @@ * Fix `ActionDispatch::ParamsParser#parse_formatted_parameters` to rewind body input stream on parsing json params. - Fixes #11345 + Fixes #11345. *Yuri Bol*, *Paul Nikitochkin* @@ -94,7 +186,7 @@ was setting `request.formats` with an array containing a `nil` value, which raised an error when setting the controller formats. - Fixes #10965 + Fixes #10965. *Becker* @@ -103,7 +195,7 @@ no `:to` present in the options hash so should only affect routes using the shorthand syntax (i.e. endpoint is inferred from the path). - Fixes #9856 + Fixes #9856. *Yves Senn*, *Andrew White* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index b4315c1f57..8a85bf346a 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,10 +21,9 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version - s.add_dependency 'rack', '~> 1.5.2' - s.add_dependency 'rack-test', '~> 0.6.2' + s.add_dependency 'rack', '~> 1.5.2' + s.add_dependency 'rack-test', '~> 0.6.2' - s.add_development_dependency 'actionview', version - s.add_development_dependency 'activemodel', version - s.add_development_dependency 'tzinfo', '~> 0.3.37' + s.add_development_dependency 'actionview', version + s.add_development_dependency 'activemodel', version end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index a84ed17bd4..af5de815bb 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -114,11 +114,6 @@ module AbstractController end end - # Define some internal variables that should not be propagated to the view. - def self.default_protected_instance_vars - [] - end - abstract! # Calls the action going through the entire action dispatch stack. diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 41f19fba78..6f6079d3c5 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -13,8 +13,19 @@ module AbstractController module Rendering extend ActiveSupport::Concern - def self.default_protected_instance_vars - super.concat [:@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config] + included do + class_attribute :protected_instance_variables + self.protected_instance_variables = [] + end + + # Normalize arguments, options and then delegates render_to_body and + # sticks the result in self.response_body. + # :api: public + def render(*args, &block) + options = _normalize_render(*args, &block) + self.response_body = render_to_body(options) + _process_format(rendered_format) + self.response_body end # Raw rendering of a template to a string. @@ -29,32 +40,35 @@ module AbstractController # overridden in order to still return a string. # :api: plugin def render_to_string(*args, &block) + options = _normalize_render(*args, &block) + render_to_body(options) end - # Raw rendering of a template. - # :api: plugin - def render_to_body(options = {}) - end - - # Normalize arguments, options and then delegates render_to_body and - # sticks the result in self.response_body. + # Performs the actual template rendering. # :api: public - def render(*args, &block) + def render_to_body(options = {}) end # Return Content-Type of rendered content # :api: public def rendered_format + Mime::TEXT end + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + ) + # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns hash = {} - (instance_variables - self.class.default_protected_instance_vars).each do |name| - hash[name[1..-1]] = instance_variable_get(name) - end + variables = instance_variables + variables -= protected_instance_variables + variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES + variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) } hash end @@ -62,7 +76,11 @@ module AbstractController # render "foo/bar" to render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - options + if action.is_a? Hash + action + else + options + end end # Normalize options. @@ -76,5 +94,18 @@ module AbstractController def _process_options(options) options end + + # Process the rendered format. + # :api: private + def _process_format(format) + end + + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) + options + end end end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index d6b1908ccb..417d2efec2 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,7 +13,6 @@ module ActionController autoload :Middleware autoload_under "metal" do - autoload :BasicRendering, 'action_controller/metal/rendering' autoload :Compatibility autoload :ConditionalGet autoload :Cookies diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index a00eafc9ed..3b0d094f4f 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -3,7 +3,7 @@ require "action_controller/metal/params_wrapper" module ActionController # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>. - # Modules needes to be included in particluar order. First wee need to have <tt>AbstractController::Rendering</tt> included, + # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included, # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of @@ -14,7 +14,6 @@ module ActionController # metal = Class.new(Metal) do include AbstractController::Rendering - include ActionController::BasicRendering end # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed @@ -74,7 +73,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named "post[address][street]", the params would have included + # If the address input had been named <tt>post[address][street]</tt>, the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions @@ -261,12 +260,11 @@ module ActionController include mod end - def self.default_protected_instance_vars - super.concat [ - :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, - :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout - ] - end + # Define some internal variables that should not be propagated to the view. + self.protected_instance_variables = [ + :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, + :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout + ] ActiveSupport.run_load_hooks(:action_controller, self) end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 1d77e331f8..65351284b9 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -37,7 +37,7 @@ module ActionController #:nodoc: end helper_method type - _flash_types << type + self._flash_types += [type] end end end diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index b8afce42c9..a2cb6d1e66 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -48,7 +48,7 @@ module ActionController # You can pass any of the following options to affect the redirect status and response # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) # * <tt>flash</tt> - Set a flash message when redirecting - # * <tt>alert</tt> - Set a alert message when redirecting + # * <tt>alert</tt> - Set an alert message when redirecting # * <tt>notice</tt> - Set a notice message when redirecting # # ==== Action Options diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 8237db15ca..424473801d 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,7 +1,5 @@ module ActionController module Head - extend ActiveSupport::Concern - # Return a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. # This allows you to easily return a response that consists only of diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index b53ae7f29f..a9c3e438fb 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -73,7 +73,11 @@ module ActionController # Provides a proxy to access helpers methods from outside the view. def helpers - @helper_proxy ||= ActionView::Base.new.extend(_helpers) + @helper_proxy ||= begin + proxy = ActionView::Base.new + proxy.config = config.inheritable_copy + proxy.extend(_helpers) + end end # Overwrite modules_for_helpers to accept :all as argument, which loads diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 834d44f045..a072fce1a1 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -326,6 +326,7 @@ module ActionController #:nodoc: if collector = retrieve_collector_from_mimes(&block) options = resources.size == 1 ? {} : resources.extract_options! + options = options.clone options[:default_response] = collector.response (options.delete(:responder) || self.class.responder).call(self, resources, options) end @@ -364,9 +365,7 @@ module ActionController #:nodoc: format = collector.negotiate_format(request) if format - self.content_type ||= format.to_s - lookup_context.formats = [format.to_sym] - lookup_context.rendered_format = lookup_context.formats.first + _process_format(format) collector else raise ActionController::UnknownFormat diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index e9031f3fac..ab14a61b97 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -71,6 +71,26 @@ module ActionController self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>" end + def _compute_redirect_to_location(options) #:nodoc: + case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + # See http://tools.ietf.org/html/rfc3986#section-3.1 + # The protocol relative scheme starts with a double slash "//". + when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i + options + when String + request.protocol + request.host_with_port + options + when :back + request.headers["Referer"] or raise RedirectBackError + when Proc + _compute_redirect_to_location options.call + else + url_for(options) + end.delete("\0\r\n") + end + private def _extract_redirect_to_status(options, response_status) if options.is_a?(Hash) && options.key?(:status) @@ -81,24 +101,5 @@ module ActionController 302 end end - - def _compute_redirect_to_location(options) - case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - # The protocol relative scheme starts with a double slash "//" - when %r{\A(\w[\w+.-]*:|//).*} - options - when String - request.protocol + request.host_with_port + options - when :back - request.headers["Referer"] or raise RedirectBackError - when Proc - _compute_redirect_to_location options.call - else - url_for(options) - end.delete("\0\r\n") - end end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index abed6e53cc..62a3844b04 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -8,8 +8,7 @@ module ActionController class MissingRenderer < LoadError def initialize(format) - @format = format - super("No renderer defined for format: #{@format}") + super "No renderer defined for format: #{format}" end end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 21224b9c3b..90f0ef0b1c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,36 +1,4 @@ module ActionController - # Basic rendering implements the most minimal rendering layer. - # It only supports rendering :text and :nothing. Passing any other option will - # result in `UnsupportedOperationError` exception. For more functionality like - # different formats, layouts etc. you should use `ActionView` gem. - module BasicRendering - extend ActiveSupport::Concern - - # Render text or nothing (empty string) to response_body - # :api: public - def render(*args, &block) - super(*args, &block) - opts = args.first - if opts.has_key?(:text) && opts[:text].present? - self.response_body = opts[:text] - elsif opts.has_key?(:nothing) && opts[:nothing] - self.response_body = " " - else - raise UnsupportedOperationError - end - end - - def rendered_format - Mime::TEXT - end - - class UnsupportedOperationError < StandardError - def initialize - super "Unsupported render operation. BasicRendering supports only :text and :nothing options. For more, you need to include ActionView." - end - end - end - module Rendering extend ActiveSupport::Concern @@ -44,27 +12,35 @@ module ActionController def render(*args) #:nodoc: raise ::AbstractController::DoubleRenderError if self.response_body super - self.content_type ||= rendered_format.to_s - self.response_body end # Overwrite render_to_string because body can now be set to a rack body. def render_to_string(*) - if self.response_body = super + result = super + if result.respond_to?(:each) string = "" - self.response_body.each { |r| string << r } + result.each { |r| string << r } string + else + result end - ensure - self.response_body = nil end - def render_to_body(*) - super || " " + def render_to_body(options = {}) + super || if options[:text].present? + options[:text] + else + " " + end end private + def _process_format(format) + super + self.content_type ||= format.to_s + end + # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 573c739da4..bd64b1f812 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -124,6 +124,9 @@ module ActionController #:nodoc: @loaded = true end + # no-op + def destroy; end + def exists? true end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index ae600b1ebe..fcc76f6225 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -284,7 +284,14 @@ module ActionController # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) - convert_hashes_to_parameters(key, super) + value = super + # Don't rely on +convert_hashes_to_parameters+ + # so as to not mutate via a +fetch+ + if value.is_a?(Hash) + value = self.class.new(value) + value.permit! if permitted? + end + value rescue KeyError raise ActionController::ParameterMissing.new(key) end @@ -334,7 +341,7 @@ module ActionController def each_element(object) if object.is_a?(Array) object.map { |el| yield el }.compact - elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ } + elsif fields_for_style?(object) hash = object.class.new object.each { |k,v| hash[k] = yield v } hash @@ -343,6 +350,10 @@ module ActionController end end + def fields_for_style?(object) + object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } + end + def unpermitted_parameters!(params) unpermitted_keys = unpermitted_keys(params) if unpermitted_keys.any? @@ -421,7 +432,7 @@ module ActionController # Slicing filters out non-declared keys. slice(*filter.keys).each do |key, value| - return unless value + next unless value if filter[key] == EMPTY_ARRAY # Declaration { comment_ids: [] }. diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index d3696cbb8a..5247e61a23 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -41,7 +41,7 @@ module ActionDispatch # :nodoc: # Get and set headers for this response. attr_accessor :header - + alias_method :headers=, :header= alias_method :headers, :header @@ -181,9 +181,9 @@ module ActionDispatch # :nodoc: end alias_method :status_message, :message - def respond_to?(method) + def respond_to?(method, include_private = false) if method.to_s == 'to_path' - stream.respond_to?(:to_path) + stream.respond_to?(method) else super end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index da0cddd93c..971cb3447f 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -9,8 +9,8 @@ module ActionDispatch attr_reader :memos def initialize - @regexp_states = Hash.new { |h,k| h[k] = {} } - @string_states = Hash.new { |h,k| h[k] = {} } + @regexp_states = {} + @string_states = {} @accepting = {} @memos = Hash.new { |h,k| h[k] = [] } end @@ -111,14 +111,8 @@ module ActionDispatch end def []=(from, to, sym) - case sym - when String - @string_states[from][sym] = to - when Regexp - @regexp_states[from][sym] = to - else - raise ArgumentError, 'unknown symbol: %s' % sym.class - end + to_mappings = states_hash_for(sym)[from] ||= {} + to_mappings[sym] = to end def states @@ -137,18 +131,35 @@ module ActionDispatch private + def states_hash_for(sym) + case sym + when String + @string_states + when Regexp + @regexp_states + else + raise ArgumentError, 'unknown symbol: %s' % sym.class + end + end + def move_regexp(t, a) return [] if t.empty? t.map { |s| - @regexp_states[s].map { |re, v| re === a ? v : nil } + if states = @regexp_states[s] + states.map { |re, v| re === a ? v : nil } + end }.flatten.compact.uniq end def move_string(t, a) return [] if t.empty? - t.map { |s| @string_states[s][a] }.compact + t.map do |s| + if states = @string_states[s] + states[a] + end + end.compact end end end diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 462f1a122d..d1a004af50 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -7,15 +7,18 @@ module ActionDispatch # Normalizes URI path. # # Strips off trailing slash and ensures there is a leading slash. + # Also converts downcase url encoded string to uppercase. # # normalize_path("/foo") # => "/foo" # normalize_path("/foo/") # => "/foo" # normalize_path("foo") # => "/foo" # normalize_path("") # => "/" + # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path = "/#{path}" path.squeeze!('/') path.sub!(%r{/+\Z}, '') + path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = '/' if path == '' path end @@ -35,7 +38,7 @@ module ActionDispatch UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze end - Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI + Parser = URI::Parser.new def self.escape_path(path) Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT) diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 0a8cb1b4d4..9e66cab052 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,9 +1,12 @@ # encoding: utf-8 + +require 'thread_safe' + module ActionDispatch module Journey # :nodoc: module Visitors # :nodoc: class Visitor # :nodoc: - DISPATCH_CACHE = Hash.new { |h,k| + DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k| h[k] = :"visit_#{k}" } @@ -84,44 +87,43 @@ module ActionDispatch # Used for formatting urls (url_for) class Formatter < Visitor # :nodoc: - attr_reader :options, :consumed + attr_reader :options def initialize(options) @options = options - @consumed = {} end private - def visit_GROUP(node) - if consumed == options - nil - else - route = visit(node.left) - route.include?("\0") ? nil : route + def visit(node, optional = false) + case node.type + when :LITERAL, :SLASH, :DOT + node.left + when :STAR + visit(node.left) + when :GROUP + visit(node.left, true) + when :CAT + visit_CAT(node, optional) + when :SYMBOL + visit_SYMBOL(node) end end - def terminal(node) - node.left - end - - def binary(node) - [visit(node.left), visit(node.right)].join - end + def visit_CAT(node, optional) + left = visit(node.left, optional) + right = visit(node.right, optional) - def nary(node) - node.children.map { |c| visit(c) }.join + if optional && !(right && left) + "" + else + [left, right].join + end end def visit_SYMBOL(node) - key = node.to_sym - - if value = options[key] - consumed[key] = value + if value = options[node.to_sym] Router::Utils.escape_path(value) - else - "\0" end end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 8879291dbd..57bc6d5cd0 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -143,7 +143,7 @@ module ActionDispatch # proxies with incompatible IP header conventions, and there is no way # for us to determine which header is the right one after the fact. # Since we have no idea, we give up and explode. - should_check_ip = @check_ip && client_ips.last + should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user raise IpSpoofAttackError, "IP spoofing attack?! " + diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index fcc5bc12c4..1d4f0f89a6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -29,8 +29,11 @@ module ActionDispatch def call(env) @app.call(env) rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) + if env['action_dispatch.show_exceptions'] == false + raise exception + else + render_exception(env, exception) + end end private diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 288ce3e867..db9c993590 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -362,8 +362,9 @@ module ActionDispatch # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show) # - # Because request various HTTP verbs with a single action has security - # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers] + # Because requesting various HTTP verbs with a single action has security + # implications, you must either specify the actions in + # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers] # instead +match+ # # === Options @@ -432,10 +433,10 @@ module ActionDispatch # # match 'json_only', constraints: { format: 'json' } # - # class Blacklist + # class Whitelist # def matches?(request) request.remote_ip == '1.2.3.4' end # end - # match 'path', to: 'c#a', constraints: Blacklist.new + # match 'path', to: 'c#a', constraints: Whitelist.new # # See <tt>Scoping#constraints</tt> for more examples with its scope # equivalent. @@ -1066,18 +1067,18 @@ module ActionDispatch # a singular resource to map /profile (rather than /profile/:id) to # the show action: # - # resource :geocoder + # resource :profile # # creates six different routes in your application, all mapping to - # the +GeoCoders+ controller (note that the controller is named after + # the +Profiles+ controller (note that the controller is named after # the plural): # - # GET /geocoder/new - # POST /geocoder - # GET /geocoder - # GET /geocoder/edit - # PATCH/PUT /geocoder - # DELETE /geocoder + # GET /profile/new + # POST /profile + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile # # === Options # Takes same options as +resources+. diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 6d3f8da932..2fb03f2712 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -74,6 +74,19 @@ module ActionDispatch # * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>. # Default is <tt>:url</tt>. # + # Also includes all the options from <tt>url_for</tt>. These include such + # things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage + # is given below: + # + # polymorphic_url([blog, post], anchor: 'my_anchor') + # # => "http://example.com/blogs/1/posts/1#my_anchor" + # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") + # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" + # + # For all of these options, see the documentation for <tt>url_for</tt>. + # + # ==== Functionality + # # # an Article record # polymorphic_url(record) # same as article_url(record) # diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index d751e04e6a..3e54c7e71c 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -17,7 +17,7 @@ module ActionDispatch def call(env) req = Request.new(env) - # If any of the path parameters has a invalid encoding then + # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. req.symbolized_path_parameters.each do |key, value| unless value.valid_encoding? @@ -30,6 +30,10 @@ module ActionDispatch uri.host ||= req.host uri.port ||= req.port unless req.standard_port? + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + end + body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>) headers = { @@ -48,6 +52,11 @@ module ActionDispatch def inspect "redirect(#{status})" end + + private + def relative_path?(path) + path && !path.empty? && path[0] != '/' + end end class PathRedirect < Redirect @@ -81,6 +90,11 @@ module ActionDispatch url_options[:path] = (url_options[:path] % escape_path(params)) end + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + end + ActionDispatch::Http::URL.url_for url_options end @@ -104,6 +118,10 @@ module ActionDispatch # # get 'docs/:article', to: redirect('/wiki/%{article}') # + # Note that if you return a path without a leading slash then the url is prefixed with the + # current SCRIPT_NAME environment variable. This is typically '/' but may be different in + # a mounted engine or where the application is deployed to a subdirectory of a website. + # # Alternatively you can use one of the other syntaxes: # # The block version of redirect allows for the easy encapsulation of any logic associated with diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0e5dc1fc6c..b8abdabca5 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -28,7 +28,7 @@ module ActionDispatch def call(env) params = env[PARAMETERS_KEY] - # If any of the path parameters has a invalid encoding then + # If any of the path parameters has an invalid encoding then # raise since it's likely to trigger errors further on. params.each do |key, value| next unless value.respond_to?(:valid_encoding?) @@ -514,11 +514,12 @@ module ActionDispatch @recall = recall.dup @set = set + normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - handle_nil_action! + normalize_action! end def controller @@ -537,6 +538,11 @@ module ActionDispatch end end + # Set 'index' as default action for recall + def normalize_recall! + @recall[:action] ||= 'index' + end + def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -552,8 +558,8 @@ module ActionDispatch options[:controller] = options[:controller].to_s end - if options[:action] - options[:action] = options[:action].to_s + if options.key?(:action) + options[:action] = (options[:action] || 'index').to_s end end @@ -563,8 +569,6 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - @recall[:action] ||= 'index' if current_controller - use_recall_for(:controller) or return use_recall_for(:action) or return use_recall_for(:id) @@ -586,13 +590,11 @@ module ActionDispatch @options[:controller] = controller.sub(%r{^/}, '') if controller end - # This handles the case of action: nil being explicitly passed. - # It is identical to action: "index" - def handle_nil_action! - if options.has_key?(:action) && options[:action].nil? - options[:action] = 'index' + # Move 'index' action from options to recall + def normalize_action! + if @options[:action] == 'index' + @recall[:action] = @options.delete(:action) end - recall[:action] = options.delete(:action) if options[:action] == 'index' end # Generates a path from routes, returns [path, params]. diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 8e19025722..bcebe532bf 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -20,7 +20,7 @@ module ActionDispatch # # <%= link_to('Click here', controller: 'users', # action: 'new', message: 'Welcome!') %> - # # => "/users/new?message=Welcome%21" + # # => <a href="/users/new?message=Welcome%21">Click here</a> # # link_to, and all other functions that require URL generation functionality, # actually use ActionController::UrlFor under the hood. And in particular, diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 44ed0ac1f3..93f9fab9c2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -67,21 +67,11 @@ module ActionDispatch end def normalize_argument_to_redirection(fragment) - normalized = case fragment - when Regexp - fragment - when %r{^\w[A-Za-z\d+.-]*:.*} - fragment - when String - @request.protocol + @request.host_with_port + fragment - when :back - raise RedirectBackError unless refer = @request.headers["Referer"] - refer - else - @controller.url_for(fragment) - end - - normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized + if Regexp === fragment + fragment + else + @controller._compute_redirect_to_location(fragment) + end end end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 22a410db94..ba4cffcd3e 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -39,6 +39,8 @@ class ActionPackAssertionsController < ActionController::Base def redirect_external() redirect_to "http://www.rubyonrails.org"; end + def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end + def response404() head '404 AWOL' end def response500() head '500 Sorry' end @@ -258,6 +260,19 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end end + def test_assert_redirect_failure_message_with_protocol_relative_url + begin + process :redirect_external_protocol_relative + assert_redirected_to "/foo" + rescue ActiveSupport::TestCase::Assertion => ex + assert_no_match( + /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/, + ex.message, + 'protocol relative url was incorrectly normalized' + ) + end + end + def test_template_objects_exist process :assign_this assert !@controller.instance_variable_defined?(:"@hi") @@ -309,6 +324,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase process :redirect_external assert_equal 'http://www.rubyonrails.org', @response.redirect_url + + process :redirect_external_protocol_relative + assert_equal '//www.rubyonrails.org', @response.redirect_url end def test_no_redirect_url diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index a67dff5436..57b45b8f7b 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -20,7 +20,7 @@ class FragmentCachingMetalTest < ActionController::TestCase @controller = FragmentCachingMetalTestController.new @controller.perform_caching = true @controller.cache_store = @store - @params = { controller: 'posts', action: 'index'} + @params = { controller: 'posts', action: 'index' } @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller.params = @params @@ -40,7 +40,7 @@ class CachingController < ActionController::Base end class FragmentCachingTestController < CachingController - def some_action; end; + def some_action; end end class FragmentCachingTest < ActionController::TestCase diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index e58a1aadb8..3b5d7ef446 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -17,7 +17,7 @@ class ActionController::Base def assigns(key = nil) assigns = {} instance_variables.each do |ivar| - next if ActionController::Base.default_protected_instance_vars.include?(ivar) + next if ActionController::Base.protected_instance_variables.include?(ivar) assigns[ivar[1..-1]] = instance_variable_get(ivar) end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 3b874a739a..c64ffef654 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -214,6 +214,18 @@ class FlashTest < ActionController::TestCase get :redirect_with_foo_flash assert_equal "for great justice", @controller.send(:flash)[:foo] end + + class SubclassesTestController < TestController; end + + def test_add_flash_type_to_subclasses + TestController.add_flash_types :foo + assert SubclassesTestController._flash_types.include?(:foo) + end + + def test_do_not_add_flash_type_to_parent_class + SubclassesTestController.add_flash_types :bar + assert_not TestController._flash_types.include?(:bar) + end end class FlashIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 248c81193e..20f99f19ee 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -201,6 +201,12 @@ class HelperTest < ActiveSupport::TestCase # fun/pdf_helper.rb assert methods.include?(:foobar) end + + def test_helper_proxy_config + AllHelpersController.config.my_var = 'smth' + + assert_equal 'smth', AllHelpersController.helpers.config.my_var + end private def expected_helper_methods diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb index 76af9e3414..a70592fa1b 100644 --- a/actionpack/test/controller/mime/respond_with_test.rb +++ b/actionpack/test/controller/mime/respond_with_test.rb @@ -65,7 +65,17 @@ class RespondWithController < ActionController::Base respond_with(resource, :responder => responder) end + def respond_with_additional_params + @params = RespondWithController.params + respond_with({:result => resource}, @params) + end + protected + def self.params + { + :foo => 'bar' + } + end def resource Customer.new("david", request.delete? ? nil : 13) @@ -145,6 +155,11 @@ class RespondWithControllerTest < ActionController::TestCase Mime::Type.unregister(:mobile) end + def test_respond_with_shouldnt_modify_original_hash + get :respond_with_additional_params + assert_equal RespondWithController.params, assigns(:params) + end + def test_using_resource @request.accept = "application/xml" get :using_resource diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index f4bdd3e1d4..2b36a399bb 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -92,7 +92,7 @@ module RenderStreaming io.rewind assert_match "(undefined method `invalid!' for nil:NilClass)", io.read ensure - ActionController::Base.logger = _old + ActionView::Base.logger = _old end end diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index d6c3926a4d..2a253799f3 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -1,6 +1,15 @@ require 'abstract_unit' module RenderText + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + + def index + render text: "Hello World!" + end + end + class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] @@ -63,6 +72,12 @@ module RenderText end class RenderTextTest < Rack::TestCase + test "rendering text from a minimal controller" do + get "/render_text/minimal/index" + assert_body "Hello World!" + assert_status 200 + end + test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', :action => 'index' } diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb index 91df527dec..3b1257e8d5 100644 --- a/actionpack/test/controller/parameters/nested_parameters_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_test.rb @@ -169,4 +169,19 @@ class NestedParametersTest < ActiveSupport::TestCase assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death end + + test "nested number as key" do + params = ActionController::Parameters.new({ + product: { + properties: { + '0' => "prop0", + '1' => "prop1" + } + } + }) + params = params.require(:product).permit(:properties => ["0"]) + assert_not_nil params[:properties]["0"] + assert_nil params[:properties]["1"] + assert_equal "prop0", params[:properties]["0"] + end end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 437da43d9b..b60c5f058d 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -107,6 +107,15 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal [], permitted[:id] end + test 'do not break params filtering on nil values' do + params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil) + + permitted = params.permit(:a, c: [], b: []) + assert_equal 1, permitted[:a] + assert_equal [1, 2, 3], permitted[:b] + assert_equal nil, permitted[:c] + end + test 'key to empty array: arrays of permitted scalars pass' do [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| params = ActionController::Parameters.new(id: array) @@ -138,6 +147,12 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal :foo, e.param end + test "fetch with a default value of a hash does not mutate the object" do + params = ActionController::Parameters.new({}) + params.fetch :foo, {} + assert_equal nil, params[:foo] + end + test "fetch doesnt raise ParameterMissing exception if there is a default" do assert_equal "monkey", @params.fetch(:foo, "monkey") assert_equal "monkey", @params.fetch(:foo) { "monkey" } diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index c272e785c2..727db79241 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -78,6 +78,11 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas cookies.encrypted[:foo] = 'bar' render :nothing => true end + + def try_to_reset_session + reset_session + render :nothing => true + end end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession @@ -320,6 +325,11 @@ class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController: post :encrypted assert_response :ok end + + test 'should allow reset_session' do + post :try_to_reset_session + assert_response :ok + end end class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f735564305..46df1a7bd5 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1904,6 +1904,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get)) end + def test_downcased_unicode_path + assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界').downcase, :method => :get)) + end + private def sort_extras!(extras) if extras.length == 2 diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 113608ecf4..e519fff51e 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -31,6 +31,14 @@ module TestGenerationPrefix get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" get "/conflicting_url", :to => "inside_engine_generating#conflicting" get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test + + get "/relative_path_redirect", :to => redirect("foo") + get "/relative_option_redirect", :to => redirect(:path => "foo") + get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } + + get "/absolute_path_redirect", :to => redirect("/foo") + get "/absolute_option_redirect", :to => redirect(:path => "/foo") + get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } end routes @@ -182,6 +190,48 @@ module TestGenerationPrefix assert_equal "engine", last_response.body end + test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + # Inside Application test "[APP] generating engine's route includes prefix" do get "/generate" @@ -281,6 +331,14 @@ module TestGenerationPrefix routes = ActionDispatch::Routing::RouteSet.new routes.draw do get "/posts/:id", :to => "posts#show", :as => :post + + get "/relative_path_redirect", :to => redirect("foo") + get "/relative_option_redirect", :to => redirect(:path => "foo") + get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } + + get "/absolute_path_redirect", :to => redirect("/foo") + get "/absolute_option_redirect", :to => redirect(:path => "/foo") + get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } end routes @@ -331,5 +389,47 @@ module TestGenerationPrefix get "/posts/1" assert_equal "/posts/1", last_response.body end + + test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do + get "/relative_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do + get "/relative_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do + get "/relative_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 2fbe7358f9..4501ea095c 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -212,6 +212,11 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = nil end end + + test "respond_to? accepts include_private" do + assert_not @response.respond_to?(:method_missing) + assert @response.respond_to?(:method_missing, true) + end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index e4c3ddd3f9..3e9e90a950 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1102,6 +1102,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'projects#index', @response.body end + def test_scoped_root_as_name + draw do + scope '(:locale)', :locale => /en|pl/ do + root :to => 'projects#index', :as => 'projects' + end + end + + assert_equal '/en', projects_path(:locale => 'en') + assert_equal '/', projects_path + get '/en' + assert_equal 'projects#index', @response.body + end + def test_scope_with_format_option draw do get "direct/index", as: :no_format_direct, format: false diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb index 7104ff3730..cf2774bb5f 100644 --- a/actionpack/test/fixtures/helpers/abc_helper.rb +++ b/actionpack/test/fixtures/helpers/abc_helper.rb @@ -1,5 +1,3 @@ module AbcHelper def bare_a() end - def bare_b() end - def bare_c() end end diff --git a/actionpack/test/fixtures/helpers/helpery_test_helper.rb b/actionpack/test/fixtures/helpers/helpery_test_helper.rb deleted file mode 100644 index a4f2951efa..0000000000 --- a/actionpack/test/fixtures/helpers/helpery_test_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module HelperyTestHelper - def helpery_test - "Default" - end -end diff --git a/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index 057dc40cca..93348f4647 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -15,6 +15,14 @@ module ActionDispatch def test_uri_unescape assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d") end + + def test_normalize_path_not_greedy + assert_equal "/foo%20bar%20baz", Utils.normalize_path("/foo%20bar%20baz") + end + + def test_normalize_path_uppercase + assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz") + end end end end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 38abb16d08..08af187311 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -96,88 +96,6 @@ class Comment attr_accessor :body end -class Tag - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - attr_reader :post_id - def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end - def to_key; id ? [id] : nil end - def save; @id = 1; @post_id = 1 end - def persisted?; @id.present? end - def to_param; @id; end - def value - @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" - end - - attr_accessor :relevances - def relevances_attributes=(attributes); end - -end - -class CommentRelevance - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - attr_reader :comment_id - def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end - def to_key; id ? [id] : nil end - def save; @id = 1; @comment_id = 1 end - def persisted?; @id.present? end - def to_param; @id; end - def value - @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" - end -end - -class Sheep - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - def to_key; id ? [id] : nil end - def save; @id = 1 end - def new_record?; @id.nil? end - def name - @id.nil? ? 'new sheep' : "sheep ##{@id}" - end -end - - -class TagRelevance - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - attr_reader :tag_id - def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end - def to_key; id ? [id] : nil end - def save; @id = 1; @tag_id = 1 end - def persisted?; @id.present? end - def to_param; @id; end - def value - @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" - end -end - -class Author < Comment - attr_accessor :post - def post_attributes=(attributes); end -end - -class HashBackedAuthor < Hash - extend ActiveModel::Naming - include ActiveModel::Conversion - - def persisted?; false; end - - def name - "hash backed author" - end -end - module Blog def self.use_relative_model_naming? true @@ -193,21 +111,8 @@ module Blog end end -class ArelLike - def to_ary - true - end - def each - a = Array.new(2) { |id| Comment.new(id + 1) } - a.each { |i| yield i } - end -end - class RenderJsonTestException < Exception def to_json(options = nil) return { :error => self.class.name, :message => self.to_s }.to_json end end - -class Car < Struct.new(:color) -end |