diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
23 files changed, 491 insertions, 389 deletions
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 505d5560b1..040b51e040 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,5 +1,3 @@ -require 'active_support/memoizable' - module ActionDispatch module Http class Headers < ::Hash diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index fa2948c8db..8a9f9c4315 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -256,6 +256,10 @@ module Mime @@html_types.include?(to_sym) || @string =~ /html/ end + def respond_to?(method, include_private = false) #:nodoc: + super || method.to_s =~ /(\w+)\?$/ + end + private def method_missing(method, *args) if method.to_s =~ /(\w+)\?$/ diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b80574f497..e86dfd64d2 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -29,9 +29,9 @@ module ActionDispatch ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 - def #{env.sub(/^HTTP_/n, '').downcase} - @env["#{env}"] - end + def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset + @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"] + end # end METHOD end @@ -94,31 +94,31 @@ module ActionDispatch end # Is this a GET (or HEAD) request? - # Equivalent to <tt>request.request_method == :get</tt>. + # Equivalent to <tt>request.request_method_symbol == :get</tt>. def get? HTTP_METHOD_LOOKUP[request_method] == :get end # Is this a POST request? - # Equivalent to <tt>request.request_method == :post</tt>. + # Equivalent to <tt>request.request_method_symbol == :post</tt>. def post? HTTP_METHOD_LOOKUP[request_method] == :post end # Is this a PUT request? - # Equivalent to <tt>request.request_method == :put</tt>. + # Equivalent to <tt>request.request_method_symbol == :put</tt>. def put? HTTP_METHOD_LOOKUP[request_method] == :put end # Is this a DELETE request? - # Equivalent to <tt>request.request_method == :delete</tt>. + # Equivalent to <tt>request.request_method_symbol == :delete</tt>. def delete? HTTP_METHOD_LOOKUP[request_method] == :delete end # Is this a HEAD request? - # Equivalent to <tt>request.method == :head</tt>. + # Equivalent to <tt>request.method_symbol == :head</tt>. def head? HTTP_METHOD_LOOKUP[method] == :head end @@ -155,28 +155,21 @@ module ActionDispatch @ip ||= super end - # Which IP addresses are "trusted proxies" that can be stripped from - # the right-hand-side of X-Forwarded-For. - # - # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces. - TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^(10 | # private IP 10.x.x.x - 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 # private IP 192.168.x.x - )\. - }x - - # Determines originating IP address. REMOTE_ADDR is the standard - # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or - # HTTP_X_FORWARDED_FOR are set by proxies so check for these if - # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma- - # delimited list in the case of multiple chained proxies; the last - # address which is not trusted is the originating IP. + # Originating IP address, usually set by the RemoteIp middleware. def remote_ip @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s end + # Returns the unique request id, which is based off either the X-Request-Id header that can + # be generated by a firewall, load balancer, or web server or by the RequestId middleware + # (which sets the action_dispatch.request_id environment variable). + # + # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. + # This relies on the rack variable set by the ActionDispatch::RequestId middleware. + def uuid + @uuid ||= env["action_dispatch.request_id"] + end + # Returns the lowercase name of the HTTP server software. def server_software (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index a15ad28f16..94fa747a79 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -11,24 +11,13 @@ module ActionDispatch raise(ArgumentError, ':tempfile is required') unless @tempfile end - def open - @tempfile.open - end - - def path - @tempfile.path - end - def read(*args) @tempfile.read(*args) end - def rewind - @tempfile.rewind - end - - def size - @tempfile.size + # Delegate these methods to the tempfile. + [:open, :path, :rewind, :size].each do |method| + class_eval "def #{method}; @tempfile.#{method}; end" end private diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 8487b0fc8c..129a8b1031 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -45,7 +45,7 @@ module ActionDispatch rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) rewritten_url << "?#{params.to_query}" unless params.empty? - rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor] + rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] rewritten_url end @@ -64,14 +64,16 @@ module ActionDispatch end def host_or_subdomain_and_domain(options) - return options[:host] unless options[:subdomain] || options[:domain] + return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?) tld_length = options[:tld_length] || @@tld_length host = "" - host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)) - host << "." - host << (options[:domain] || extract_domain(options[:host], tld_length)) + unless options[:subdomain] == false + host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)) + host << "." + end + host << (options[:domain] || extract_domain(options[:host], tld_length)) host end end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 1c312f2587..51cec41a34 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -85,6 +85,7 @@ module ActionDispatch class CookieOverflow < StandardError; end class CookieJar #:nodoc: + include Enumerable # This regular expression is used to split the levels of a domain. # The top level domain can be any string without a period or @@ -124,6 +125,10 @@ module ActionDispatch alias :closed? :closed def close!; @closed = true end + def each(&block) + @cookies.each(&block) + end + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) @cookies[name.to_s] @@ -169,7 +174,7 @@ module ActionDispatch options = { :value => value } end - value = @cookies[key.to_s] = value + @cookies[key.to_s] = value handle_options(options) @@ -238,10 +243,13 @@ module ActionDispatch @delete_cookies.clear end + mattr_accessor :always_write_cookie + self.always_write_cookie = false + private def write_cookie?(cookie) - @secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development? + @secure || !cookie[:secure] || always_write_cookie end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 2adbce031b..e59404ef68 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -70,6 +70,10 @@ module ActionDispatch end end + # Implementation detail: please do not change the signature of the + # FlashHash class. Doing that will likely affect all Rails apps in + # production as the FlashHash currently stored in their sessions will + # become invalid. class FlashHash include Enumerable diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index d4208ca96e..6ded9dbfed 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -52,14 +52,9 @@ module ActionDispatch false end rescue Exception => e # YAML, XML or Ruby code block errors - logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" + logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" - raise - { "body" => request.raw_post, - "content_type" => request.content_mime_type, - "content_length" => request.content_length, - "exception" => "#{e.message} (#{e.class})", - "backtrace" => e.backtrace } + raise e end def content_type_from_legacy_post_data_format_header(env) @@ -73,8 +68,8 @@ module ActionDispatch nil end - def logger - defined?(Rails.logger) ? Rails.logger : Logger.new($stderr) + def logger(env) + env['action_dispatch.logger'] || Logger.new($stderr) end end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index c7d710b98e..66ece60860 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -2,50 +2,80 @@ module ActionDispatch class RemoteIp class IpSpoofAttackError < StandardError ; end - class RemoteIpGetter - def initialize(env, check_ip_spoofing, trusted_proxies) - @env = env - @check_ip_spoofing = check_ip_spoofing - @trusted_proxies = trusted_proxies + # IP addresses that are "trusted proxies" that can be stripped from + # the comma-delimited list in the X-Forwarded-For header. See also: + # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces + TRUSTED_PROXIES = %r{ + ^127\.0\.0\.1$ | # localhost + ^(10 | # private IP 10.x.x.x + 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 + 192\.168 # private IP 192.168.x.x + )\. + }x + + attr_reader :check_ip, :proxies + + def initialize(app, check_ip_spoofing = true, custom_proxies = nil) + @app = app + @check_ip = check_ip_spoofing + if custom_proxies + custom_regexp = Regexp.new(custom_proxies) + @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp) + else + @proxies = TRUSTED_PROXIES end + end - def remote_addrs - @remote_addrs ||= begin - list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : [] - list.reject { |addr| addr =~ @trusted_proxies } - end + def call(env) + env["action_dispatch.remote_ip"] = GetIp.new(env, self) + @app.call(env) + end + + class GetIp + def initialize(env, middleware) + @env = env + @middleware = middleware + @calculated_ip = false end - def to_s - return remote_addrs.first if remote_addrs.any? - - forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : [] - - if client_ip = @env['HTTP_CLIENT_IP'] - if @check_ip_spoofing && !forwarded_ips.include?(client_ip) - # We don't know which came from the proxy, and which from the user - raise IpSpoofAttackError, "IP spoofing attack?!" \ - "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ - "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" - end - return client_ip + # Determines originating IP address. REMOTE_ADDR is the standard + # but will be wrong if the user is behind a proxy. Proxies will set + # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. + # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of + # multiple chained proxies. The last address which is not a known proxy + # will be the originating IP. + def calculate_ip + client_ip = @env['HTTP_CLIENT_IP'] + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR') + remote_addrs = ips_from('REMOTE_ADDR') + + check_ip = client_ip && @middleware.check_ip + if check_ip && !forwarded_ips.include?(client_ip) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?!" \ + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"] + not_proxy = client_ip || forwarded_ips.last || remote_addrs.first + + # Return first REMOTE_ADDR if there are no other options + not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first end - end - def initialize(app, check_ip_spoofing = true, trusted_proxies = nil) - @app = app - @check_ip_spoofing = check_ip_spoofing - regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)' - regex << "|(#{trusted_proxies})" if trusted_proxies - @trusted_proxies = Regexp.new(regex, "i") - end + def to_s + return @ip if @calculated_ip + @calculated_ip = true + @ip = calculate_ip + end - def call(env) - env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) - @app.call(env) + protected + + def ips_from(header, allow_proxies = false) + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies } + end end + end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb new file mode 100644 index 0000000000..bee446c8a5 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -0,0 +1,39 @@ +require 'securerandom' +require 'active_support/core_ext/string/access' +require 'active_support/core_ext/object/blank' + +module ActionDispatch + # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through + # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header. + # + # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated + # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the + # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only. + # + # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files + # from multiple pieces of the stack. + class RequestId + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id + status, headers, body = @app.call(env) + + headers["X-Request-Id"] = env["action_dispatch.request_id"] + [ status, headers, body ] + end + + private + def external_request_id(env) + if request_id = env["HTTP_X_REQUEST_ID"].presence + request_id.gsub(/[^\w\-]/, "").first(255) + end + end + + def internal_request_id + SecureRandom.hex(16) + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index a70d814749..6bcf099d2c 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -59,7 +59,10 @@ module ActionDispatch # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => const_error - raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" + raise ActionDispatch::Session::SessionRestoreError, + "Session contains objects whose class definition isn't available.\n" + + "Remember to require the classes for all objects kept in the session.\n" + + "(Original exception: #{const_error.message} [#{const_error.class}])\n" end retry else diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb new file mode 100644 index 0000000000..d3b6fd12fa --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -0,0 +1,50 @@ +require 'action_dispatch/middleware/session/abstract_store' +require 'rack/session/memcache' + +module ActionDispatch + module Session + # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful + # if you don't store critical data in your sessions and you don't need them to live for extended periods + # of time. + class CacheStore < AbstractStore + # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is + # not specified, <tt>Rails.cache</tt> will be used. + def initialize(app, options = {}) + @cache = options[:cache] || Rails.cache + options[:expire_after] ||= @cache.options[:expires_in] + super + end + + # Get a session from the cache. + def get_session(env, sid) + sid ||= generate_sid + session = @cache.read(cache_key(sid)) + session ||= {} + [sid, session] + end + + # Set a session in the cache. + def set_session(env, sid, session, options) + key = cache_key(sid) + if session + @cache.write(key, session, :expires_in => options[:expire_after]) + else + @cache.delete(key) + end + sid + end + + # Remove a session from the cache. + def destroy_session(env, sid, options) + @cache.delete(cache_key(sid)) + generate_sid + end + + private + # Turn the session id into a cache key. + def cache_key(sid) + "_session_id:#{sid}" + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index a765c23dae..c850e25507 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/exception' require 'action_controller/metal/exceptions' require 'active_support/notifications' require 'action_dispatch/http/request' +require 'active_support/deprecation' module ActionDispatch # This middleware rescues any exception returned by the application and renders @@ -38,9 +39,9 @@ module ActionDispatch "application's log file and/or the web server's log file to find out what " << "went wrong.</body></html>"]] - def initialize(app, consider_all_requests_local = false) + def initialize(app, consider_all_requests_local = nil) + ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works" unless consider_all_requests_local.nil? @app = app - @consider_all_requests_local = consider_all_requests_local end def call(env) @@ -62,14 +63,13 @@ module ActionDispatch private def render_exception(env, exception) - log_error(exception) + log_error(env, exception) exception = original_exception(exception) - request = Request.new(env) - if @consider_all_requests_local || request.local? - rescue_action_locally(request, exception) + if env['action_dispatch.show_detailed_exceptions'] == true + rescue_action_diagnostics(env, exception) else - rescue_action_in_public(exception) + rescue_action_error_page(exception) end rescue Exception => failsafe_error $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" @@ -78,16 +78,16 @@ module ActionDispatch # Render detailed diagnostics for unhandled exceptions rescued from # a controller action. - def rescue_action_locally(request, exception) + def rescue_action_diagnostics(env, exception) template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], - :request => request, + :request => Request.new(env), :exception => exception, - :application_trace => application_trace(exception), - :framework_trace => framework_trace(exception), - :full_trace => full_trace(exception) + :application_trace => application_trace(env, exception), + :framework_trace => framework_trace(env, exception), + :full_trace => full_trace(env, exception) ) - file = "rescues/#{@@rescue_templates[exception.class.name]}.erb" - body = template.render(:file => file, :layout => 'rescues/layout.erb') + file = "rescues/#{@@rescue_templates[exception.class.name]}" + body = template.render(:template => file, :layout => 'rescues/layout') render(status_code(exception), body) end @@ -98,7 +98,7 @@ module ActionDispatch # it will first attempt to render the file at <tt>public/500.da.html</tt> # then attempt to render <tt>public/500.html</tt>. If none of them exist, # the body of the response will be left empty. - def rescue_action_in_public(exception) + def rescue_action_error_page(exception) status = status_code(exception) locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale path = "#{public_path}/#{status}.html" @@ -124,37 +124,41 @@ module ActionDispatch defined?(Rails.public_path) ? Rails.public_path : 'public_path' end - def log_error(exception) - return unless logger + def log_error(env, exception) + return unless logger(env) ActiveSupport::Deprecation.silence do message = "\n#{exception.class} (#{exception.message}):\n" message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << application_trace(exception).join("\n ") - logger.fatal("#{message}\n\n") + message << " " << application_trace(env, exception).join("\n ") + logger(env).fatal("#{message}\n\n") end end - def application_trace(exception) - clean_backtrace(exception, :silent) + def application_trace(env, exception) + clean_backtrace(env, exception, :silent) end - def framework_trace(exception) - clean_backtrace(exception, :noise) + def framework_trace(env, exception) + clean_backtrace(env, exception, :noise) end - def full_trace(exception) - clean_backtrace(exception, :all) + def full_trace(env, exception) + clean_backtrace(env, exception, :all) end - def clean_backtrace(exception, *args) - defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ? - Rails.backtrace_cleaner.clean(exception.backtrace, *args) : + def clean_backtrace(env, exception, *args) + env['action_dispatch.backtrace_cleaner'] ? + env['action_dispatch.backtrace_cleaner'].clean(exception.backtrace, *args) : exception.backtrace end - def logger - defined?(Rails.logger) ? Rails.logger : Logger.new($stderr) + def logger(env) + env['action_dispatch.logger'] || stderr_logger + end + + def stderr_logger + Logger.new($stderr) end def original_exception(exception) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 6e71fd7ddc..1a308707d1 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -16,6 +16,7 @@ background-color: #eee; padding: 10px; font-size: 11px; + white-space: pre-wrap; } a { color: #000; } diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index f51cc3711b..f18ebabf29 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -1,20 +1,21 @@ require "action_dispatch" -require "rails" module ActionDispatch class Railtie < Rails::Railtie config.action_dispatch = ActiveSupport::OrderedOptions.new - config.action_dispatch.x_sendfile_header = "" + config.action_dispatch.x_sendfile_header = nil config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true config.action_dispatch.best_standards_support = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true} - initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header + + config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? + ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 53374949ae..88e422c05d 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,4 +1,3 @@ -require 'erb' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/inclusion' @@ -50,7 +49,7 @@ 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/]+$} + SHORTHAND_REGEX = %r{/[\w/]+$} WILDCARD_PATH = %r{\*([^/\)]+)\)?$} def initialize(set, scope, path, options) @@ -71,7 +70,7 @@ module ActionDispatch if using_match_shorthand?(path_without_format, @options) to_shorthand = @options[:to].blank? - @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1') + @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1') end @options.merge!(default_controller_and_action(to_shorthand)) @@ -91,7 +90,7 @@ module ActionDispatch # match "account/overview" def using_match_shorthand?(path, options) - path && options.except(:via, :anchor, :to, :as).empty? && path =~ SHORTHAND_REGEX + path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX end def normalize_path(path) @@ -214,8 +213,8 @@ module ActionDispatch end def segment_keys - @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new( - Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS) + @segment_keys ||= Journey::Path::Pattern.new( + Journey::Router::Strexp.compile(@path, requirements, SEPARATORS) ).names end @@ -236,7 +235,7 @@ module ActionDispatch # (:locale) becomes (/:locale) instead of /(:locale). Except # for root cases, where the latter is the correct one. def self.normalize_path(path) - path = Rack::Mount::Utils.normalize_path(path) + path = Journey::Router::Utils.normalize_path(path) path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$} path end @@ -286,7 +285,7 @@ module ActionDispatch # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" } + # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] } # match 'photos/:id' => PhotoRackApp # # Yes, controller actions are just rack endpoints # match 'photos/:id' => PhotosController.action(:show) @@ -375,10 +374,6 @@ module ActionDispatch # # Matches any request starting with 'path' # match 'path' => 'c#a', :anchor => false def match(path, options=nil) - mapping = Mapping.new(@set, @scope, path, options || {}) - app, conditions, requirements, defaults, as, anchor = mapping.to_route - @set.add_route(app, conditions, requirements, defaults, as, anchor) - self end # Mount a Rack-based application to be used within the application. @@ -453,7 +448,9 @@ module ActionDispatch prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } - _routes.url_helpers.send("#{name}_path", prefix_options) + prefix = _routes.url_helpers.send("#{name}_path", prefix_options) + prefix = '' if prefix == '/' + prefix end end end @@ -695,7 +692,7 @@ module ActionDispatch # Allows you to constrain the nested routes based on a set of rules. # For instance, in order to change the routes to allow for a dot character in the +id+ parameter: # - # constraints(:id => /\d+\.\d+) do + # constraints(:id => /\d+\.\d+/) do # resources :posts # end # @@ -705,7 +702,7 @@ module ActionDispatch # You may use this to also restrict other parameters: # # resources :posts do - # constraints(:post_id => /\d+\.\d+) do + # constraints(:post_id => /\d+\.\d+/) do # resources :comments # end # end @@ -734,7 +731,7 @@ module ActionDispatch # if the user should be given access to that route, or +false+ if the user should not. # # class Iphone - # def self.matches(request) + # def self.matches?(request) # request.env["HTTP_USER_AGENT"] =~ /iPhone/ # end # end @@ -866,20 +863,18 @@ module ActionDispatch CANONICAL_ACTIONS = %w(index create new show update destroy) class Resource #:nodoc: - DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit] - attr_reader :controller, :path, :options def initialize(entities, options = {}) @name = entities.to_s - @path = (options.delete(:path) || @name).to_s - @controller = (options.delete(:controller) || @name).to_s - @as = options.delete(:as) + @path = (options[:path] || @name).to_s + @controller = (options[:controller] || @name).to_s + @as = options[:as] @options = options end def default_actions - self.class::DEFAULT_ACTIONS + [:index, :create, :new, :show, :update, :destroy, :edit] end def actions @@ -933,15 +928,15 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit] - def initialize(entities, options) + super @as = nil - @name = entities.to_s - @path = (options.delete(:path) || @name).to_s - @controller = (options.delete(:controller) || plural).to_s - @as = options.delete(:as) - @options = options + @controller = (options[:controller] || plural).to_s + @as = options[:as] + end + + def default_actions + [:show, :create, :update, :destroy, :new, :edit] end def plural @@ -991,7 +986,7 @@ module ActionDispatch return self end - resource_scope(SingletonResource.new(resources.pop, options)) do + resource_scope(:resource, SingletonResource.new(resources.pop, options)) do yield if block_given? collection do @@ -1023,6 +1018,7 @@ module ActionDispatch # creates seven different routes in your application, all mapping to # the +Photos+ controller: # + # GET /photos # GET /photos/new # POST /photos # GET /photos/:id @@ -1038,19 +1034,20 @@ module ActionDispatch # # This generates the following comments routes: # - # GET /photos/:id/comments/new - # POST /photos/:id/comments - # GET /photos/:id/comments/:id - # GET /photos/:id/comments/:id/edit - # PUT /photos/:id/comments/:id - # DELETE /photos/:id/comments/:id + # GET /photos/:photo_id/comments + # GET /photos/:photo_id/comments/new + # POST /photos/:photo_id/comments + # GET /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments/:id/edit + # PUT /photos/:photo_id/comments/:id + # DELETE /photos/:photo_id/comments/:id # # === Options # Takes same options as <tt>Base#match</tt> as well as: # # [:path_names] - # Allows you to change the paths of the seven default actions. - # Paths not specified are not changed. + # Allows you to change the segment component of the +edit+ and +new+ actions. + # Actions not specified are not changed. # # resources :posts, :path_names => { :new => "brand_new" } # @@ -1120,7 +1117,7 @@ module ActionDispatch return self end - resource_scope(Resource.new(resources.pop, options)) do + resource_scope(:resources, Resource.new(resources.pop, options)) do yield if block_given? collection do @@ -1243,32 +1240,44 @@ module ActionDispatch parent_resource.instance_of?(Resource) && @scope[:shallow] end - def match(*args) - options = args.extract_options!.dup - options[:anchor] = true unless options.key?(:anchor) - - if args.length > 1 - args.each { |path| match(path, options.dup) } - return self + def match(path, *rest) + if rest.empty? && Hash === path + options = path + path, to = options.find { |name, value| name.is_a?(String) } + options[:to] = to + options.delete(path) + paths = [path] + else + options = rest.pop || {} + paths = [path] + rest end - on = options.delete(:on) - if VALID_ON_OPTIONS.include?(on) - args.push(options) - return send(on){ match(*args) } - elsif on + options[:anchor] = true unless options.key?(:anchor) + + if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end - if @scope[:scope_level] == :resources - args.push(options) - return nested { match(*args) } - elsif @scope[:scope_level] == :resource - args.push(options) - return member { match(*args) } + paths.each { |_path| decomposed_match(_path, options.dup) } + self + end + + def decomposed_match(path, options) # :nodoc: + if on = options.delete(:on) + send(on) { decomposed_match(path, options) } + else + case @scope[:scope_level] + when :resources + nested { decomposed_match(path, options) } + when :resource + member { decomposed_match(path, options) } + else + add_route(path, options) + end end + end - action = args.first + def add_route(action, options) # :nodoc: path = path_for_action(action, options.delete(:path)) if action.to_s =~ /^[\w\/]+$/ @@ -1277,13 +1286,15 @@ module ActionDispatch action = nil end - if options.key?(:as) && !options[:as] + if !options.fetch(:as) { true } options.delete(:as) else options[:as] = name_for_action(options[:as], action) end - super(path, options) + mapping = Mapping.new(@set, @scope, path, options) + app, conditions, requirements, defaults, as, anchor = mapping.to_route + @set.add_route(app, conditions, requirements, defaults, as, anchor) end def root(options={}) @@ -1339,7 +1350,7 @@ module ActionDispatch end def scope_action_options? #:nodoc: - @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except]) + @scope[:options] && (@scope[:options][:only] || @scope[:options][:except]) end def scope_action_options #:nodoc: @@ -1347,11 +1358,11 @@ module ActionDispatch end def resource_scope? #:nodoc: - @scope[:scope_level].in?([:resource, :resources]) + [:resource, :resources].include? @scope[:scope_level] end def resource_method_scope? #:nodoc: - @scope[:scope_level].in?([:collection, :member, :new]) + [:collection, :member, :new].include? @scope[:scope_level] end def with_exclusive_scope @@ -1376,8 +1387,8 @@ module ActionDispatch @scope[:scope_level_resource] = old_resource end - def resource_scope(resource) #:nodoc: - with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do + def resource_scope(kind, resource) #:nodoc: + with_scope_level(kind, resource) do scope(parent_resource.resource_scope) do yield end @@ -1385,10 +1396,12 @@ module ActionDispatch end def nested_options #:nodoc: - {}.tap do |options| - options[:as] = parent_resource.member_name - options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint? - end + options = { :as => parent_resource.member_name } + options[:constraints] = { + :"#{parent_resource.singular}_id" => id_constraint + } if id_constraint? + + options end def id_constraint? #:nodoc: @@ -1438,7 +1451,7 @@ module ActionDispatch name_prefix = @scope[:as] if parent_resource - return nil if as.nil? && action.nil? + return nil unless as || action collection_name = parent_resource.collection_name member_name = parent_resource.member_name @@ -1464,19 +1477,6 @@ module ActionDispatch end end - module Shorthand #:nodoc: - def match(*args) - if args.size == 1 && args.last.is_a?(Hash) - options = args.pop - path, to = options.find { |name, value| name.is_a?(String) } - options.merge!(:to => to).delete(path) - super(path, options) - else - super - end - end - end - def initialize(set) #:nodoc: @set = set @scope = { :path_names => @set.resources_path_names } @@ -1487,7 +1487,6 @@ module ActionDispatch include Redirection include Scoping include Resources - include Shorthand end end end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 804991ad5f..330400e139 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -2,6 +2,54 @@ require 'action_dispatch/http/request' module ActionDispatch module Routing + class Redirect # :nodoc: + attr_reader :status, :block + + def initialize(status, block) + @status = status + @block = block + end + + def call(env) + req = Request.new(env) + + uri = URI.parse(path(req.symbolized_path_parameters, req)) + uri.scheme ||= req.scheme + uri.host ||= req.host + uri.port ||= req.port unless req.standard_port? + + body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>) + + headers = { + 'Location' => uri.to_s, + 'Content-Type' => 'text/html', + 'Content-Length' => body.length.to_s + } + + [ status, headers, [body] ] + end + + def path(params, request) + block.call params, request + end + end + + class OptionRedirect < Redirect # :nodoc: + alias :options :block + + def path(params, request) + url_options = { + :protocol => request.protocol, + :host => request.host, + :port => request.optional_port, + :path => request.path, + :params => request.query_parameters + }.merge options + + ActionDispatch::Http::URL.url_for url_options + end + end + module Redirection # Redirect any path to another path: @@ -40,71 +88,27 @@ module ActionDispatch options = args.last.is_a?(Hash) ? args.pop : {} status = options.delete(:status) || 301 + return OptionRedirect.new(status, options) if options.any? + path = args.shift - path_proc = if path.is_a?(String) - proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) } - elsif options.any? - options_proc(options) - elsif path.respond_to?(:call) - proc { |params, request| path.call(params, request) } - elsif block - block - else - raise ArgumentError, "redirection argument not supported" - end + block = lambda { |params, request| + (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) + } if String === path - redirection_proc(status, path_proc) - end + block = path if path.respond_to? :call - private - - def options_proc(options) - proc do |params, request| - path = if options[:path].nil? - request.path - elsif params.empty? || !options[:path].match(/%\{\w*\}/) - options.delete(:path) - else - (options.delete(:path) % params) - end - - default_options = { - :protocol => request.protocol, - :host => request.host, - :port => request.optional_port, - :path => path, - :params => request.query_parameters - } - - ActionDispatch::Http::URL.url_for(options.reverse_merge(default_options)) - end + # :FIXME: remove in Rails 4.0 + if block && block.respond_to?(:arity) && block.arity < 2 + msg = "redirect blocks with arity of #{block.arity} are deprecated. Your block must take 2 parameters: the environment, and a request object" + ActiveSupport::Deprecation.warn msg + block = lambda { |params, _| block.call(params) } end - def redirection_proc(status, path_proc) - lambda do |env| - req = Request.new(env) - - params = [req.symbolized_path_parameters] - params << req if path_proc.arity > 1 - - uri = URI.parse(path_proc.call(*params)) - uri.scheme ||= req.scheme - uri.host ||= req.host - uri.port ||= req.port unless req.standard_port? - - body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>) - - headers = { - 'Location' => uri.to_s, - 'Content-Type' => 'text/html', - 'Content-Length' => body.length.to_s - } - - [ status, headers, [body] ] - end - end + raise ArgumentError, "redirection argument not supported" unless block + Redirect.new status, block + end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb deleted file mode 100644 index 10b3d38346..0000000000 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ /dev/null @@ -1,60 +0,0 @@ -module ActionDispatch - module Routing - class Route #:nodoc: - attr_reader :app, :conditions, :defaults, :name - attr_reader :path, :requirements, :set - - def initialize(set, app, conditions, requirements, defaults, name, anchor) - @set = set - @app = app - @defaults = defaults - @name = name - - # FIXME: we should not be doing this much work in a constructor. - - @requirements = requirements.merge(defaults) - @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp) - @requirements.delete_if { |k, v| - v == Regexp.compile("[^#{SEPARATORS.join}]+") - } - - if path = conditions[:path_info] - @path = path - conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor) - end - - @verbs = conditions[:request_method] || [] - - @conditions = conditions.dup - - # Rack-Mount requires that :request_method be a regular expression. - # :request_method represents the HTTP verb that matches this route. - # - # Here we munge values before they get sent on to rack-mount. - @conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty? - @conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info] - @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) } - @requirements.delete_if{ |k,v| !valid_condition?(k) } - end - - def verb - @verbs.join '|' - end - - def segment_keys - @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym } - end - - def to_s - @to_s ||= begin - "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect] - end - end - - private - def valid_condition?(method) - segment_keys.include?(method) || set.valid_conditions.include?(method) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 11228c597d..2bcde16110 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,4 +1,4 @@ -require 'rack/mount' +require 'journey' require 'forwardable' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' @@ -37,7 +37,7 @@ module ActionDispatch # If this is a default_controller (i.e. a controller specified by the user) # we should raise an error in case it's not found, because it usually means - # an user error. However, if the controller was retrieved through a dynamic + # a user error. However, if the controller was retrieved through a dynamic # segment, as in :controller(/:action), we should simply return nil and # delegate the control back to Rack cascade. Besides, if this is not a default # controller, it means we should respect the @scope[:module] parameter. @@ -165,13 +165,14 @@ module ActionDispatch remove_possible_method :#{selector} def #{selector}(*args) options = args.extract_options! + result = #{options.inspect} if args.any? - options[:_positional_args] = args - options[:_positional_keys] = #{route.segment_keys.inspect} + result[:_positional_args] = args + result[:_positional_keys] = #{route.segment_keys.inspect} end - options ? #{options.inspect}.merge(options) : #{options.inspect} + result.merge(options) end protected :#{selector} END_EVAL @@ -205,29 +206,42 @@ module ActionDispatch end end - attr_accessor :set, :routes, :named_routes, :default_scope + attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions + alias :routes :set + def self.default_resources_path_names { :new => 'new', :edit => 'edit' } end def initialize(request_class = ActionDispatch::Request) - self.routes = [] self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} self.request_class = request_class - self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym } + @valid_conditions = {} + + request_class.public_instance_methods.each { |m| + @valid_conditions[m.to_sym] = true + } + @valid_conditions[:controller] = true + @valid_conditions[:action] = true + self.valid_conditions.delete(:id) - self.valid_conditions.push(:controller, :action) - @append = [] - @prepend = [] + @append = [] + @prepend = [] @disable_clear_and_finalize = false - clear! + @finalized = false + + @set = Journey::Routes.new + @router = Journey::Router.new(@set, { + :parameters_key => PARAMETERS_KEY, + :request_class => request_class}) + @formatter = Journey::Formatter.new @set end def draw(&block) @@ -263,17 +277,13 @@ module ActionDispatch return if @finalized @append.each { |blk| eval_block(blk) } @finalized = true - @set.freeze end def clear! @finalized = false - routes.clear named_routes.clear - @set = ::Rack::Mount::RouteSet.new( - :parameters_key => PARAMETERS_KEY, - :request_class => request_class - ) + set.clear + formatter.clear @prepend.each { |blk| eval_block(blk) } end @@ -341,26 +351,54 @@ module ActionDispatch def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) - route = Route.new(self, app, conditions, requirements, defaults, name, anchor) - @set.add_route(route.app, route.conditions, route.defaults, route.name) + + path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor) + conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym }) + + route = @set.add_route(app, path, conditions, defaults, name) named_routes[name] = route if name - routes << route route end + def build_path(path, requirements, separators, anchor) + strexp = Journey::Router::Strexp.new( + path, + requirements, + SEPARATORS, + anchor) + + Journey::Path::Pattern.new(strexp) + end + private :build_path + + def build_conditions(current_conditions, req_predicates, path_values) + conditions = current_conditions.dup + + verbs = conditions[:request_method] || [] + + # Rack-Mount requires that :request_method be a regular expression. + # :request_method represents the HTTP verb that matches this route. + # + # Here we munge values before they get sent on to rack-mount. + unless verbs.empty? + conditions[:request_method] = %r[^#{verbs.join('|')}$] + end + conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) } + + conditions + end + private :build_conditions + class Generator #:nodoc: - PARAMETERIZE = { - :parameterize => lambda do |name, value| - if name == :controller - value - elsif value.is_a?(Array) - value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') - else - return nil unless param = value.to_param - param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/") - end + PARAMETERIZE = lambda do |name, value| + if name == :controller + value + elsif value.is_a?(Array) + value.map { |v| v.to_param }.join('/') + elsif param = value.to_param + param end - } + end attr_reader :options, :recall, :set, :named_route @@ -450,14 +488,14 @@ module ActionDispatch end def generate - path, params = @set.set.generate(:path_info, named_route, options, recall, PARAMETERIZE) + path, params = @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE) raise_routing_error unless path return [path, params.keys] if @extras [path, params] - rescue Rack::Mount::RoutingError + rescue Journey::Router::RoutingError raise_routing_error end @@ -529,12 +567,12 @@ module ActionDispatch def call(env) finalize! - @set.call(env) + @router.call(env) end def recognize_path(path, environment = {}) method = (environment[:method] || "GET").to_s.upcase - path = Rack::Mount::Utils.normalize_path(path) unless path =~ %r{://} + path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://} begin env = Rack::MockRequest.env_for(path, {:method => method}) @@ -543,7 +581,7 @@ module ActionDispatch end req = @request_class.new(env) - @set.recognize(req) do |route, matches, params| + @router.recognize(req) do |route, matches, params| params.each do |key, value| if value.is_a?(String) value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware? diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 480144fe9d..39ba83fb9a 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -42,7 +42,7 @@ module ActionDispatch # url_for(:controller => 'users', # :action => 'new', # :message => 'Welcome!', - # :host => 'www.example.com') # Changed this. + # :host => 'www.example.com') # # => "http://www.example.com/users/new?message=Welcome%21" # # By default, all controllers and views have access to a special version of url_for, @@ -52,7 +52,7 @@ module ActionDispatch # # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' - # in full. However, mailers don't have hostname information, and what's why you'll still + # in full. However, mailers don't have hostname information, and that's why you'll still # have to specify the <tt>:host</tt> argument when generating URLs in mailers. # # @@ -116,9 +116,10 @@ module ActionDispatch # If <tt>:only_path</tt> is false, this option must be # provided either explicitly, or via +default_url_options+. # * <tt>:subdomain</tt> - Specifies the subdomain of the link, using the +tld_length+ - # to split the domain from the host. - # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+ # to split the subdomain from the host. + # If false, removes all subdomains from the host part of the link. + # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+ + # to split the domain from the host. # * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if # <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1. @@ -131,16 +132,20 @@ module ActionDispatch # # Examples: # - # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080' # => 'http://somehost.org:8080/tasks/testing' - # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' - # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true # => 'http://somehost.org/tasks/testing/' - # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' + # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080' + # # => 'http://somehost.org:8080/tasks/testing' + # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true + # # => '/tasks/testing#ok' + # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true + # # => 'http://somehost.org/tasks/testing/' + # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33' + # # => 'http://somehost.org/tasks/testing?number=33' def url_for(options = nil) case options when String options when nil, Hash - _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys) else polymorphic_url(options) end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 5fa91d1a76..b4555f4f59 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -415,9 +415,9 @@ module ActionDispatch assert !deliveries.empty?, "No e-mail in delivery list" for delivery in deliveries - for part in delivery.parts + for part in (delivery.parts.empty? ? [delivery] : delivery.parts) if part["Content-Type"].to_s =~ /^text\/html\W/ - root = HTML::Document.new(part.body).root + root = HTML::Document.new(part.body.to_s).root assert_select root, ":root", &block end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index aae5752c93..0f1bb9f260 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -241,8 +241,8 @@ module ActionDispatch end # Performs the actual request. - def process(method, path, parameters = nil, env = nil) - env ||= {} + def process(method, path, parameters = nil, rack_env = nil) + rack_env ||= {} if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -258,7 +258,7 @@ module ActionDispatch hostname, port = host.split(':') - default_env = { + env = { :method => method, :params => parameters, @@ -276,7 +276,7 @@ module ActionDispatch session = Rack::Test::Session.new(_mock_session) - env.reverse_merge!(default_env) + env.merge!(rack_env) # NOTE: rack-test v0.5 doesn't build a default uri correctly # Make sure requested path is always a full uri diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index f668b81b45..b08ff41950 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -5,12 +5,7 @@ require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch module TestProcess def assigns(key = nil) - assigns = {}.with_indifferent_access - @controller.instance_variable_names.each do |ivar| - next if ActionController::Base.protected_instance_variables.include?(ivar) - assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar) - end - + assigns = @controller.view_assigns.with_indifferent_access key.nil? ? assigns : assigns[key] end |