001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.web.util;
020
021 import javax.servlet.http.HttpServletRequest;
022 import javax.servlet.http.HttpServletResponse;
023 import java.io.IOException;
024 import java.io.UnsupportedEncodingException;
025 import java.net.URLEncoder;
026 import java.util.Map;
027
028 /**
029 * View that redirects to an absolute, context relative, or current request
030 * relative URL, exposing all model attributes as HTTP query parameters.
031 * <p/>
032 * A URL for this view is supposed to be a HTTP redirect URL, i.e.
033 * suitable for HttpServletResponse's <code>sendRedirect</code> method, which
034 * is what actually does the redirect if the HTTP 1.0 flag is on, or via sending
035 * back an HTTP 303 code - if the HTTP 1.0 compatibility flag is off.
036 * <p/>
037 * Note that while the default value for the "contextRelative" flag is off,
038 * you will probably want to almost always set it to true. With the flag off,
039 * URLs starting with "/" are considered relative to the web server root, while
040 * with the flag on, they are considered relative to the web application root.
041 * Since most web apps will never know or care what their context path actually
042 * is, they are much better off setting this flag to true, and submitting paths
043 * which are to be considered relative to the web application root.
044 * <p/>
045 * Note that in a Servlet 2.2 environment, i.e. a servlet container which
046 * is only compliant to the limits of this spec, this class will probably fail
047 * when feeding in URLs which are not fully absolute, or relative to the current
048 * request (no leading "/"), as these are the only two types of URL that
049 * <code>sendRedirect</code> supports in a Servlet 2.2 environment.
050 * <p/>
051 * <em>This class was borrowed from a nearly identical version found in
052 * the <a href="http://www.springframework.org/">Spring Framework</a>, with minor modifications to
053 * avoid a dependency on Spring itself for a very small amount of code - we couldn't have done it better, and
054 * don't want to repeat all of their great effort ;).
055 * The original author names and copyright (Apache 2.0) has been left in place. A special
056 * thanks to Rod Johnson, Juergen Hoeller, and Colin Sampaleanu for making this available.</em>
057 *
058 * @see #setContextRelative
059 * @see #setHttp10Compatible
060 * @see javax.servlet.http.HttpServletResponse#sendRedirect
061 * @since 0.2
062 */
063 public class RedirectView {
064
065 //TODO - complete JavaDoc
066
067 /**
068 * The default encoding scheme: UTF-8
069 */
070 public static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
071
072 private String url;
073
074 private boolean contextRelative = false;
075
076 private boolean http10Compatible = true;
077
078 private String encodingScheme = DEFAULT_ENCODING_SCHEME;
079
080 /**
081 * Constructor for use as a bean.
082 */
083 @SuppressWarnings({"UnusedDeclaration"})
084 public RedirectView() {
085 }
086
087 /**
088 * Create a new RedirectView with the given URL.
089 * <p>The given URL will be considered as relative to the web server,
090 * not as relative to the current ServletContext.
091 *
092 * @param url the URL to redirect to
093 * @see #RedirectView(String, boolean)
094 */
095 public RedirectView(String url) {
096 setUrl(url);
097 }
098
099 /**
100 * Create a new RedirectView with the given URL.
101 *
102 * @param url the URL to redirect to
103 * @param contextRelative whether to interpret the given URL as
104 * relative to the current ServletContext
105 */
106 public RedirectView(String url, boolean contextRelative) {
107 this(url);
108 this.contextRelative = contextRelative;
109 }
110
111 /**
112 * Create a new RedirectView with the given URL.
113 *
114 * @param url the URL to redirect to
115 * @param contextRelative whether to interpret the given URL as
116 * relative to the current ServletContext
117 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
118 */
119 public RedirectView(String url, boolean contextRelative, boolean http10Compatible) {
120 this(url);
121 this.contextRelative = contextRelative;
122 this.http10Compatible = http10Compatible;
123 }
124
125
126 public String getUrl() {
127 return url;
128 }
129
130 public void setUrl(String url) {
131 this.url = url;
132 }
133
134 /**
135 * Set whether to interpret a given URL that starts with a slash ("/")
136 * as relative to the current ServletContext, i.e. as relative to the
137 * web application root.
138 * <p/>
139 * Default is "false": A URL that starts with a slash will be interpreted
140 * as absolute, i.e. taken as-is. If true, the context path will be
141 * prepended to the URL in such a case.
142 *
143 * @param contextRelative whether to interpret a given URL that starts with a slash ("/")
144 * as relative to the current ServletContext, i.e. as relative to the
145 * web application root.
146 * @see javax.servlet.http.HttpServletRequest#getContextPath
147 */
148 public void setContextRelative(boolean contextRelative) {
149 this.contextRelative = contextRelative;
150 }
151
152 /**
153 * Set whether to stay compatible with HTTP 1.0 clients.
154 * <p>In the default implementation, this will enforce HTTP status code 302
155 * in any case, i.e. delegate to <code>HttpServletResponse.sendRedirect</code>.
156 * Turning this off will send HTTP status code 303, which is the correct
157 * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
158 * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any
159 * difference. However, some clients depend on 303 when redirecting
160 * after a POST request; turn this flag off in such a scenario.
161 *
162 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients.
163 * @see javax.servlet.http.HttpServletResponse#sendRedirect
164 */
165 public void setHttp10Compatible(boolean http10Compatible) {
166 this.http10Compatible = http10Compatible;
167 }
168
169 /**
170 * Set the encoding scheme for this view. Default is UTF-8.
171 *
172 * @param encodingScheme the encoding scheme for this view. Default is UTF-8.
173 */
174 @SuppressWarnings({"UnusedDeclaration"})
175 public void setEncodingScheme(String encodingScheme) {
176 this.encodingScheme = encodingScheme;
177 }
178
179
180 /**
181 * Convert model to request parameters and redirect to the given URL.
182 *
183 * @param model the model to convert
184 * @param request the incoming HttpServletRequest
185 * @param response the outgoing HttpServletResponse
186 * @throws java.io.IOException if there is a problem issuing the redirect
187 * @see #appendQueryProperties
188 * @see #sendRedirect
189 */
190 public final void renderMergedOutputModel(
191 Map model, HttpServletRequest request, HttpServletResponse response) throws IOException {
192
193 // Prepare name URL.
194 StringBuilder targetUrl = new StringBuilder();
195 if (this.contextRelative && getUrl().startsWith("/")) {
196 // Do not apply context path to relative URLs.
197 targetUrl.append(request.getContextPath());
198 }
199 targetUrl.append(getUrl());
200 //change the following method to accept a StringBuilder instead of a StringBuilder for Shiro 2.x:
201 appendQueryProperties(targetUrl, model, this.encodingScheme);
202
203 sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
204 }
205
206 /**
207 * Append query properties to the redirect URL.
208 * Stringifies, URL-encodes and formats model attributes as query properties.
209 *
210 * @param targetUrl the StringBuffer to append the properties to
211 * @param model Map that contains model attributes
212 * @param encodingScheme the encoding scheme to use
213 * @throws java.io.UnsupportedEncodingException if string encoding failed
214 * @see #urlEncode
215 * @see #queryProperties
216 * @see #urlEncode(String, String)
217 */
218 protected void appendQueryProperties(StringBuilder targetUrl, Map model, String encodingScheme)
219 throws UnsupportedEncodingException {
220
221 // Extract anchor fragment, if any.
222 // The following code does not use JDK 1.4's StringBuffer.indexOf(String)
223 // method to retain JDK 1.3 compatibility.
224 String fragment = null;
225 int anchorIndex = targetUrl.toString().indexOf('#');
226 if (anchorIndex > -1) {
227 fragment = targetUrl.substring(anchorIndex);
228 targetUrl.delete(anchorIndex, targetUrl.length());
229 }
230
231 // If there aren't already some parameters, we need a "?".
232 boolean first = (getUrl().indexOf('?') < 0);
233 Map queryProps = queryProperties(model);
234
235 if (queryProps != null) {
236 for (Object o : queryProps.entrySet()) {
237 if (first) {
238 targetUrl.append('?');
239 first = false;
240 } else {
241 targetUrl.append('&');
242 }
243 Map.Entry entry = (Map.Entry) o;
244 String encodedKey = urlEncode(entry.getKey().toString(), encodingScheme);
245 String encodedValue =
246 (entry.getValue() != null ? urlEncode(entry.getValue().toString(), encodingScheme) : "");
247 targetUrl.append(encodedKey).append('=').append(encodedValue);
248 }
249 }
250
251 // Append anchor fragment, if any, to end of URL.
252 if (fragment != null) {
253 targetUrl.append(fragment);
254 }
255 }
256
257 /**
258 * URL-encode the given input String with the given encoding scheme, using
259 * {@link URLEncoder#encode(String, String) URLEncoder.encode(input, enc)}.
260 *
261 * @param input the unencoded input String
262 * @param encodingScheme the encoding scheme
263 * @return the encoded output String
264 * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder
265 * @see java.net.URLEncoder#encode(String, String)
266 * @see java.net.URLEncoder#encode(String)
267 */
268 protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException {
269 return URLEncoder.encode(input, encodingScheme);
270 }
271
272 /**
273 * Determine name-value pairs for query strings, which will be stringified,
274 * URL-encoded and formatted by appendQueryProperties.
275 * <p/>
276 * This implementation returns all model elements as-is.
277 *
278 * @param model the model elements for which to determine name-value pairs.
279 * @return the name-value pairs for query strings.
280 * @see #appendQueryProperties
281 */
282 protected Map queryProperties(Map model) {
283 return model;
284 }
285
286 /**
287 * Send a redirect back to the HTTP client
288 *
289 * @param request current HTTP request (allows for reacting to request method)
290 * @param response current HTTP response (for sending response headers)
291 * @param targetUrl the name URL to redirect to
292 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
293 * @throws IOException if thrown by response methods
294 */
295 @SuppressWarnings({"UnusedDeclaration"})
296 protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
297 String targetUrl, boolean http10Compatible) throws IOException {
298 if (http10Compatible) {
299 // Always send status code 302.
300 response.sendRedirect(response.encodeRedirectURL(targetUrl));
301 } else {
302 // Correct HTTP status code is 303, in particular for POST requests.
303 response.setStatus(303);
304 response.setHeader("Location", response.encodeRedirectURL(targetUrl));
305 }
306 }
307
308 }