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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
require "action_dispatch/http/request"
require "active_support/core_ext/uri"
require "active_support/core_ext/array/extract_options"
require "rack/utils"
require "action_controller/metal/exceptions"
require "action_dispatch/routing/endpoint"
module ActionDispatch
module Routing
class Redirect < Endpoint # :nodoc:
attr_reader :status, :block
def initialize(status, block)
@status = status
@block = block
end
def redirect?; true; end
def call(env)
serve Request.new env
end
def serve(req)
uri = URI.parse(path(req.path_parameters, req))
unless uri.host
if relative_path?(uri.path)
uri.path = "#{req.script_name}/#{uri.path}"
elsif uri.path.empty?
uri.path = req.script_name.empty? ? "/" : req.script_name
end
end
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.unwrapped_html_escape(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
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)
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
"redirect(#{status}, #{block})"
end
private
def interpolation_required?(string, params)
!params.empty? && string && string.match(/%\{\w*\}/)
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
if !params.empty? && url_options[:path].match(/%\{\w*\}/)
url_options[:path] = (url_options[:path] % escape_path(params))
end
unless options[:host] || options[:domain]
if relative_path?(url_options[:path])
url_options[:path] = "/#{url_options[:path]}"
url_options[:script_name] = request.script_name
elsif url_options[:path].empty?
url_options[:path] = request.script_name.empty? ? "/" : ""
url_options[:script_name] = request.script_name
end
end
ActionDispatch::Http::URL.url_for url_options
end
def inspect
"redirect(#{status}, #{options.map { |k, v| "#{k}: #{v}" }.join(', ')})"
end
end
module Redirection
# Redirect any path to another path:
#
# get "/stories" => redirect("/posts")
#
# This will redirect the user, while ignoring certain parts of the request, including query string, etc.
# `/stories`, `/stories?foo=bar`, etc all redirect to `/posts`.
#
# You can also use interpolation in the supplied redirect argument:
#
# 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
# the redirect in question. Either the params and request are supplied as arguments, or just
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
# get 'jokes/:number', to: redirect { |params, request|
# path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# }
#
# Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
# the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
#
# get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
# get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
# get '/stories', to: redirect(path: '/posts')
#
# This will redirect the user, while changing only the specified parts of the request,
# for example the `path` option in the last example.
# `/stories`, `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively.
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
# get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.extract_options!
status = options.delete(:status) || 301
path = args.shift
return OptionRedirect.new(status, options) if options.any?
return PathRedirect.new(status, path) if String === path
block = path if path.respond_to? :call
raise ArgumentError, "redirection argument not supported" unless block
Redirect.new status, block
end
end
end
end
|