diff options
Diffstat (limited to 'actionpack')
61 files changed, 623 insertions, 125 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a681a2dc79..9d50342867 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,136 @@ -## Rails 3.2.10 (unreleased) ## +## 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* + +* Fix assets loading performance in 3.2.13. + + Issue #8756 uses Sprockets for resolving files that already exist on disk, + for those files their extensions don't need to be rewritten. + + Fixes #9803. + + *Fred Wu* + +* Fix `ActionController#action_missing` not being called. + Fixes #9799. + + *Janko Luin* + +* `ActionView::Helpers::NumberHelper#number_to_human` returns the number unaltered when + the units hash does not contain the needed key, e.g. when the number provided is less + than the largest key provided. + + Examples: + + number_to_human(123, units: {}) # => 123 + number_to_human(123, units: { thousand: 'k' }) # => 123 + + Fixes #9269. + Backport #9347. + + *Michael Hoffman* + +* Include I18n locale fallbacks in view lookup. + Fixes GH#3512. + + *Juan Barreneche* + +* Fix `ActionDispatch::Request#formats` when the Accept request-header is an + empty string. Fix #7774 [Backport #8977, #9541] + + *Soylent + Maxime Réty* + + +## Rails 3.2.13 (Mar 18, 2013) ## + +* 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][]" ...> + + Backport #9616. + + *Olek Janiszewski* + +* Determine the controller#action from only the matched path when using the + shorthand syntax. Previously the complete path was used, which led + to problems with nesting (scopes and namespaces). + Fixes #7554. + Backport #9361. + + Example: + + # this will route to questions#new + scope ':locale' do + get 'questions/new' + end + + *Yves Senn* + +* Fix `assert_template` with `render :stream => true`. + Fix #1743. + Backport #5288. + + *Sergey Nartimov* + +* Eagerly populate the http method lookup cache so local project inflections do + not interfere with use of underscore method ( and we don't need locks ) + + *Aditya Sanghi* + +* `BestStandardsSupport` no longer duplicates `X-UA-Compatible` values on + each request to prevent header size from blowing up. + + *Edward Anderson* + +* Fixed JSON params parsing regression for non-object JSON content. + + *Dylan Smith* + +* Prevent unnecessary asset compilation when using `javascript_include_tag` on + files with non-standard extensions. + + *Noah Silas* + +* Fixes issue where duplicate assets can be required with sprockets. + + *Jeremy Jackson* + +* Bump `rack` dependency to 1.4.3, eliminate `Rack::File` headers deprecation warning. + + *Sam Ruby + Carlos Antonio da Silva* + +* Do not append second slash to `root_url` when using `trailing_slash: true` + + Fix #8700. + Backport #8701. + + Example: + # before + root_url # => http://test.host// + + # after + root_url # => http://test.host/ + + *Yves Senn* + +* Fix a bug in `content_tag_for` that prevents it for work without a block. + + *Jasl* * Clear url helper methods when routes are reloaded by removing the methods explicitly rather than just clearing the module because it didn't work @@ -6,24 +138,24 @@ *Andrew White* -* Fix a bug in ActionDispatch::Request#raw_post that caused env['rack.input'] +* Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']` to be read but not rewound. *Matt Venables* * More descriptive error messages when calling `render :partial` with an invalid `:layout` argument. - #8376 - render :partial => 'partial', :layout => true + Fixes #8376. + render :partial => 'partial', :layout => true # results in ActionView::MissingTemplate: Missing partial /true *Yves Senn* -* Accept symbols as #send_data :disposition value. [Backport #8329] *Elia Schito* +* Accept symbols as `#send_data` :disposition value. [Backport #8329] *Elia Schito* -* Add i18n scope to distance_of_time_in_words. [Backport #7997] *Steve Klabnik* +* Add i18n scope to `distance_of_time_in_words`. [Backport #7997] *Steve Klabnik* * Fix side effect of `url_for` changing the `:controller` string option. [Backport #6003] Before: @@ -37,7 +169,7 @@ puts controller #=> '/projects' - [Nikita Beloglazov + Andrew White] + *Nikita Beloglazov + Andrew White* * Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list of mime types where template text is not html escaped by default. It prevents `Jack & Joe` @@ -72,6 +204,22 @@ *Daniel Fox, Grant Hutchins & Trace Wax* + +## Rails 3.2.12 (Feb 11, 2013) ## + +* No changes. + + +## Rails 3.2.11 (Jan 8, 2013) ## + +* Strip nils from collections on JSON and XML posts. [CVE-2013-0155] + + +## Rails 3.2.10 (Jan 2, 2013) ## + +* No changes. + + ## Rails 3.2.9 (Nov 12, 2012) ## * Clear url helpers when reloading routes. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index ebd3c926eb..f01842575e 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 1.2') s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('rack', '~> 1.4.0') + s.add_dependency('rack', '~> 1.4.5') s.add_dependency('rack-test', '~> 0.6.1') s.add_dependency('journey', '~> 1.0.4') s.add_dependency('sprockets', '~> 2.2.1') diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index de3354d4f9..9d1ff8cbe4 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -58,7 +58,8 @@ module ActionController end def method_for_action(action_name) - super || (respond_to?(:method_missing) && "_handle_method_missing") + super || ((self.class.public_method_defined?(:method_missing) || + self.class.protected_method_defined?(:method_missing)) && "_handle_method_missing") end end end diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index b55c4643be..1ded166491 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -27,20 +27,14 @@ module ActionController self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze end - def inherited(klass) - klass.class_eval { @visible_actions = {} } - super - end - def visible_action?(action_name) - return @visible_actions[action_name] if @visible_actions.key?(action_name) - @visible_actions[action_name] = !hidden_actions.include?(action_name) + not hidden_actions.include?(action_name) end # Overrides AbstractController::Base#action_methods to remove any methods # that are listed as hidden methods. def action_methods - @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }) + @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 9abb86caf8..7630ee2926 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -77,7 +77,7 @@ module ActionController private def _extract_redirect_to_status(options, response_status) - status = if options.is_a?(Hash) && options.key?(:status) + if options.is_a?(Hash) && options.key?(:status) Rack::Utils.status_code(options.delete(:status)) elsif response_status.key?(:status) Rack::Utils.status_code(response_status[:status]) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b574a36430..e089feea87 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -20,7 +20,12 @@ module ActionController ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] - @layouts[path] += 1 + if path + @layouts[path] += 1 + if path =~ /^layouts\/(.*)/ + @layouts[$1] += 1 + end + end end ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| @@ -56,6 +61,15 @@ module ActionController # # assert that the "new" view template was rendered # assert_template "new" # + # # assert that the layout 'admin' was rendered + # assert_template :layout => 'admin' + # assert_template :layout => 'layouts/admin' + # assert_template :layout => :admin + # + # # assert that no layout was rendered + # assert_template :layout => nil + # assert_template :layout => false + # # # assert that the "_customer" partial was rendered twice # assert_template :partial => '_customer', :count => 2 # @@ -70,6 +84,8 @@ module ActionController # def assert_template(options = {}, message = nil) validate_request! + # Force body to be read in case the template is being streamed + response.body case options when NilClass, String, Symbol @@ -86,17 +102,18 @@ module ActionController end end when Hash - if expected_layout = options[:layout] + if options.key?(:layout) + expected_layout = options[:layout] msg = build_message(message, "expecting layout <?> but action rendered <?>", expected_layout, @layouts.keys) case expected_layout - when String - assert(@layouts.keys.include?(expected_layout), msg) + when String, Symbol + assert(@layouts.keys.include?(expected_layout.to_s), msg) when Regexp assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) - when nil + when nil, false assert(@layouts.empty?, msg) end end @@ -123,7 +140,7 @@ module ActionController options[:partial], @partials.keys) assert(@partials.include?(expected_partial), msg) end - else + elsif options.key?(:partial) assert @partials.empty?, "Expected no partials to be rendered" end @@ -458,7 +475,7 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? "anonymous_controller" : - @controller.class.name.underscore.sub(/_controller$/, '') + @controller.class.controller_path @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index af06bffa16..994e11563d 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -66,7 +66,7 @@ module HTML # A regular expression of the valid characters used to separate protocols like # the ':' in 'http://foo.com' - self.protocol_separator = /:|(�*58)|(p)|(%|%)3A/ + self.protocol_separator = /:|(�*58)|(p)|(�*3a)|(%|%)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)) @@ -110,8 +110,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 @@ -122,7 +122,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 @@ -171,7 +171,7 @@ module HTML def contains_bad_protocols?(attr_name, value) uri_attributes.include?(attr_name) && - (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase)) + (value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip)) end end end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 8dd1af7f3d..d5b9e55139 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -26,8 +26,6 @@ module ActionDispatch module FilterParameters extend ActiveSupport::Concern - @@parameter_filter_for = {} - # Return a hash of parameters with all sensitive data replaced. def filtered_parameters @filtered_parameters ||= parameter_filter.filter(parameters) @@ -54,7 +52,7 @@ module ActionDispatch end def parameter_filter_for(filters) - @@parameter_filter_for[filters] ||= ParameterFilter.new(filters) + ParameterFilter.new(filters) end KV_RE = '[^&;=]+' diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 5c48a60469..42f14bc1e9 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -98,8 +98,8 @@ module ActionDispatch BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ def valid_accept_header - (xhr? && (accept || content_mime_type)) || - (accept && accept !~ BROWSER_LIKE_ACCEPTS) + (xhr? && (accept.present? || content_mime_type)) || + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end def use_accept_header diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 0413346d94..31155732d2 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -56,7 +56,12 @@ module ActionDispatch RFC5789 = %w(PATCH) HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789 - HTTP_METHOD_LOOKUP = Hash.new { |h, m| h[m] = m.underscore.to_sym if HTTP_METHODS.include?(m) } + HTTP_METHOD_LOOKUP = {} + + # Populate the HTTP method lookup cache + HTTP_METHODS.each do |method| + HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym + end # Returns the HTTP \method that the application should see. # In the case where the \method was overridden by a middleware @@ -248,18 +253,14 @@ module ActionDispatch LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip } end - protected - # Remove nils from the params hash def deep_munge(hash) - keys = hash.keys.find_all { |k| hash[k] == [nil] } - keys.each { |k| hash[k] = nil } - - hash.each_value do |v| + hash.each do |k, v| case v when Array v.grep(Hash) { |x| deep_munge(x) } v.compact! + hash[k] = nil if v.empty? when Hash deep_munge(v) end @@ -268,6 +269,8 @@ module ActionDispatch hash end + protected + def parse_query(qs) deep_munge(super) end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 64459836b5..f07d5adc9b 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -43,7 +43,11 @@ module ActionDispatch params = options[:params] || {} params.reject! {|k,v| v.to_param.nil? } - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + if options[:trailing_slash] && !path.ends_with?('/') + rewritten_url << path.sub(/(\?|\z)/) { "/" + $& } + else + rewritten_url << path + end rewritten_url << "?#{params.to_query}" unless params.empty? rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] rewritten_url diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb index d338996240..94efeb79fa 100644 --- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb +++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb @@ -17,7 +17,9 @@ module ActionDispatch status, headers, body = @app.call(env) if headers["X-UA-Compatible"] && @header - headers["X-UA-Compatible"] << "," << @header.to_s + unless headers["X-UA-Compatible"][@header] + headers["X-UA-Compatible"] << "," << @header.to_s + end else headers["X-UA-Compatible"] = @header end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 6ded9dbfed..0e03d85d9a 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -38,7 +38,7 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :xml_simple, :xml_node - data = Hash.from_xml(request.body.read) || {} + data = request.deep_munge(Hash.from_xml(request.body.read) || {}) request.body.rewind if request.body.respond_to?(:rewind) data.with_indifferent_access when :yaml @@ -47,7 +47,7 @@ module ActionDispatch data = ActiveSupport::JSON.decode(request.body) request.body.rewind if request.body.respond_to?(:rewind) data = {:_json => data} unless data.is_a?(Hash) - data.with_indifferent_access + request.deep_munge(data).with_indifferent_access else false end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index c04fee21dc..cb6d98f09a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -25,6 +25,8 @@ module ActionDispatch module Compatibility def initialize(app, options = {}) options[:key] ||= '_session_id' + # FIXME Rack's secret is not being used + options[:secret] ||= SecureRandom.hex(30) super end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 29e9e6c261..80c596fd51 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -22,15 +22,12 @@ module ActionDispatch # # Session options: # - # * <tt>:secret</tt>: An application-wide key string or block returning a - # string called per generated digest. The block is called with the - # CGI::Session instance as an argument. 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. Examples: + # * <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. # # :secret => '449fe2e7daee471bffae2fd8dc02313d' - # :secret => Proc.new { User.current_user.secret_key } # # * <tt>:digest</tt>: The message digest algorithm used to verify session # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index ad11b6a211..a8d176560c 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -5,7 +5,8 @@ module ActionDispatch def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ - @file_server = ::Rack::File.new(@root, cache_control) + headers = cache_control && { 'Cache-Control' => cache_control } + @file_server = ::Rack::File.new(@root, headers) end def match?(path) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9a474d2e3a..d71b21efc3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -51,7 +51,6 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - SHORTHAND_REGEX = %r{/[\w/]+$} WILDCARD_PATH = %r{\*([^/\)]+)\)?$} def initialize(set, scope, path, options) @@ -68,14 +67,7 @@ module ActionDispatch private def normalize_options! - path_without_format = @path.sub(/\(\.:format\)$/, '') - - if using_match_shorthand?(path_without_format, @options) - to_shorthand = @options[:to].blank? - @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1') - end - - @options.merge!(default_controller_and_action(to_shorthand)) + @options.merge!(default_controller_and_action) requirements.each do |name, requirement| # segment_keys.include?(k.to_s) || k == :controller @@ -153,7 +145,7 @@ module ActionDispatch end end - def default_controller_and_action(to_shorthand=nil) + def default_controller_and_action if to.respond_to?(:call) { } else @@ -166,7 +158,7 @@ module ActionDispatch controller ||= default_controller action ||= default_action - unless controller.is_a?(Regexp) || to_shorthand + unless controller.is_a?(Regexp) controller = [@scope[:module], controller].compact.join("/").presence end @@ -1261,6 +1253,11 @@ 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]) @@ -1271,6 +1268,10 @@ module ActionDispatch self end + def using_match_shorthand?(path, options) + path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$} + end + def decomposed_match(path, options) # :nodoc: if on = options.delete(:on) send(on) { decomposed_match(path, options) } @@ -1288,9 +1289,10 @@ module ActionDispatch def add_route(action, options) # :nodoc: path = path_for_action(action, options.delete(:path)) + action = action.to_s.dup - if action.to_s =~ /^[\w\/]+$/ - options[:action] ||= action unless action.to_s.include?("/") + if action =~ /^[\w\/]+$/ + options[:action] ||= action unless action.include?("/") else action = nil end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a993699e05..6e63f92ff3 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -97,9 +97,7 @@ module ActionDispatch @routes = {} @helpers = [] - @module = Module.new do - instance_methods.each { |selector| remove_method(selector) } - end + @module = Module.new end def helper_names @@ -108,13 +106,11 @@ module ActionDispatch def clear! @helpers.each do |helper| - @module.module_eval do - remove_possible_method helper - end + @module.remove_possible_method helper end - @routes = {} - @helpers = [] + @routes.clear + @helpers.clear end def add(name, route) diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index ab0dd1d618..f319266765 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack module VERSION #:nodoc: MAJOR = 3 MINOR = 2 - TINY = 9 + TINY = 13 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 99aa144d3a..39b9a8d27c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -112,7 +112,7 @@ module ActionView # english it would read better as about 80 years. minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset / 525600) + distance_in_years = (minutes_with_offset.div 525600) if remainder < 131400 locale.t(:about_x_years, :count => distance_in_years) elsif remainder < 394200 diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index d00bad7608..0c079256a3 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -331,9 +331,9 @@ module ActionView # In many cases you will want to wrap the above in another helper, so you # could do something like the following: # - # def labelled_form_for(record_or_name_or_array, *args, &proc) + # def labelled_form_for(record_or_name_or_array, *args, &block) # options = args.extract_options! - # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc) + # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &block) # end # # If you don't need to attach a form to a model instance, then check out @@ -355,7 +355,7 @@ module ActionView # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| # ... # <% end %> - def form_for(record, options = {}, &proc) + def form_for(record, options = {}, &block) raise ArgumentError, "Missing block" unless block_given? options[:html] ||= {} @@ -374,12 +374,10 @@ module ActionView options[:html][:method] = options.delete(:method) if options.has_key?(:method) options[:html][:authenticity_token] = options.delete(:authenticity_token) - builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc) - fields_for = fields_for(object_name, object, options, &proc) + builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &block) + output = capture(builder, &block) default_options = builder.multipart? ? { :multipart => true } : {} - output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html))) - output << fields_for - output.safe_concat('</form>') + form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html))) { output } end def apply_form_for_options!(object_or_array, options) #:nodoc: @@ -1198,27 +1196,26 @@ module ActionView def add_default_name_and_id(options) if options.has_key?("index") - options["name"] ||= tag_name_with_index(options["index"]) + options["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"] ||= tag_name_with_index(@auto_index) + options["name"] ||= tag_name_with_index(@auto_index, options["multiple"]) options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= tag_name + options["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/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 623c45fa13..f63a951c33 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -508,9 +508,9 @@ module ActionView convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } } if priority_zones - if priority_zones.is_a?(Regexp) + if priority_zones.is_a?(Regexp) priority_zones = model.all.find_all {|z| z =~ priority_zones} - end + end zone_options += options_for_select(convert_zones[priority_zones], selected) zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index f0573437ca..7268a9ff9a 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -181,7 +181,7 @@ module ActionView # # => <label for="name">Name</label> # # label_tag 'name', 'Your name' - # # => <label for="name">Your Name</label> + # # => <label for="name">Your name</label> # # label_tag 'name', nil, :class => 'small_label' # # => <label for="name" class="small_label">Name</label> diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 2f372bd111..ad86d13456 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -1,5 +1,7 @@ # encoding: utf-8 +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/big_decimal/conversions' require 'active_support/core_ext/float/rounding' require 'active_support/core_ext/object/blank' @@ -358,7 +360,7 @@ module ActionView end - STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] # Formats the bytes in +number+ into a more understandable # representation (e.g., giving it 1500 yields 1.5 KB). This @@ -450,7 +452,7 @@ module ActionView end DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, - -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto} # Pretty prints (formats and approximates) a number in a way it # is more readable by humans (eg.: 1200000000 becomes "1.2 @@ -591,7 +593,7 @@ module ActionView unit = case units when Hash - units[DECIMAL_UNITS[display_exponent]] + units[DECIMAL_UNITS[display_exponent]] || '' when String, Symbol I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) else diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index aae6389445..709b5d2c17 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -98,7 +98,9 @@ module ActionView options, prefix = prefix, nil if prefix.is_a?(Hash) options = options ? options.dup : {} options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix)) - if block.arity == 0 + if !block_given? + content_tag(tag_name, "", options) + elsif block.arity == 0 content_tag(tag_name, capture(&block), options) else content_tag(tag_name, capture(record, &block), options) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 33b508e9b5..1a656ed37f 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -44,7 +44,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) { Mime::SET.symbols } register_detail(:handlers){ Template::Handlers.extensions } @@ -97,7 +103,7 @@ module ActionView # Helpers related to template lookup using the lookup context information. module ViewPaths - attr_reader :view_paths + attr_reader :view_paths, :html_fallback_for_js # Whenever setting view paths, makes a copy so we can manipulate then in # instance objects as we wish. @@ -194,7 +200,10 @@ module ActionView def formats=(values) if values values.concat(default_formats) if values.delete "*/*" - values << :html if values == [:js] + if values == [:js] + values << :html + @html_fallback_for_js = true + end end super(values) end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 0b5d3785d4..b79b89e142 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -37,5 +37,11 @@ module ActionView def instrument(name, options={}) ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } end + + def prepend_formats(formats) + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js + @lookup_context.formats = formats | @lookup_context.formats + end end end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 71fa05ab3e..f3300e470b 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -281,6 +281,8 @@ module ActionView @block = block @details = extract_details(options) + prepend_formats(options[:formats]) + if String === partial @object = options[:object] @path = partial diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index a27d5dd1b1..d15e75637a 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -10,9 +10,10 @@ module ActionView template = determine_template(options) context = @lookup_context + prepend_formats(template.formats) + unless context.rendered_format - context.formats = template.formats unless template.formats.empty? - context.rendered_format = context.formats.first + context.rendered_format = template.formats.first || formats.last end render_template(template, options[:layout], options[:locals]) diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 690c71b472..243c2e5e50 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -31,7 +31,7 @@ module Sprockets else super(source.to_s, { :src => path_to_asset(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options)) end - end.uniq.join("\n").html_safe + end.flatten.uniq.join("\n").html_safe end def stylesheet_link_tag(*sources) @@ -48,7 +48,7 @@ module Sprockets else super(source.to_s, { :href => path_to_asset(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options)) end - end.uniq.join("\n").html_safe + end.flatten.uniq.join("\n").html_safe end def asset_path(source, options = {}) @@ -157,18 +157,25 @@ module Sprockets end def rewrite_extension(source, dir, ext) - source_ext = File.extname(source) - if ext && source_ext != ".#{ext}" - if !source_ext.empty? && (asset = asset_environment[source]) && - asset.pathname.to_s =~ /#{source}\Z/ - source - else - "#{source}.#{ext}" - end - else + source_ext = File.extname(source)[1..-1] + + if !ext || ext == source_ext + source + elsif source_ext.blank? + "#{source}.#{ext}" + elsif File.exists?(source) || exact_match_present?(source) source + else + "#{source}.#{ext}" end end + + def exact_match_present?(source) + pathname = asset_environment.resolve(source) + pathname.to_s =~ /#{Regexp.escape(source)}\Z/ + rescue Sprockets::FileNotFound + false + 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 5252e43c25..3b5a515e84 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -76,6 +76,11 @@ class ActionPackAssertionsController < ActionController::Base render "test/hello_world", :layout => "layouts/standard" end + def render_with_layout_and_partial + @variable_for_layout = nil + render "test/hello_world_with_partial", :layout => "layouts/standard" + end + def session_stuffing session['xmas'] = 'turkey' render :text => "ho ho ho" @@ -483,11 +488,43 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_expecting_no_layout + get :render_with_layout + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template :layout => nil + end + end + def test_passes_with_correct_layout get :render_with_layout assert_template :layout => "layouts/standard" end + def test_passes_with_layout_and_partial + get :render_with_layout_and_partial + assert_template :layout => "layouts/standard" + end + + def test_passed_with_no_layout + get :hello_world + assert_template :layout => nil + end + + def test_passed_with_no_layout_false + get :hello_world + assert_template :layout => false + end + + def test_passes_with_correct_layout_without_layouts_prefix + get :render_with_layout + assert_template :layout => "standard" + end + + def test_passes_with_correct_layout_symbol + get :render_with_layout + assert_template :layout => :standard + end + def test_assert_template_reset_between_requests get :hello_world assert_template 'test/hello_world' diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index affa9a6add..a652d8ffad 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -86,6 +86,12 @@ end class RecordIdentifierController < ActionController::Base end +class ActionMissingController < ActionController::Base + def action_missing(action) + render :text => "Response for #{action}" + end +end + class ControllerClassTests < ActiveSupport::TestCase def test_controller_path @@ -196,6 +202,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/layout_test.rb b/actionpack/test/controller/layout_test.rb index bc171e201b..d8b04119f2 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -78,6 +78,14 @@ end class DefaultLayoutController < LayoutTest end +class StreamingLayoutController < LayoutTest + def render(*args) + options = args.extract_options! + options[:stream] = true + super(*(args << options)) + end +end + class AbsolutePathLayoutController < LayoutTest layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test') end @@ -122,6 +130,14 @@ class LayoutSetInResponseTest < ActionController::TestCase assert_template :layout => "layouts/layout_test" end + if RUBY_VERSION >= '1.9' + def test_layout_set_when_using_streaming_layout + @controller = StreamingLayoutController.new + get :hello + assert_template :hello + end + end + def test_layout_set_when_set_in_controller @controller = HasOwnLayoutController.new get :hello diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index 41ff2f3809..a5fc3f614a 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -19,4 +19,13 @@ class LocalizedTemplatesTest < ActionController::TestCase get :hello_world assert_equal "Hello World", @response.body 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
\ No newline at end of file diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 8c631e218f..3964540def 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1406,10 +1406,11 @@ class RenderTest < ActionController::TestCase end def test_locals_option_to_assert_template_is_not_supported + get :partial_collection_with_locals + warning_buffer = StringIO.new $stderr = warning_buffer - get :partial_collection_with_locals assert_template :partial => 'customer_greeting', :locals => { :greeting => 'Bonjour' } assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string ensure diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 7079a6ff90..1a1610eac7 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -58,13 +58,13 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end class MockController - def self.build(helpers) + def self.build(helpers, additional_options = {}) Class.new do - def url_for(options) + define_method :url_for do |options| options[:protocol] ||= "http" options[:host] ||= "test.host" - super(options) + super(options.merge(additional_options)) end include helpers @@ -468,8 +468,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase routes.send(:pages_url) end - def setup_for_named_route - MockController.build(rs.url_helpers).new + def setup_for_named_route(options = {}) + MockController.build(rs.url_helpers, options).new end def test_named_route_without_hash @@ -487,6 +487,16 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_trailing_slash + rs.draw do + root :to => "hello#index" + end + + routes = setup_for_named_route(:trailing_slash => true) + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("http://test.host/?foo=bar", routes.send(:root_url, :foo => :bar)) + end + def test_named_route_with_regexps rs.draw do match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index ae8588cbb0..13b6f4fc39 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -118,6 +118,19 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end + def test_post_xml_using_a_disallowed_type_attribute + $stderr = StringIO.new + with_test_route_set do + post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 + + post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 + end + ensure + $stderr = STDERR + end + def test_register_and_use_yaml with_test_route_set do with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb index 0737c40a39..551bb9621a 100644 --- a/actionpack/test/dispatch/best_standards_support_test.rb +++ b/actionpack/test/dispatch/best_standards_support_test.rb @@ -16,9 +16,10 @@ class BestStandardsSupportTest < ActiveSupport::TestCase assert_equal nil, headers["X-UA-Compatible"] end - def test_appends_to_app_headers + def test_appends_to_app_headers_without_duplication_after_multiple_requests app_headers = { "X-UA-Compatible" => "requiresActiveX=true" } _, headers, _ = app(true, app_headers).call({}) + _, headers, _ = app(true, app_headers).call({}) expects = "requiresActiveX=true,IE=Edge,chrome=1" assert_equal expects, headers["X-UA-Compatible"] diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index ad44b4b16a..4886bf13b2 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -30,6 +30,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "nils are stripped from collections" do + assert_parses( + {"person" => nil}, + "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => ['foo']}, + "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => nil}, + "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + test "logs error if parsing unsuccessful" do with_test_routing do output = StringIO.new @@ -105,6 +120,13 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "parses json with non-object JSON content" do + assert_parses( + {"user" => {"_json" => "string content" }, "_json" => "string content" }, + "\"string content\"", { 'CONTENT_TYPE' => 'application/json' } + ) + end + private def assert_parses(expected, actual, headers = {}) with_test_routing(UsersController) do diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index 0984f00066..cadafa7f38 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -30,6 +30,23 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest assert_equal "<ok>bar</ok>", resp.body end + def assert_parses(expected, xml) + with_test_routing do + post "/parse", xml, default_headers + assert_response :ok + assert_equal(expected, TestController.last_request_parameters) + end + end + + test "nils are stripped from collections" do + assert_parses( + {"hash" => { "person" => nil} }, + "<hash><person type=\"array\"><person nil=\"true\"/></person></hash>") + assert_parses( + {"hash" => { "person" => ['foo']} }, + "<hash><person type=\"array\"><person>foo</person><person nil=\"true\"/></person>\n</hash>") + end + test "parses hash params" do with_test_routing do xml = "<person><name>David</name></person>" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 56431b4daf..222cdfde1f 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -371,6 +371,27 @@ class RequestTest < ActiveSupport::TestCase assert request.put? end + test "post uneffected by local inflections" do + existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup + existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup + begin + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym "POS" + end + assert_equal "pos_t", "POST".underscore + request = stub_request "REQUEST_METHOD" => "POST" + assert_equal :post, ActionDispatch::Request::HTTP_METHOD_LOOKUP["POST"] + assert_equal :post, request.method_symbol + assert request.post? + ensure + # Reset original acronym set + ActiveSupport::Inflector.inflections do |inflect| + inflect.send(:instance_variable_set,"@acronyms",existing_acrnoyms) + inflect.send(:instance_variable_set,"@acronym_regex",existing_acrnoym_regex) + end + end + end + test "xml format" do request = stub_request request.expects(:parameters).at_least_once.returns({ :format => 'xml' }) @@ -460,6 +481,15 @@ class RequestTest < ActiveSupport::TestCase request.expects(:parameters).at_least_once.returns({}) assert_equal [ Mime::HTML ], request.formats + request = stub_request 'HTTP_ACCEPT' => '' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::HTML], request.formats + + request = stub_request 'HTTP_ACCEPT' => '', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::JS], request.formats + request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 46d16598f7..88a5c37c43 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -515,6 +515,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match '/sculptors', :to => 'italians#sculptors' match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/} end + + get 'search' => 'search' + + scope ':locale' do + match 'questions/new', :via => :get + end + + namespace :api do + namespace :v3 do + scope ':locale' do + get "products/list" + end + end + end end end @@ -1415,6 +1429,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_match_shorthand_inside_scope_with_variables_with_controller + with_test_routes do + get '/de/questions/new' + assert_equal 'questions#new', @response.body + assert_equal 'de', @request.params[:locale] + end + end + + def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller + with_test_routes do + get '/api/v3/en/products/list' + assert_equal 'api/v3/products#list', @response.body + end + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper with_test_routes do assert_equal '/replies', replies_path @@ -2477,6 +2506,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1') end + def test_action_from_path_is_not_frozen + get '/search' + assert !@request.params[:action].frozen? + end + private def with_test_routes yield diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js index e69de29bb2..e611d2b129 100644 --- a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js +++ b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js @@ -0,0 +1 @@ +//= require xmlhr diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css index e69de29bb2..2365eaa4cd 100644 --- a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css +++ b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css @@ -0,0 +1 @@ +/*= require style */ diff --git a/actionpack/test/fixtures/test/_changing_priority.html.erb b/actionpack/test/fixtures/test/_changing_priority.html.erb new file mode 100644 index 0000000000..3225efc49a --- /dev/null +++ b/actionpack/test/fixtures/test/_changing_priority.html.erb @@ -0,0 +1 @@ +HTML
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_changing_priority.json.erb b/actionpack/test/fixtures/test/_changing_priority.json.erb new file mode 100644 index 0000000000..7fa41dce66 --- /dev/null +++ b/actionpack/test/fixtures/test/_changing_priority.json.erb @@ -0,0 +1 @@ +JSON
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_first_json_partial.json.erb b/actionpack/test/fixtures/test/_first_json_partial.json.erb new file mode 100644 index 0000000000..790ee896db --- /dev/null +++ b/actionpack/test/fixtures/test/_first_json_partial.json.erb @@ -0,0 +1 @@ +<%= render :partial => "test/second_json_partial" %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_json_change_priority.json.erb b/actionpack/test/fixtures/test/_json_change_priority.json.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/test/_json_change_priority.json.erb diff --git a/actionpack/test/fixtures/test/_second_json_partial.json.erb b/actionpack/test/fixtures/test/_second_json_partial.json.erb new file mode 100644 index 0000000000..5ebb7f1afd --- /dev/null +++ b/actionpack/test/fixtures/test/_second_json_partial.json.erb @@ -0,0 +1 @@ +Third level
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/change_priority.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb new file mode 100644 index 0000000000..71ecef11c4 --- /dev/null +++ b/actionpack/test/fixtures/test/change_priority.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => "test/json_change_priority", :formats => :json %> +HTML Template, but <%= render :partial => "test/changing_priority" %> partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb new file mode 100644 index 0000000000..ec31545356 --- /dev/null +++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb @@ -0,0 +1,2 @@ +Hello world! +<%= render '/test/partial' %> diff --git a/actionpack/test/fixtures/test/html_template.html.erb b/actionpack/test/fixtures/test/html_template.html.erb new file mode 100644 index 0000000000..1b483357bd --- /dev/null +++ b/actionpack/test/fixtures/test/html_template.html.erb @@ -0,0 +1 @@ +<%= render :partial => "test/first_json_partial", :formats => :json %>
\ No newline at end of file diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index e4f84f8dd7..ad46ff4429 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -19,6 +19,8 @@ class DateHelperTest < ActionView::TestCase end def assert_distance_of_time_in_words(from, to=nil) + Fixnum.send(:private, :/) if RUBY_VERSION >= '1.9.3' # test we avoid Integer#/ (redefined by mathn) + to ||= from # 0..1 with include_seconds @@ -96,6 +98,8 @@ class DateHelperTest < ActionView::TestCase # test to < from assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, true) + ensure + Fixnum.send(:public, :/) if RUBY_VERSION >= '1.9.3' end def test_distance_in_words diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 49a325af79..22af39add4 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -301,6 +301,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" />', hidden_field("post", "title") @@ -755,7 +765,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_form_for_with_format form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f| concat f.label(:title) @@ -2217,6 +2226,19 @@ class FormHelperTest < ActionView::TestCase assert_equal "fields", output end + def test_form_for_only_instantiates_builder_once + initialization_count = 0 + builder_class = Class.new(ActionView::Helpers::FormBuilder) do + define_method :initialize do |*args| + super(*args) + initialization_count += 1 + end + end + + form_for(@post, :builder => builder_class) { } + assert_equal 1, initialization_count, 'form builder instantiated more than once' + end + protected def protect_against_forgery? false diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 3009fa5330..72c2609a48 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -515,6 +515,14 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_multiple_and_with_explicit_name_ending_with_brackets + output_buffer = select(:post, :category, "", {}, :multiple => true, :name => 'post[category][]') + assert_dom_equal( + "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><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( diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index 62ad6be680..dee60c9d00 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -176,6 +176,7 @@ class SanitizerTest < ActionController::TestCase %(<IMG SRC="jav
ascript:alert('XSS');">), %(<IMG SRC="jav
ascript:alert('XSS');">), %(<IMG SRC="  javascript:alert('XSS');">), + %(<IMG SRC="javascript: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>" @@ -210,7 +211,7 @@ class SanitizerTest < ActionController::TestCase # TODO: Clean up def test_should_sanitize_attributes - assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'><script>alert()</script>">blah</span>) + assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="#{CGI.escapeHTML "'><script>alert()</script>"}">blah</span>) end def test_should_sanitize_illegal_style_properties @@ -256,6 +257,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 @@ -276,6 +282,15 @@ class SanitizerTest < ActionController::TestCase assert_sanitized "<span class=\"\\", "<span class=\"\\\">" end + def test_x03a + assert_sanitized %(<a href="javascript:alert('XSS');">), "<a>" + assert_sanitized %(<a href="javascript:alert('XSS');">), "<a>" + assert_sanitized %(<a href="http://legit">), %(<a href="http://legit">) + assert_sanitized %(<a href="javascript:alert('XSS');">), "<a>" + assert_sanitized %(<a href="javascript:alert('XSS');">), "<a>" + assert_sanitized %(<a href="http://legit">), %(<a href="http://legit">) + end + protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 8d679aac1d..37ce3cf6b6 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -251,6 +251,11 @@ class NumberHelperTest < ActionView::TestCase assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '}) end + def test_number_to_human_with_custom_units_that_are_missing_the_needed_key + assert_equal '123', number_to_human(123, :units => {:thousand => 'k'}) + assert_equal '123', number_to_human(123, :units => {}) + end + def test_number_to_human_with_custom_format assert_equal '123 times Thousand', number_to_human(123456, :format => "%n times %u") volume = {:unit => "ml", :thousand => "lt", :million => "m3"} diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index f33471ab28..3cb5997a83 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -81,6 +81,14 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_content_tag_for_collection_without_given_block + post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true } + post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true } + expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>) + actual = content_tag_for(:li, [post_1, post_2]) + assert_dom_equal expected, actual + end + def test_div_for_collection post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true } post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true } diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index b907e3297b..72f494c811 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -54,6 +54,16 @@ module RenderTestCases assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html]) end + def test_render_partial_implicitly_use_format_of_the_rendered_partial + @view.lookup_context.formats = [:html] + assert_equal "Third level", @view.render(:template => "test/html_template") + end + + 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_priority") + end + def test_render_template_with_a_missing_partial_of_another_format @view.lookup_context.formats = [:html] assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index e944cfaee3..1ad9bcab5a 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -266,6 +266,24 @@ class SprocketsHelperTest < ActionView::TestCase javascript_include_tag('/javascripts/application') assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>}, javascript_include_tag(:application) + assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/extra-[0-9a-f]+.js\?body=1" type="text/javascript"></script>}, + javascript_include_tag(:application, :extra) + end + + test "precompiled assets with an extension when no JS runtime is available" do + @config.assets.compile = false + @config.assets.digests = {'foo.min.js' => 'foo.min-f00.js'} + Sprockets::Index.any_instance.stubs(:build_asset).raises + assert_nothing_raised { javascript_include_tag('foo.min') } + end + + test "assets that exist on filesystem don't need to go through Sprockets" do + @config.assets.digest = false + @config.assets.debug = true + + Rails.application.assets.expects(:resolve).never + + asset_paths.asset_for(FIXTURES.join("sprockets/app/javascripts/foo.min.js"), 'min') end test "stylesheet path through asset_path" do @@ -324,6 +342,9 @@ class SprocketsHelperTest < ActionView::TestCase assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />}, stylesheet_link_tag(:application) + assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/extra-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />}, + stylesheet_link_tag(:application, :extra) + assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />}, stylesheet_link_tag(:application, :media => "print") end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 56943381d8..b7f785fe3a 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -1,3 +1,4 @@ +# encoding: US-ASCII require "abstract_unit" require "logger" |