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 org.apache.shiro.SecurityUtils;
022import org.apache.shiro.session.Session;
023import org.apache.shiro.subject.Subject;
024import org.apache.shiro.subject.support.DefaultSubjectContext;
025import org.apache.shiro.lang.util.StringUtils;
026import org.apache.shiro.web.env.EnvironmentLoader;
027import org.apache.shiro.web.env.WebEnvironment;
028import org.apache.shiro.web.filter.AccessControlFilter;
029import org.owasp.encoder.Encode;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import javax.servlet.ServletContext;
034import javax.servlet.ServletRequest;
035import javax.servlet.ServletResponse;
036import javax.servlet.http.HttpServletRequest;
037import javax.servlet.http.HttpServletResponse;
038import java.io.IOException;
039import java.io.UnsupportedEncodingException;
040import java.net.URLDecoder;
041import java.util.Map;
042
043/**
044 * Simple utility class for operations used across multiple class hierarchies in the web framework code.
045 * <p/>
046 * Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
047 * and in these cases, we have retained all license, copyright and author information.
048 *
049 * @since 0.9
050 */
051@SuppressWarnings({"checkstyle:MethodCount", "checkstyle:JavadocVariable"})
052public final class WebUtils {
053
054    public static final String SERVLET_REQUEST_KEY = ServletRequest.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
055    public static final String SERVLET_RESPONSE_KEY = ServletResponse.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
056
057    public static final String ALLOW_BACKSLASH = "org.apache.shiro.web.ALLOW_BACKSLASH";
058
059    /**
060     * {@link org.apache.shiro.session.Session Session} key used to save a request and later restore it,
061     * for example when redirecting to a
062     * requested page after login, equal to {@code shiroSavedRequest}.
063     */
064    public static final String SAVED_REQUEST_KEY = "shiroSavedRequest";
065
066    /**
067     * Standard Servlet 2.3+ spec request attributes for include URI and paths.
068     * <p>If included via a RequestDispatcher, the current resource will see the
069     * originating request. Its own URI and paths are exposed as request attributes.
070     */
071    public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
072    public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
073    public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
074    public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
075    public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
076
077    /**
078     * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
079     * <p>If forwarded to via a RequestDispatcher, the current resource will see its
080     * own URI and paths. The originating URI and paths are exposed as request attributes.
081     */
082    public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
083    public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
084    public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
085    public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
086    public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
087
088    /**
089     * Default character encoding to use when <code>request.getCharacterEncoding</code>
090     * returns <code>null</code>, according to the Servlet spec.
091     *
092     * @see javax.servlet.ServletRequest#getCharacterEncoding
093     */
094    public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
095
096    private static boolean isAllowBackslash = Boolean.getBoolean(ALLOW_BACKSLASH);
097
098    private static final Logger LOGGER = LoggerFactory.getLogger(WebUtils.class);
099
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}