View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.util;
20  
21  import org.apache.shiro.SecurityUtils;
22  import org.apache.shiro.session.Session;
23  import org.apache.shiro.subject.Subject;
24  import org.apache.shiro.subject.support.DefaultSubjectContext;
25  import org.apache.shiro.lang.util.StringUtils;
26  import org.apache.shiro.web.env.EnvironmentLoader;
27  import org.apache.shiro.web.env.WebEnvironment;
28  import org.apache.shiro.web.filter.AccessControlFilter;
29  import org.owasp.encoder.Encode;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import javax.servlet.ServletContext;
34  import javax.servlet.ServletRequest;
35  import javax.servlet.ServletResponse;
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  import java.io.IOException;
39  import java.io.UnsupportedEncodingException;
40  import java.net.URLDecoder;
41  import java.util.Map;
42  
43  /**
44   * Simple utility class for operations used across multiple class hierarchies in the web framework code.
45   * <p/>
46   * Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
47   * and in these cases, we have retained all license, copyright and author information.
48   *
49   * @since 0.9
50   */
51  @SuppressWarnings({"checkstyle:MethodCount", "checkstyle:JavadocVariable"})
52  public final class WebUtils {
53  
54      public static final String SERVLET_REQUEST_KEY = ServletRequest.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
55      public static final String SERVLET_RESPONSE_KEY = ServletResponse.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
56  
57      public static final String ALLOW_BACKSLASH = "org.apache.shiro.web.ALLOW_BACKSLASH";
58  
59      /**
60       * {@link org.apache.shiro.session.Session Session} key used to save a request and later restore it,
61       * for example when redirecting to a
62       * requested page after login, equal to {@code shiroSavedRequest}.
63       */
64      public static final String SAVED_REQUEST_KEY = "shiroSavedRequest";
65  
66      /**
67       * Standard Servlet 2.3+ spec request attributes for include URI and paths.
68       * <p>If included via a RequestDispatcher, the current resource will see the
69       * originating request. Its own URI and paths are exposed as request attributes.
70       */
71      public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
72      public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
73      public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
74      public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
75      public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
76  
77      /**
78       * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
79       * <p>If forwarded to via a RequestDispatcher, the current resource will see its
80       * own URI and paths. The originating URI and paths are exposed as request attributes.
81       */
82      public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
83      public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
84      public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
85      public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
86      public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
87  
88      /**
89       * Default character encoding to use when <code>request.getCharacterEncoding</code>
90       * returns <code>null</code>, according to the Servlet spec.
91       *
92       * @see javax.servlet.ServletRequest#getCharacterEncoding
93       */
94      public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
95  
96      private static boolean isAllowBackslash = Boolean.getBoolean(ALLOW_BACKSLASH);
97  
98      private static final Logger LOGGER = LoggerFactory.getLogger(WebUtils.class);
99  
100     private WebUtils() {
101     }
102 
103     /**
104      * Return the path within the web application for the given request.
105      * Detects include request URL if called within a RequestDispatcher include.
106      * <p/>
107      * For example, for a request to URL
108      * <p/>
109      * <code>http://www.somehost.com/myapp/my/url.jsp</code>,
110      * <p/>
111      * for an application deployed to <code>/mayapp</code> (the application's context path), this method would return
112      * <p/>
113      * <code>/my/url.jsp</code>.
114      *
115      * @param request current HTTP request
116      * @return the path within the web application
117      */
118     public static String getPathWithinApplication(HttpServletRequest request) {
119         return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
120     }
121 
122     /**
123      * Return the request URI for the given request, detecting an include request
124      * URL if called within a RequestDispatcher include.
125      * <p>As the value returned by <code>request.getRequestURI()</code> is <i>not</i>
126      * decoded by the servlet container, this method will decode it.
127      * <p>The URI that the web container resolves <i>should</i> be correct, but some
128      * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
129      * in the URI. This method cuts off such incorrect appendices.
130      *
131      * @param request current HTTP request
132      * @return the request URI
133      * @deprecated use getPathWithinApplication() to get the path minus the context path,
134      * or call HttpServletRequest.getRequestURI() directly from your code.
135      */
136     @Deprecated
137     public static String getRequestUri(HttpServletRequest request) {
138         String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
139         if (uri == null) {
140             uri = request.getRequestURI();
141         }
142         return normalize(decodeAndCleanUriString(request, uri));
143     }
144 
145     private static String getServletPath(HttpServletRequest request) {
146         String servletPath = (String) request.getAttribute(INCLUDE_SERVLET_PATH_ATTRIBUTE);
147         return servletPath != null ? servletPath : valueOrEmpty(request.getServletPath());
148     }
149 
150     private static String getPathInfo(HttpServletRequest request) {
151         String pathInfo = (String) request.getAttribute(INCLUDE_PATH_INFO_ATTRIBUTE);
152         return pathInfo != null ? pathInfo : valueOrEmpty(request.getPathInfo());
153     }
154 
155     private static String valueOrEmpty(String input) {
156         if (input == null) {
157             return "";
158         }
159         return input;
160     }
161 
162     /**
163      * Normalize a relative URI path that may have relative values ("/./",
164      * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
165      * useful only for normalizing application-generated paths.  It does not
166      * try to perform security checks for malicious input.
167      * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
168      * Tomcat trunk, r939305
169      *
170      * @param path Relative path to be normalized
171      * @return normalized path
172      */
173     public static String normalize(String path) {
174         return normalize(path, isAllowBackslash);
175     }
176 
177     /**
178      * Normalize a relative URI path that may have relative values ("/./",
179      * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
180      * useful only for normalizing application-generated paths.  It does not
181      * try to perform security checks for malicious input.
182      * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
183      * Tomcat trunk, r939305
184      *
185      * @param path             Relative path to be normalized
186      * @param replaceBackSlash Should '\\' be replaced with '/'
187      * @return normalized path
188      */
189     @SuppressWarnings({"checkstyle:CyclomaticComplexity", "checkstyle:NPathComplexity"})
190     private static String normalize(String path, boolean replaceBackSlash) {
191 
192         if (path == null) {
193             return null;
194         }
195 
196         // Create a place for the normalized path
197         String normalized = path;
198 
199         if (replaceBackSlash && normalized.indexOf('\\') >= 0) {
200             normalized = normalized.replace('\\', '/');
201         }
202 
203         if (normalized.equals("/.")) {
204             return "/";
205         }
206 
207         // Add a leading "/" if necessary
208         if (!normalized.startsWith("/")) {
209             normalized = "/" + normalized;
210         }
211 
212         // Resolve occurrences of "//" in the normalized path
213         while (true) {
214             int index = normalized.indexOf("//");
215             if (index < 0) {
216                 break;
217             }
218             normalized = normalized.substring(0, index) + normalized.substring(index + 1);
219         }
220 
221         // Resolve occurrences of "/./" in the normalized path
222         while (true) {
223             int index = normalized.indexOf("/./");
224             if (index < 0) {
225                 break;
226             }
227             normalized = normalized.substring(0, index) + normalized.substring(index + 2);
228         }
229 
230         // Resolve occurrences of "/../" in the normalized path
231         while (true) {
232             int index = normalized.indexOf("/../");
233             if (index < 0) {
234                 break;
235             }
236             if (index == 0) {
237                 // Trying to go outside our context
238                 return (null);
239             }
240             int index2 = normalized.lastIndexOf('/', index - 1);
241             normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
242         }
243 
244         // Return the normalized path that we have completed
245         return (normalized);
246 
247     }
248 
249 
250     /**
251      * Decode the supplied URI string and strips any extraneous portion after a ';'.
252      *
253      * @param request the incoming HttpServletRequest
254      * @param uri     the application's URI string
255      * @return the supplied URI string stripped of any extraneous portion after a ';'.
256      */
257     private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
258         uri = decodeRequestString(request, uri);
259         return removeSemicolon(uri);
260     }
261 
262     private static String removeSemicolon(String uri) {
263         int semicolonIndex = uri.indexOf(';');
264         return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
265     }
266 
267     /**
268      * Return the context path for the given request, detecting an include request
269      * URL if called within a RequestDispatcher include.
270      * <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
271      * decoded by the servlet container, this method will decode it.
272      *
273      * @param request current HTTP request
274      * @return the context path
275      */
276     public static String getContextPath(HttpServletRequest request) {
277         String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
278         if (contextPath == null) {
279             contextPath = request.getContextPath();
280         }
281         contextPath = normalize(decodeRequestString(request, contextPath));
282         if ("/".equals(contextPath)) {
283             // the normalize method will return a "/" and includes on Jetty, will also be a "/".
284             contextPath = "";
285         }
286         return contextPath;
287     }
288 
289     /**
290      * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via the
291      * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
292      * <p/>
293      * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
294      * environment startup and no environment at all.
295      *
296      * @param sc ServletContext to find the web application context for
297      * @return the root WebApplicationContext for this web app
298      * @throws IllegalStateException if the root WebApplicationContext could not be found
299      * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
300      * @since 1.2
301      */
302     public static WebEnvironment getRequiredWebEnvironment(ServletContext sc)
303             throws IllegalStateException {
304 
305         WebEnvironment we = getWebEnvironment(sc);
306         if (we == null) {
307             throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?");
308         }
309         return we;
310     }
311 
312     /**
313      * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via
314      * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
315      * <p/>
316      * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
317      * environment startup and no environment at all.
318      *
319      * @param sc ServletContext to find the web application context for
320      * @return the root WebApplicationContext for this web app, or <code>null</code> if none
321      * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
322      * @since 1.2
323      */
324     public static WebEnvironment getWebEnvironment(ServletContext sc) {
325         return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY);
326     }
327 
328     /**
329      * Find the Shiro {@link WebEnvironment} for this web application.
330      *
331      * @param sc       ServletContext to find the web application context for
332      * @param attrName the name of the ServletContext attribute to look for
333      * @return the desired WebEnvironment for this web app, or <code>null</code> if none
334      * @since 1.2
335      */
336     public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) {
337         if (sc == null) {
338             throw new IllegalArgumentException("ServletContext argument must not be null.");
339         }
340         Object attr = sc.getAttribute(attrName);
341         if (attr == null) {
342             return null;
343         }
344         if (attr instanceof RuntimeException) {
345             throw (RuntimeException) attr;
346         }
347         if (attr instanceof Error) {
348             throw (Error) attr;
349         }
350         if (attr instanceof Exception) {
351             throw new IllegalStateException((Exception) attr);
352         }
353         if (!(attr instanceof WebEnvironment)) {
354             throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr);
355         }
356         return (WebEnvironment) attr;
357     }
358 
359 
360     /**
361      * Decode the given source string with a URLDecoder. The encoding will be taken
362      * from the request, falling back to the default "ISO-8859-1".
363      * <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
364      *
365      * @param request current HTTP request
366      * @param source  the String to decode
367      * @return the decoded String
368      * @see #DEFAULT_CHARACTER_ENCODING
369      * @see javax.servlet.ServletRequest#getCharacterEncoding
370      * @see java.net.URLDecoder#decode(String, String)
371      * @see java.net.URLDecoder#decode(String)
372      */
373     @SuppressWarnings("deprecation")
374     public static String decodeRequestString(HttpServletRequest request, String source) {
375         String enc = determineEncoding(request);
376         try {
377             return URLDecoder.decode(source, enc);
378         } catch (UnsupportedEncodingException ex) {
379             if (LOGGER.isWarnEnabled()) {
380                 LOGGER.warn("Could not decode request string [" + Encode.forHtml(source)
381                         + "] with encoding '" + Encode.forHtml(enc)
382                         + "': falling back to platform default encoding; exception message: " + ex.getMessage());
383             }
384             return URLDecoder.decode(source);
385         }
386     }
387 
388     /**
389      * Determine the encoding for the given request.
390      * Can be overridden in subclasses.
391      * <p>The default implementation checks the request's
392      * {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
393      * <code>null</code>, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
394      *
395      * @param request current HTTP request
396      * @return the encoding for the request (never <code>null</code>)
397      * @see javax.servlet.ServletRequest#getCharacterEncoding()
398      */
399     protected static String determineEncoding(HttpServletRequest request) {
400         String enc = request.getCharacterEncoding();
401         if (enc == null) {
402             enc = DEFAULT_CHARACTER_ENCODING;
403         }
404         return enc;
405     }
406 
407     /*
408      * Returns {@code true} IFF the specified {@code SubjectContext}:
409      * <ol>
410      * <li>A {@link WebSubjectContext} instance</li>
411      * <li>The {@code WebSubjectContext}'s request/response pair are not null</li>
412      * <li>The request is an {@link HttpServletRequest} instance</li>
413      * <li>The response is an {@link HttpServletResponse} instance</li>
414      * </ol>
415      *
416      * @param context the SubjectContext to check to see if it is HTTP compatible.
417      * @return {@code true} IFF the specified context has HTTP request/response objects, {@code false} otherwise.
418      * @since 1.0
419      */
420 
421     public static boolean isWeb(Object requestPairSource) {
422         return requestPairSource instanceof RequestPairSource && isWeb((RequestPairSource) requestPairSource);
423     }
424 
425     public static boolean isHttp(Object requestPairSource) {
426         return requestPairSource instanceof RequestPairSource && isHttp((RequestPairSource) requestPairSource);
427     }
428 
429     public static ServletRequest getRequest(Object requestPairSource) {
430         if (requestPairSource instanceof RequestPairSource) {
431             return ((RequestPairSource) requestPairSource).getServletRequest();
432         }
433         return null;
434     }
435 
436     public static ServletResponse getResponse(Object requestPairSource) {
437         if (requestPairSource instanceof RequestPairSource) {
438             return ((RequestPairSource) requestPairSource).getServletResponse();
439         }
440         return null;
441     }
442 
443     public static HttpServletRequest getHttpRequest(Object requestPairSource) {
444         ServletRequest request = getRequest(requestPairSource);
445         if (request instanceof HttpServletRequest) {
446             return (HttpServletRequest) request;
447         }
448         return null;
449     }
450 
451     public static HttpServletResponse getHttpResponse(Object requestPairSource) {
452         ServletResponse response = getResponse(requestPairSource);
453         if (response instanceof HttpServletResponse) {
454             return (HttpServletResponse) response;
455         }
456         return null;
457     }
458 
459     private static boolean isWeb(RequestPairSource source) {
460         ServletRequest request = source.getServletRequest();
461         ServletResponse response = source.getServletResponse();
462         return request != null && response != null;
463     }
464 
465     private static boolean isHttp(RequestPairSource source) {
466         ServletRequest request = source.getServletRequest();
467         ServletResponse response = source.getServletResponse();
468         return request instanceof HttpServletRequest && response instanceof HttpServletResponse;
469     }
470 
471     /**
472      * Returns {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
473      * otherwise.
474      * <p/>
475      * <b>This method exists for Shiro's internal framework needs and should never be called by Shiro end-users.  It
476      * could be changed/removed at any time.</b>
477      *
478      * @param requestPairSource a {@link RequestPairSource} instance, almost always a
479      *                          {@link org.apache.shiro.web.subject.WebSubject WebSubject} instance.
480      * @return {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
481      * otherwise.
482      */
483     public static boolean isSessionCreationEnabled(Object requestPairSource) {
484         if (requestPairSource instanceof RequestPairSource) {
485             RequestPairSource source = (RequestPairSource) requestPairSource;
486             return isSessionCreationEnabled(source.getServletRequest());
487         }
488         //by default
489         return true;
490     }
491 
492     /**
493      * Returns {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
494      * otherwise.
495      * <p/>
496      * <b>This method exists for Shiro's internal framework needs and should never be called by Shiro end-users.  It
497      * could be changed/removed at any time.</b>
498      *
499      * @param request incoming servlet request.
500      * @return {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
501      * otherwise.
502      */
503     public static boolean isSessionCreationEnabled(ServletRequest request) {
504         if (request != null) {
505             Object val = request.getAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED);
506             if (val != null && val instanceof Boolean) {
507                 return (Boolean) val;
508             }
509         }
510         //by default
511         return true;
512     }
513 
514     /**
515      * A convenience method that merely casts the incoming <code>ServletRequest</code> to an
516      * <code>HttpServletRequest</code>:
517      * <p/>
518      * <code>return (HttpServletRequest)request;</code>
519      * <p/>
520      * Logic could be changed in the future for logging or throwing an meaningful exception in
521      * non HTTP request environments (e.g. Portlet API).
522      *
523      * @param request the incoming ServletRequest
524      * @return the <code>request</code> argument casted to an <code>HttpServletRequest</code>.
525      */
526     public static HttpServletRequest toHttp(ServletRequest request) {
527         return (HttpServletRequest) request;
528     }
529 
530     /**
531      * A convenience method that merely casts the incoming <code>ServletResponse</code> to an
532      * <code>HttpServletResponse</code>:
533      * <p/>
534      * <code>return (HttpServletResponse)response;</code>
535      * <p/>
536      * Logic could be changed in the future for logging or throwing an meaningful exception in
537      * non HTTP request environments (e.g. Portlet API).
538      *
539      * @param response the outgoing ServletResponse
540      * @return the <code>response</code> argument casted to an <code>HttpServletResponse</code>.
541      */
542     public static HttpServletResponse toHttp(ServletResponse response) {
543         return (HttpServletResponse) response;
544     }
545 
546     /**
547      * Redirects the current request to a new URL based on the given parameters.
548      *
549      * @param request          the servlet request.
550      * @param response         the servlet response.
551      * @param url              the URL to redirect the user to.
552      * @param queryParams      a map of parameters that should be set as request parameters for the new request.
553      * @param contextRelative  true if the URL is relative to the servlet context path, or false if the URL is absolute.
554      * @param http10Compatible whether to stay compatible with HTTP 1.0 clients.
555      * @throws java.io.IOException if thrown by response methods.
556      */
557     public static void issueRedirect(ServletRequest request, ServletResponse response, String url,
558                                      Map queryParams, boolean contextRelative,
559                                      boolean http10Compatible) throws IOException {
560         RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
561         view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
562     }
563 
564     /**
565      * Redirects the current request to a new URL based on the given parameters and default values
566      * for unspecified parameters.
567      *
568      * @param request  the servlet request.
569      * @param response the servlet response.
570      * @param url      the URL to redirect the user to.
571      * @throws java.io.IOException if thrown by response methods.
572      */
573     public static void issueRedirect(ServletRequest request, ServletResponse response, String url) throws IOException {
574         issueRedirect(request, response, url, null, true, true);
575     }
576 
577     /**
578      * Redirects the current request to a new URL based on the given parameters and default values
579      * for unspecified parameters.
580      *
581      * @param request     the servlet request.
582      * @param response    the servlet response.
583      * @param url         the URL to redirect the user to.
584      * @param queryParams a map of parameters that should be set as request parameters for the new request.
585      * @throws java.io.IOException if thrown by response methods.
586      */
587     public static void issueRedirect(ServletRequest request,
588                                      ServletResponse response, String url, Map queryParams) throws IOException {
589         issueRedirect(request, response, url, queryParams, true, true);
590     }
591 
592     /**
593      * Redirects the current request to a new URL based on the given parameters and default values
594      * for unspecified parameters.
595      *
596      * @param request         the servlet request.
597      * @param response        the servlet response.
598      * @param url             the URL to redirect the user to.
599      * @param queryParams     a map of parameters that should be set as request parameters for the new request.
600      * @param contextRelative true if the URL is relative to the servlet context path, or false if the URL is absolute.
601      * @throws java.io.IOException if thrown by response methods.
602      */
603     public static void issueRedirect(ServletRequest request,
604                                      ServletResponse response, String url, Map queryParams, boolean contextRelative)
605             throws IOException {
606         issueRedirect(request, response, url, queryParams, contextRelative, true);
607     }
608 
609     /**
610      * <p>Checks to see if a request param is considered true using a loose matching strategy for
611      * general values that indicate that something is true or enabled, etc.</p>
612      * <p/>
613      * <p>Values that are considered "true" include (case-insensitive): true, t, 1, enabled, y, yes, on.</p>
614      *
615      * @param request   the servlet request
616      * @param paramName @return true if the param value is considered true or false if it isn't.
617      * @return true if the given parameter is considered "true" - false otherwise.
618      */
619     @SuppressWarnings("checkstyle:BooleanExpressionComplexity")
620     public static boolean isTrue(ServletRequest request, String paramName) {
621         String value = getCleanParam(request, paramName);
622         return value != null
623                 && (value.equalsIgnoreCase("true")
624                 || value.equalsIgnoreCase("t")
625                 || value.equalsIgnoreCase("1")
626                 || value.equalsIgnoreCase("enabled")
627                 || value.equalsIgnoreCase("y")
628                 || value.equalsIgnoreCase("yes")
629                 || value.equalsIgnoreCase("on"));
630     }
631 
632     /**
633      * Convenience method that returns a request parameter value, first running it through
634      * {@link StringUtils#clean(String)}.
635      *
636      * @param request   the servlet request.
637      * @param paramName the parameter name.
638      * @return the clean param value, or null if the param does not exist or is empty.
639      */
640     public static String getCleanParam(ServletRequest request, String paramName) {
641         return StringUtils.clean(request.getParameter(paramName));
642     }
643 
644     public static void saveRequest(ServletRequest request) {
645         Subject subject = SecurityUtils.getSubject();
646         Session session = subject.getSession();
647         HttpServletRequest httpRequest = toHttp(request);
648         SavedRequest savedRequest = new SavedRequest(httpRequest);
649         session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
650     }
651 
652     public static SavedRequest getAndClearSavedRequest(ServletRequest request) {
653         SavedRequest savedRequest = getSavedRequest(request);
654         if (savedRequest != null) {
655             Subject subject = SecurityUtils.getSubject();
656             Session session = subject.getSession();
657             session.removeAttribute(SAVED_REQUEST_KEY);
658         }
659         return savedRequest;
660     }
661 
662     public static SavedRequest getSavedRequest(ServletRequest request) {
663         SavedRequest savedRequest = null;
664         Subject subject = SecurityUtils.getSubject();
665         Session session = subject.getSession(false);
666         if (session != null) {
667             savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_KEY);
668         }
669         return savedRequest;
670     }
671 
672     /**
673      * Redirects the to the request url from a previously
674      * {@link #saveRequest(javax.servlet.ServletRequest) saved} request, or if there is no saved request, redirects the
675      * end user to the specified {@code fallbackUrl}.  If there is no saved request or fallback url, this method
676      * throws an {@link IllegalStateException}.
677      * <p/>
678      * This method is primarily used to support a common login scenario - if an unauthenticated user accesses a
679      * page that requires authentication, it is expected that request is
680      * {@link #saveRequest(javax.servlet.ServletRequest) saved} first and then redirected to the login page. Then,
681      * after a successful login, this method can be called to redirect them back to their originally requested URL, a
682      * nice usability feature.
683      *
684      * @param request     the incoming request
685      * @param response    the outgoing response
686      * @param fallbackUrl the fallback url to redirect to if there is no saved request available.
687      * @throws IllegalStateException if there is no saved request and the {@code fallbackUrl} is {@code null}.
688      * @throws IOException           if there is an error redirecting
689      * @since 1.0
690      */
691     public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
692             throws IOException {
693         String successUrl = null;
694         boolean contextRelative = true;
695         SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
696         if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
697             successUrl = savedRequest.getRequestUrl();
698             contextRelative = false;
699         }
700 
701         if (successUrl == null) {
702             successUrl = fallbackUrl;
703         }
704 
705         if (successUrl == null) {
706             throw new IllegalStateException("Success URL not available via saved request or via the "
707                     + "successUrlFallback method parameter. One of these must be non-null for "
708                     + "issueSuccessRedirect() to work.");
709         }
710 
711         WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
712     }
713 
714     public static boolean isAllowBackslash() {
715         return isAllowBackslash;
716     }
717 
718     /**
719      * Reload System properties.
720      * If relevant system property is modified ,this method needs to be called to take effect.
721      * There is a property <code>org.apache.shiro.web.ALLOW_BACKSLASH</code> that needs attention.
722      */
723     public static void reloadSystemProperties() {
724         isAllowBackslash = Boolean.getBoolean(ALLOW_BACKSLASH);
725     }
726 
727 }