aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
authorMikel Lindsaar <raasdnil@gmail.com>2010-03-11 22:05:15 +1100
committerMikel Lindsaar <raasdnil@gmail.com>2010-03-11 22:05:15 +1100
commitf5774e3e3f70a3acfa559b9ff889e9417fb71d4b (patch)
treee738112994d40d6c3792065da80bddfa7439467b /actionpack/lib/action_dispatch
parentcefe723e285f20d1f2a33f67da03348568f7e0b0 (diff)
parent073852dff0b48296a9a184f94e722183334f3c4c (diff)
downloadrails-f5774e3e3f70a3acfa559b9ff889e9417fb71d4b.tar.gz
rails-f5774e3e3f70a3acfa559b9ff889e9417fb71d4b.tar.bz2
rails-f5774e3e3f70a3acfa559b9ff889e9417fb71d4b.zip
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb15
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb39
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb37
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb51
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb8
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb37
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb296
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb64
12 files changed, 302 insertions, 264 deletions
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 40617e239a..fec250e928 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -67,21 +67,6 @@ module ActionDispatch
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
end
- # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
- # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
- # otherwise.
- def template_format
- parameter_format = parameters[:format]
-
- if parameter_format
- parameter_format
- elsif xhr?
- :js
- else
- :html
- end
- end
-
# Receives an array of mimes and return the first user sent mime that
# matches the order array.
#
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 7a17023ed2..ea9f0f99c2 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -30,6 +30,14 @@ module ActionDispatch
METHOD
end
+ def self.new(env)
+ if request = env["action_dispatch.request"] && request.instance_of?(self)
+ return request
+ end
+
+ super
+ end
+
def key?(key)
@env.key?(key)
end
@@ -119,36 +127,7 @@ module ActionDispatch
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
def remote_ip
- remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
-
- unless remote_addr_list.blank?
- not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies}
- return not_trusted_addrs.first unless not_trusted_addrs.empty?
- end
- remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
-
- if @env.include? 'HTTP_CLIENT_IP'
- if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
- # We don't know which came from the proxy, and which from the user
- raise ActionController::ActionControllerError.new <<EOM
-IP spoofing attack?!
-HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
-HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
-EOM
- end
-
- return @env['HTTP_CLIENT_IP']
- end
-
- if remote_ips
- while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip)
- remote_ips.pop
- end
-
- return remote_ips.last.strip
- end
-
- @env['REMOTE_ADDR']
+ (@env["action_dispatch.remote_ip"] || ip).to_s
end
# Returns the lowercase name of the HTTP server software.
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 42ad19044d..b64a83c62e 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -3,7 +3,7 @@ module ActionDispatch
module URL
# Returns the complete URL used for this request.
def url
- protocol + host_with_port + request_uri
+ protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
@@ -85,42 +85,11 @@ module ActionDispatch
subdomains(tld_length).join('.')
end
- # Returns the query string, accounting for server idiosyncrasies.
- def query_string
- @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
- end
-
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
def request_uri
- if uri = @env['REQUEST_URI']
- # Remove domain, which webrick puts into the request_uri.
- (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
- else
- # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
- uri = @env['PATH_INFO'].to_s
-
- if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
- uri = uri.sub(/#{script_filename}\//, '')
- end
-
- env_qs = @env['QUERY_STRING'].to_s
- uri += "?#{env_qs}" unless env_qs.empty?
-
- if uri.blank?
- @env.delete('REQUEST_URI')
- else
- @env['REQUEST_URI'] = uri
- end
- end
- end
-
- # Returns the interpreted \path to requested resource after all the installation
- # directory of this application was taken into account.
- def path
- path = request_uri.to_s[/\A[^\?]*/]
- path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
- path
+ ActiveSupport::Deprecation.warn "Using #request_uri is deprecated. Use fullpath instead.", caller
+ fullpath
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 522982e202..f4c4324fb0 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -1,4 +1,3 @@
-require 'active_support/json'
require 'action_dispatch/http/request'
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
new file mode 100644
index 0000000000..c7d710b98e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -0,0 +1,51 @@
+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
+ end
+
+ def remote_addrs
+ @remote_addrs ||= begin
+ list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
+ list.reject { |addr| addr =~ @trusted_proxies }
+ end
+ 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
+ end
+
+ return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
+ 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 call(env)
+ env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
+ @app.call(env)
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 04a101dbb2..22da82479e 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/hash/keys'
-require 'rack/request'
module ActionDispatch
module Session
@@ -177,9 +176,8 @@ module ActionDispatch
if key.blank?
raise ArgumentError, 'A key is required to write a ' +
'cookie containing the session data. Use ' +
- 'config.action_controller.session = { :key => ' +
- '"_myapp_session", :secret => "some secret phrase" } in ' +
- 'config/application.rb'
+ 'config.action_controller.session_store :cookie_store, { :key => ' +
+ '"_myapp_session" } in config/application.rb'
end
end
@@ -193,10 +191,9 @@ module ActionDispatch
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
- "config.action_controller.session = { :key => " +
- "\"_myapp_session\", :secret => \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\" } " +
- "in config/environment.rb"
+ "config.cookie_secret = \"some secret phrase of at " +
+ "least #{SECRET_MIN_LENGTH} characters\"" +
+ "in config/application.rb"
end
if secret.length < SECRET_MIN_LENGTH
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 18a2922fa7..5c5362ce4a 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -58,7 +58,7 @@ module ActionDispatch
if lazy_compare?(@klass) && lazy_compare?(middleware)
normalize(@klass) == normalize(middleware)
else
- klass == ActiveSupport::Inflector.constantize(middleware.to_s)
+ klass.name == middleware.to_s
end
end
end
@@ -122,7 +122,11 @@ module ActionDispatch
find_all { |middleware| middleware.active? }
end
- def build(app)
+ def build(app = nil, &blk)
+ app ||= blk
+
+ raise "MiddlewareStack#build requires an app" unless app
+
active.reverse.inject(app) { |a, e| e.build(a) }
end
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 79e9464337..e486bd4079 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -6,6 +6,7 @@ module ActionDispatch
railtie_name :action_dispatch
config.action_dispatch.x_sendfile_header = "X-Sendfile"
+ config.action_dispatch.ip_spoofing_check = true
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 52e7b0e77d..0b7b09ee7a 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/except'
+
module ActionDispatch
module Routing
class Mapper
@@ -36,7 +38,7 @@ module ActionDispatch
end
def to_route
- [ app, conditions, requirements, defaults, @options[:as] ]
+ [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
end
private
@@ -64,7 +66,7 @@ module ActionDispatch
# match "account/overview"
def using_match_shorthand?(args, options)
- args.present? && options.except(:via).empty? && !args.first.include?(':')
+ args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':')
end
def normalize_path(path)
@@ -85,7 +87,7 @@ module ActionDispatch
end
def requirements
- @requirements ||= returning(@options[:constraints] || {}) do |requirements|
+ @requirements ||= (@options[:constraints] || {}).tap do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
end
@@ -174,9 +176,15 @@ module ActionDispatch
end
def match(*args)
- @set.add_route(*Mapping.new(@set, @scope, args).to_route)
+ mapping = Mapping.new(@set, @scope, args).to_route
+ @set.add_route(*mapping)
self
end
+
+ def default_url_options=(options)
+ @set.default_url_options = options
+ end
+ alias_method :default_url_options, :default_url_options=
end
module HttpHelpers
@@ -293,6 +301,7 @@ module ActionDispatch
options = args.extract_options!
options = (@scope[:options] || {}).merge(options)
+ options[:anchor] = true unless options.key?(:anchor)
if @scope[:name_prefix] && !options[:as].blank?
options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
@@ -450,7 +459,10 @@ module ActionDispatch
scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resource, resource) do
- yield if block_given?
+
+ scope(:name_prefix => resource.name.to_s) do
+ yield if block_given?
+ end
get :show if resource.actions.include?(:show)
post :create if resource.actions.include?(:create)
@@ -533,6 +545,21 @@ module ActionDispatch
end
end
+ def mount(app, options = nil)
+ if options
+ path = options.delete(:at)
+ else
+ options = app
+ app, path = options.find { |k, v| k.respond_to?(:call) }
+ options.delete(app) if app
+ end
+
+ raise "A rack application must be specified" unless path
+
+ match(path, options.merge(:to => app, :anchor => false))
+ self
+ end
+
def match(*args)
options = args.extract_options!
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index e6e44d3546..6f37eadd6b 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -4,7 +4,7 @@ module ActionDispatch
attr_reader :app, :conditions, :defaults, :name
attr_reader :path, :requirements
- def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
+ def initialize(app, conditions, requirements, defaults, name, anchor)
@app = app
@defaults = defaults
@name = name
@@ -17,7 +17,7 @@ module ActionDispatch
if path = conditions[:path_info]
@path = path
- conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS)
+ conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end
@conditions = conditions.inject({}) { |h, (k, v)|
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 99436e3cb0..722be432c7 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -168,31 +168,6 @@ module ActionDispatch
selector = url_helper_name(name, kind)
hash_access_method = hash_access_name(name, kind)
- # We use module_eval to avoid leaks.
- #
- # def users_url(*args)
- # if args.empty? || Hash === args.first
- # options = hash_for_users_url(args.first || {})
- # else
- # options = hash_for_users_url(args.extract_options!)
- # default = default_url_options(options) if self.respond_to?(:default_url_options, true)
- # options = (default ||= {}).merge(options)
- #
- # keys = []
- # keys -= options.keys if args.size < keys.size - 1
- #
- # args = args.zip(keys).inject({}) do |h, (v, k)|
- # h[k] = v
- # h
- # end
- #
- # # Tell url_for to skip default_url_options
- # options[:use_defaults] = false
- # options.merge!(args)
- # end
- #
- # url_for(options)
- # end
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(*args)
options = #{hash_access_method}(args.extract_options!)
@@ -211,6 +186,7 @@ module ActionDispatch
attr_accessor :routes, :named_routes
attr_accessor :disable_clear_and_finalize, :resources_path_names
+ attr_accessor :default_url_options
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
@@ -221,8 +197,10 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.controller_namespaces = Set.new
+ self.default_url_options = {}
@disable_clear_and_finalize = false
+ clear!
end
def draw(&block)
@@ -259,31 +237,22 @@ module ActionDispatch
named_routes.install(destinations, regenerate_code)
end
- def url_for
- @url_for ||= begin
- router = self
- Module.new do
- extend ActiveSupport::Concern
- include UrlFor
-
- define_method(:_router) { router }
- end
- end
- end
-
def url_helpers
@url_helpers ||= begin
router = self
Module.new do
extend ActiveSupport::Concern
- include router.url_for
+ include UrlFor
# ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
# we can include?
+ # Yes plz - JP
included do
router.install_helpers(self)
end
+
+ define_method(:_router) { router }
end
end
end
@@ -292,36 +261,131 @@ module ActionDispatch
routes.empty?
end
- def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
- route = Route.new(app, conditions, requirements, defaults, name)
+ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
+ route = Route.new(app, conditions, requirements, defaults, name, anchor)
@set.add_route(*route)
named_routes[name] = route if name
routes << route
route
end
- def options_as_params(options)
- # If an explicit :controller was given, always make :action explicit
- # too, so that action expiry works as expected for things like
- #
- # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
- #
- # (the above is from the unit tests). In the above case, because the
- # controller was explicitly given, but no action, the action is implied to
- # be "index", not the recalled action of "show".
- #
- # great fun, eh?
-
- options_as_params = options.clone
- options_as_params[:action] ||= 'index' if options[:controller]
- options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
- options_as_params
- end
+ class Generator
+ attr_reader :options, :recall, :set, :script_name, :named_route
+
+ def initialize(options, recall, set, extras = false)
+ @script_name = options.delete(:script_name)
+ @named_route = options.delete(:use_route)
+ @options = options.dup
+ @recall = recall.dup
+ @set = set
+ @extras = extras
+
+ normalize_options!
+ normalize_controller_action_id!
+ use_relative_controller!
+ controller.sub!(%r{^/}, '') if controller
+ handle_nil_action!
+ end
+
+ def controller
+ @controller ||= @options[:controller]
+ end
+
+ def current_controller
+ @recall[:controller]
+ end
+
+ def use_recall_for(key)
+ if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
+ @options[key] = @recall.delete(key)
+ end
+ end
+
+ def normalize_options!
+ # If an explicit :controller was given, always make :action explicit
+ # too, so that action expiry works as expected for things like
+ #
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ #
+ # (the above is from the unit tests). In the above case, because the
+ # controller was explicitly given, but no action, the action is implied to
+ # be "index", not the recalled action of "show".
+
+ if options[:controller]
+ options[:action] ||= 'index'
+ options[:controller] = options[:controller].to_s
+ end
+
+ if options[:action]
+ options[:action] = options[:action].to_s
+ end
+ end
+
+ # This pulls :controller, :action, and :id out of the recall.
+ # The recall key is only used if there is no key in the options
+ # or if the key in the options is identical. If any of
+ # :controller, :action or :id is not found, don't pull any
+ # more keys from the recall.
+ def normalize_controller_action_id!
+ @recall[:action] ||= 'index' if current_controller
+
+ use_recall_for(:controller) or return
+ use_recall_for(:action) or return
+ use_recall_for(:id)
+ end
+
+ # if the current controller is "foo/bar/baz" and :controller => "baz/bat"
+ # is specified, the controller becomes "foo/baz/bat"
+ def use_relative_controller!
+ if !named_route && different_controller?
+ old_parts = current_controller.split('/')
+ size = controller.count("/") + 1
+ parts = old_parts[0...-size] << controller
+ @controller = @options[:controller] = parts.join("/")
+ end
+ end
+
+ # This handles the case of :action => nil being explicitly passed.
+ # It is identical to :action => "index"
+ def handle_nil_action!
+ if options.has_key?(:action) && options[:action].nil?
+ options[:action] = 'index'
+ end
+ recall[:action] = options.delete(:action) if options[:action] == 'index'
+ end
+
+ def generate
+ error = ActionController::RoutingError.new("No route matches #{options.inspect}")
+ path, params = @set.generate(:path_info, named_route, options, recall, opts)
+
+ raise error unless path
+
+ params.reject! {|k,v| !v }
+
+ return [path, params.keys] if @extras
- def build_expiry(options, recall)
- recall.inject({}) do |expiry, (key, recalled_value)|
- expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
- expiry
+ path << "?#{params.to_query}" if params.any?
+ "#{script_name}#{path}"
+ rescue Rack::Mount::RoutingError
+ raise error
+ end
+
+ def opts
+ 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
+ Rack::Mount::Utils.escape_uri(value.to_param)
+ end
+ end
+ {:parameterize => parameterize}
+ end
+
+ def different_controller?
+ return false unless current_controller
+ controller.to_param != current_controller.to_param
end
end
@@ -332,82 +396,44 @@ module ActionDispatch
end
def generate_extras(options, recall={})
- generate(options, recall, :generate_extras)
+ generate(options, recall, true)
end
- def generate(options, recall = {}, method = :generate)
- options, recall = options.dup, recall.dup
- named_route = options.delete(:use_route)
+ def generate(options, recall = {}, extras = false)
+ Generator.new(options, recall, @set, extras).generate
+ end
- options = options_as_params(options)
- expire_on = build_expiry(options, recall)
+ RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
- recall[:action] ||= 'index' if options[:controller] || recall[:controller]
+ def url_for(options)
+ options = default_url_options.merge(options || {})
- if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
- options[:controller] = recall.delete(:controller)
+ handle_positional_args(options)
- if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
- options[:action] = recall.delete(:action)
+ rewritten_url = ""
- if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
- options[:id] = recall.delete(:id)
- end
- end
- end
-
- options[:controller] = options[:controller].to_s if options[:controller]
+ path_segments = options.delete(:_path_segments)
- if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
- old_parts = recall[:controller].split('/')
- new_parts = options[:controller].split('/')
- parts = old_parts[0..-(new_parts.length + 1)] + new_parts
- options[:controller] = parts.join('/')
- end
+ unless options[:only_path]
+ rewritten_url << (options[:protocol] || "http")
+ rewritten_url << "://" unless rewritten_url.match("://")
+ rewritten_url << rewrite_authentication(options)
- options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
+ raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
- merged = options.merge(recall)
- if options.has_key?(:action) && options[:action].nil?
- options.delete(:action)
- recall[:action] = 'index'
+ rewritten_url << options[:host]
+ rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
-
- opts = {}
- opts[:parameterize] = lambda { |name, value|
- if name == :controller
- value
- elsif value.is_a?(Array)
- value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
- else
- Rack::Mount::Utils.escape_uri(value.to_param)
- end
- }
- unless result = @set.generate(:path_info, named_route, options, recall, opts)
- raise ActionController::RoutingError, "No route matches #{options.inspect}"
- end
+ path_options = options.except(*RESERVED_OPTIONS)
+ path_options = yield(path_options) if block_given?
+ path = generate(path_options, path_segments || {})
- path, params = result
- params.each do |k, v|
- if v
- params[k] = v
- else
- params.delete(k)
- end
- end
+ # ROUTES TODO: This can be called directly, so script_name should probably be set in the router
+ rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
- if path && method == :generate_extras
- [path, params.keys]
- elsif path
- path << "?#{params.to_query}" if params.any?
- path
- else
- raise ActionController::RoutingError, "No route matches #{options.inspect}"
- end
- rescue Rack::Mount::RoutingError
- raise ActionController::RoutingError, "No route matches #{options.inspect}"
+ rewritten_url
end
def call(env)
@@ -435,6 +461,30 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
+
+ private
+ def handle_positional_args(options)
+ return unless args = options.delete(:_positional_args)
+
+ keys = options.delete(:_positional_keys)
+ keys -= options.keys if args.size < keys.size - 1 # take format into account
+
+ args = args.zip(keys).inject({}) do |h, (v, k)|
+ h[k] = v
+ h
+ end
+
+ # Tell url_for to skip default_url_options
+ options.merge!(args)
+ end
+
+ def rewrite_authentication(options)
+ if options[:user] && options[:password]
+ "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
+ else
+ ""
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 7f2c9a5c12..ec78f53fa6 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -85,37 +85,24 @@ module ActionDispatch
extend ActiveSupport::Concern
included do
- # Including in a class uses an inheritable hash. Modules get a plain hash.
- if respond_to?(:class_attribute)
- class_attribute :default_url_options
- else
- mattr_accessor :default_url_options
- remove_method :default_url_options
- end
+ # TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options
+ unless method_defined?(:default_url_options)
+ # Including in a class uses an inheritable hash. Modules get a plain hash.
+ if respond_to?(:class_attribute)
+ class_attribute :default_url_options
+ else
+ mattr_accessor :default_url_options
+ remove_method :default_url_options
+ end
- self.default_url_options = {}
+ self.default_url_options = {}
+ end
end
- # def default_url_options(options = nil)
- # self.class.default_url_options
- # end
-
def url_options
- self.class.default_url_options.merge(@url_options || {})
+ default_url_options
end
- def url_options=(options)
- @url_options = options
- end
-
- # def merge_options(options) #:nodoc:
- # if respond_to?(:default_url_options) && (defaults = default_url_options(options))
- # defaults.merge(options)
- # else
- # options
- # end
- # end
-
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
@@ -126,8 +113,6 @@ module ActionDispatch
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
- # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
@@ -139,25 +124,16 @@ module ActionDispatch
# 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 = {})
- options ||= {}
+ def url_for(options = nil)
case options
- when String
- options
- when Hash
- # Handle the deprecated instance level default_url_options
- if respond_to?(:default_url_options, true)
- ActiveSupport::Deprecation.warn "Overwriting #default_url_options is deprecated. Please set url options with self.url_options = { ... }"
- if defaults = default_url_options(options)
- options = defaults.merge(options)
- end
- end
-
- ActionController::UrlRewriter.rewrite(_router, url_options.merge(options))
- else
- polymorphic_url(options)
+ when String
+ options
+ when nil, Hash
+ _router.url_for(url_options.merge(options || {}))
+ else
+ polymorphic_url(options)
end
end
end
end
-end \ No newline at end of file
+end