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}