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 */ 019package org.apache.shiro.web.util; 020 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.http.HttpServletResponse; 023import java.io.IOException; 024import java.io.UnsupportedEncodingException; 025import java.net.URLEncoder; 026import 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 */ 063public 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; 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 protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, 296 String targetUrl, boolean http10Compatible) throws IOException { 297 String encodedRedirectURL = response.encodeRedirectURL(targetUrl); 298 if (http10Compatible) { 299 // Always send status code 302. 300 response.sendRedirect(encodedRedirectURL); 301 } else { 302 // Correct HTTP status code is 303, in particular for POST requests. 303 response.setStatus(HttpServletResponse.SC_SEE_OTHER); 304 response.setHeader("Location", encodedRedirectURL); 305 } 306 } 307 308}