diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
17 files changed, 140 insertions, 57 deletions
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 40bb060d52..346598b6de 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -10,6 +10,8 @@ module ActionDispatch self.ignore_accept_header = false end + attr_reader :variant + # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post \format is extracted from the @@ -64,6 +66,18 @@ module ActionDispatch end end + # Sets the \variant for template. + def variant=(variant) + if variant.is_a? Symbol + @variant = variant + else + raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \ + "For security reasons, never directly set the variant to a user-provided value, " \ + "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ + "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" + end + end + # Sets the \format by string extension, which can be used to force custom formats # that are not controlled by the extension. # diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index ef144c3c76..a398919ca7 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,5 +1,6 @@ require 'set' -require 'active_support/core_ext/class/attribute_accessors' +require 'singleton' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' module Mime @@ -27,7 +28,7 @@ module Mime class << self def [](type) return type if type.is_a?(Type) - Type.lookup_by_extension(type) || NullType.new + Type.lookup_by_extension(type) || NullType.instance end def fetch(type) @@ -292,6 +293,8 @@ module Mime end class NullType + include Singleton + def nil? true end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index aba8f66118..1318c62fbe 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -271,7 +271,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {}) + @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {})) rescue TypeError => e raise ActionController::BadRequest.new(:query, e) end @@ -279,7 +279,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {}) + @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {})) rescue TypeError => e raise ActionController::BadRequest.new(:request, e) end @@ -299,17 +299,24 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - protected + # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility. + def deep_munge(hash) + ActiveSupport::Deprecation.warn( + "This method has been extracted into ActionDispatch::Request::Utils.deep_munge. Please start using that instead." + ) - def parse_query(qs) - Utils.deep_munge(super) + Utils.deep_munge(hash) end - private + protected + def parse_query(qs) + Utils.deep_munge(super) + end - def check_method(name) - HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") - name - end + private + def check_method(name) + HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") + name + end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 5247e61a23..7b2655b2d8 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'monitor' module ActionDispatch # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index 971cb3447f..5a79059ed6 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -43,9 +43,7 @@ module ActionDispatch move_string(t, a).concat(move_regexp(t, a)) end - def to_json - require 'json' - + def as_json(options = nil) simple_regexp = Hash.new { |h,k| h[k] = {} } @regexp_states.each do |from, hash| @@ -54,11 +52,11 @@ module ActionDispatch end end - JSON.dump({ + { regexp_states: simple_regexp, string_states: @string_states, accepting: @accepting - }) + } end def to_svg diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 1edf86cd88..d1a004af50 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -18,7 +18,7 @@ module ActionDispatch path = "/#{path}" path.squeeze!('/') path.sub!(%r{/+\Z}, '') - path.gsub!(/(%[a-f0-9]{2}+)/) { $1.upcase } + path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = '/' if path == '' path end diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 852f1cf6f5..baf9d5779e 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -8,14 +8,14 @@ module ActionDispatch class << self delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" - end - def self.before(*args, &block) - set_callback(:call, :before, *args, &block) - end + def before(*args, &block) + set_callback(:call, :before, *args, &block) + end - def self.after(*args, &block) - set_callback(:call, :after, *args, &block) + def after(*args, &block) + set_callback(:call, :after, *args, &block) + end end def initialize(app) diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 1de3d14530..377f05c982 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,5 @@ require 'action_controller/metal/exceptions' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module ActionDispatch class ExceptionWrapper @@ -96,7 +96,7 @@ module ActionDispatch def source_fragment(path, line) return unless Rails.respond_to?(:root) && Rails.root full_path = Rails.root.join(path) - if File.exists?(full_path) + if File.exist?(full_path) File.open(full_path, "r") do |file| start = [line - 3, 0].max lines = file.each_line.drop(start).take(6) 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/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index b9eb8036e9..11b42ee5be 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -15,8 +15,8 @@ module ActionDispatch # best possible option given your application's configuration. # # If you only have secret_token set, your cookies will be signed, but - # not encrypted. This means a user cannot alter his +user_id+ without - # knowing your app's secret key, but can easily read his +user_id+. This + # not encrypted. This means a user cannot alter their +user_id+ without + # knowing your app's secret key, but can easily read their +user_id+. This # was the default for Rails 3 apps. # # If you have secret_key_base set, your cookies will be encrypted. This 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/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 7bc812fd22..6d911a75f1 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -127,6 +127,18 @@ module ActionDispatch @delegate.delete key.to_s end + def fetch(key, default=nil) + if self.key?(key) + self[key] + elsif default + self[key] = default + elsif block_given? + self[key] = yield(key) + else + raise KeyError + end + end + def inspect if loaded? super diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index cffb814e1e..120bc54333 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -179,7 +179,8 @@ module ActionDispatch private def draw_section(routes) - name_width, verb_width, path_width = widths(routes) + header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) + name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) routes.map do |r| "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index db9c993590..846a6345cb 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -147,14 +147,16 @@ module ActionDispatch @defaults.merge!(options[:defaults]) if options[:defaults] options.each do |key, default| - next if Regexp === default || IGNORE_OPTIONS.include?(key) - @defaults[key] = default + unless Regexp === default || IGNORE_OPTIONS.include?(key) + @defaults[key] = default + end end if options[:constraints].is_a?(Hash) options[:constraints].each do |key, default| - next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default) - @defaults[key] ||= default + if URL_OPTIONS.include?(key) && (String === default || Fixnum === default) + @defaults[key] ||= default + end end end @@ -166,19 +168,21 @@ module ActionDispatch end def normalize_conditions! - @conditions.merge!(:path_info => path) + @conditions[:path_info] = path constraints.each do |key, condition| - next if segment_keys.include?(key) || key == :controller - @conditions[key] = condition + unless segment_keys.include?(key) || key == :controller + @conditions[key] = condition + end end - @conditions[:required_defaults] = [] + required_defaults = [] options.each do |key, required_default| - next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) - next if Regexp === required_default - @conditions[:required_defaults] << key + unless segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default + required_defaults << key + end end + @conditions[:required_defaults] = required_defaults via_all = options.delete(:via) if options[:via] == :all @@ -192,8 +196,7 @@ module ActionDispatch end if via = options[:via] - list = Array(via).map { |m| m.to_s.dasherize.upcase } - @conditions.merge!(:request_method => list) + @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase } end end @@ -226,11 +229,13 @@ module ActionDispatch action = action.to_s unless action.is_a?(Regexp) if controller.blank? && segment_keys.exclude?(:controller) - raise ArgumentError, "missing :controller" + message = "Missing :controller key on routes definition, please check your routes." + raise ArgumentError, message end if action.blank? && segment_keys.exclude?(:action) - raise ArgumentError, "missing :action" + message = "Missing :action key on routes definition, please check your routes." + raise ArgumentError, message end if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/ @@ -384,7 +389,7 @@ module ActionDispatch # The namespace for :controller. # # match 'path', to: 'c#a', module: 'sekret', controller: 'posts' - # #=> Sekret::PostsController + # # => Sekret::PostsController # # See <tt>Scoping#namespace</tt> for its scope equivalent. # diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 68094f129f..cbf4c5aa8b 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -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,11 +52,38 @@ module ActionDispatch def inspect "redirect(#{status})" end + + private + def relative_path?(path) + path && !path.empty? && path[0] != '/' + end + + def escape(params) + Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + end + + def escape_fragment(params) + Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] + end + + def escape_path(params) + Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }] + end end class PathRedirect < Redirect + URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/ + def path(params, request) - (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params)) + if block.match(URL_PARTS) + path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1 + query = interpolation_required?($2, params) ? $2 % escape(params) : $2 + fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3 + + "#{path}#{query}#{fragment}" + else + interpolation_required?(block, params) ? block % escape(params) : block + end end def inspect @@ -60,8 +91,8 @@ module ActionDispatch end private - def escape(params) - Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + def interpolation_required?(string, params) + !params.empty? && string && string.match(/%\{\w*\}/) end end @@ -81,17 +112,17 @@ 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 def inspect "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})" end - - private - def escape_path(params) - Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }] - end end module Redirection @@ -104,6 +135,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/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index bcebe532bf..4a0ef40873 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -155,6 +155,8 @@ module ActionDispatch _routes.url_for(options.symbolize_keys.reverse_merge!(url_options)) when String options + when Array + polymorphic_url(options, options.extract_options!) else polymorphic_url(options) end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 93f9fab9c2..68feb26936 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -27,6 +27,9 @@ module ActionDispatch assert @response.send("#{type}?"), message else code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] + if code.nil? + raise ArgumentError, "Invalid response type :#{type}" + end assert_equal code, @response.response_code, message end else |