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 }