aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/url_rewriter.rb
blob: 3a38f23396e750ab60eb177430c7ec390e28f471 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
module ActionController
  # Write URLs from arbitrary places in your codebase, such as your mailers.
  #
  # Example:
  #
  #   class MyMailer
  #     include ActionController::UrlWriter
  #     default_url_options[:host] = 'www.basecamphq.com'
  #
  #     def signup_url(token)
  #       url_for(:controller => 'signup', action => 'index', :token => token)
  #     end
  #  end
  #
  # In addition to providing +url_for+, named routes are also accessible after
  # including UrlWriter.
  module UrlWriter
    # The default options for urls written by this writer. Typically a <tt>:host</tt>
    # pair is provided.
    mattr_accessor :default_url_options
    self.default_url_options = {}

    def self.included(base) #:nodoc:
      ActionController::Routing::Routes.install_helpers(base)
      base.mattr_accessor :default_url_options
      base.default_url_options ||= default_url_options
    end

    # Generate a url based on the options provided, default_url_options and the
    # routes defined in routes.rb.  The following options are supported:
    #
    # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
    # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
    # * <tt>:host</tt> - Specifies the host the link should be targetted at.
    #   If <tt>:only_path</tt> is false, this option must be
    #   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::AbstractRequest.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
    # +url_for+ is forwarded to the Routes module.
    #
    # 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'
    def url_for(options)
      options = self.class.default_url_options.merge(options)

      url = ''

      unless options.delete(:only_path)
        url << (options.delete(:protocol) || 'http')
        url << '://' unless url.match("://")

        raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]

        url << options.delete(:host)
        url << ":#{options.delete(:port)}" if options.key?(:port)
      else
        # Delete the unused options to prevent their appearance in the query string.
        [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
      end
      trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
      url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
      anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
      generated = Routing::Routes.generate(options, {})
      url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
      url << anchor if anchor

      url
    end
  end

  # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
  class UrlRewriter #:nodoc:
    RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
    def initialize(request, parameters)
      @request, @parameters = request, parameters
    end

    def rewrite(options = {})
      rewrite_url(options)
    end

    def to_str
      "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
    end

    alias_method :to_s, :to_str

    private
      # Given a path and options, returns a rewritten URL string
      def rewrite_url(options)
        rewritten_url = ""

        unless options[:only_path]
          rewritten_url << (options[:protocol] || @request.protocol)
          rewritten_url << "://" unless rewritten_url.match("://")
          rewritten_url << rewrite_authentication(options)
          rewritten_url << (options[:host] || @request.host_with_port)
          rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
        end

        path = rewrite_path(options)
        rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
        rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
        rewritten_url << "##{options[:anchor]}" if options[:anchor]

        rewritten_url
      end

      # Given a Hash of options, generates a route
      def rewrite_path(options)
        options = options.symbolize_keys
        options.update(options[:params].symbolize_keys) if options[:params]

        if (overwrite = options.delete(:overwrite_params))
          options.update(@parameters.symbolize_keys)
          options.update(overwrite.symbolize_keys)
        end

        RESERVED_OPTIONS.each { |k| options.delete(k) }

        # Generates the query string, too
        Routing::Routes.generate(options, @request.symbolized_path_parameters)
      end

      def rewrite_authentication(options)
        if options[:user] && options[:password]
          "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
        else
          ""
        end
      end
  end
end