
com.stormpath.sdk.servlet.util.ServletUtils Maven / Gradle / Ivy
/*
* Copyright 2014 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.servlet.util;
import com.stormpath.sdk.application.Application;
import com.stormpath.sdk.client.Client;
import com.stormpath.sdk.lang.Strings;
import com.stormpath.sdk.servlet.Servlets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;
public class ServletUtils {
private static final Logger log = LoggerFactory.getLogger(ServletUtils.class);
/**
* Standard Servlet 2.3+ spec request attributes for include URI and paths.
* If included via a RequestDispatcher, the current resource will see the
* originating request. Its own URI and paths are exposed as request attributes.
*/
public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
/**
* Standard Servlet 2.4+ spec request attributes for forward URI and paths.
*
If forwarded to via a RequestDispatcher, the current resource will see its
* own URI and paths. The originating URI and paths are exposed as request attributes.
*/
public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
/**
* Default character encoding to use when request.getCharacterEncoding
* returns null
, according to the Servlet spec.
*
* @see javax.servlet.ServletRequest#getCharacterEncoding
*/
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
/**
* Return the context-relative URI within the web application for the given request. This implementation will
* properly detect include request URLs if called within a RequestDispatcher include.
*
*
For example, for an application deployed with a base context of {@code /myapp} and a request URL of
* http://www.somehost.com/myapp/my/url.jsp
, this method would return /my/url.jsp
*
* @param request current HTTP request
* @return the context-relative URI within the web application
*/
public static String getContextRelativeUri(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
if (Strings.startsWithIgnoreCase(requestUri, contextPath)) {
// Normal case: URI contains context path.
String contextRelativeUri = requestUri.substring(contextPath.length());
return (Strings.hasText(contextRelativeUri) ? contextRelativeUri : "/");
} else {
// Special case: rather unusual.
return requestUri;
}
}
/**
* Return the request URI for the given request, detecting an include request
* URL if called within a RequestDispatcher include.
* As the value returned by request.getRequestURI()
is not
* decoded by the servlet container, this method will decode it.
*
The URI that the web container resolves should be correct, but some
* containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
* in the URI. This method cuts off such incorrect appendices.
*
* @param request current HTTP request
* @return the request URI
*/
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
uri = request.getRequestURI();
}
return normalize(decodeAndCleanUriString(request, uri));
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. WARNING - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
* Tomcat trunk, r939305
*
* @param path Relative path to be normalized
* @return normalized path
*/
public static String normalize(String path) {
return normalize(path, true);
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. WARNING - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
* Tomcat trunk, r939305
*
* @param path Relative path to be normalized
* @param replaceBackSlash Should '\\' be replaced with '/'
* @return normalized path
*/
private static String normalize(String path, boolean replaceBackSlash) {
if (path == null) {
return null;
}
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\\') >= 0) {
normalized = normalized.replace('\\', '/');
}
if (normalized.equals("/.")) {
return "/";
}
// Add a leading "/" if necessary
if (!normalized.startsWith("/")) {
normalized = "/" + normalized;
}
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0) {
break;
}
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0) {
break;
}
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0) {
break;
}
if (index == 0) {
return (null); // Trying to go outside our context
}
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
/**
* Decode the supplied URI string and strips any extraneous portion after a ';'.
*
* @param request the incoming HttpServletRequest
* @param uri the application's URI string
* @return the supplied URI string stripped of any extraneous portion after a ';'.
*/
public static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = decodeRequestString(request, uri);
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
/**
* Return the context path for the given request, detecting an include request
* URL if called within a RequestDispatcher include.
*
As the value returned by request.getContextPath()
is not
* decoded by the servlet container, this method will decode it.
*
* @param request current HTTP request
* @return the context path
*/
public static String getContextPath(HttpServletRequest request) {
String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
if (contextPath == null) {
contextPath = request.getContextPath();
}
if ("/".equals(contextPath)) {
// Invalid case, but happens for includes on Jetty: silently adapt it.
contextPath = "";
}
return decodeRequestString(request, contextPath);
}
public static Client getClient(ServletRequest request) {
return getClient(request.getServletContext());
}
public static Client getClient(ServletContext sc) {
return Servlets.getClient(sc);
}
public static Application getApplication(ServletRequest request) {
return getApplication(request.getServletContext());
}
public static Application getApplication(ServletContext sc) {
return (Application)sc.getAttribute(Application.class.getName());
}
/**
* Decode the given source string with a URLDecoder. The encoding will be taken
* from the request, falling back to the default "ISO-8859-1".
*
The default implementation uses URLDecoder.decode(input, enc)
.
*
* @param request current HTTP request
* @param source the String to decode
* @return the decoded String
* @see #DEFAULT_CHARACTER_ENCODING
* @see javax.servlet.ServletRequest#getCharacterEncoding
* @see java.net.URLDecoder#decode(String, String)
* @see java.net.URLDecoder#decode(String)
*/
public static String decodeRequestString(HttpServletRequest request, String source) {
String enc = determineEncoding(request);
try {
return URLDecoder.decode(source, enc);
} catch (UnsupportedEncodingException ex) {
if (log.isWarnEnabled()) {
log.warn("Could not decode request string [" + source + "] with encoding '" + enc +
"': falling back to platform default encoding; exception message: " + ex.getMessage());
}
try {
return URLDecoder.decode(source, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
String msg = "ISO-8859-1 encoding is not available as a fallback." + e.getMessage();
throw new IllegalStateException(msg, e);
}
}
}
/**
* Determine the encoding for the given request.
* Can be overridden in subclasses.
*
The default implementation checks the request's
* {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
* null
, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
*
* @param request current HTTP request
* @return the encoding for the request (never null
)
* @see javax.servlet.ServletRequest#getCharacterEncoding()
*/
protected static String determineEncoding(HttpServletRequest request) {
String enc = request.getCharacterEncoding();
if (enc == null) {
enc = DEFAULT_CHARACTER_ENCODING;
}
return enc;
}
/**
* Redirects the current request to a new URL based on the given parameters.
*
* @param request the servlet request.
* @param response the servlet response.
* @param url the URL to redirect the user to.
* @param queryParams a map of parameters that should be set as request parameters for the new request.
* @param contextRelative true if the URL is relative to the servlet context path, or false if the URL is
* absolute.
* @param http10Compatible whether to stay compatible with HTTP 1.0 clients.
* @throws java.io.IOException if thrown by response methods.
*/
public static void issueRedirect(HttpServletRequest request, HttpServletResponse response, String url,
Map queryParams, boolean contextRelative, boolean http10Compatible)
throws IOException {
String targetUrl = new RedirectUrlBuilder(request).setUrl(url).setQueryParameters(queryParams)
.setContextRelative(contextRelative).build();
if (http10Compatible) {
// Always send status code 302.
response.sendRedirect(response.encodeRedirectURL(targetUrl));
} else {
// Correct HTTP status code is 303, in particular for POST requests.
response.setStatus(303);
response.setHeader("Location", response.encodeRedirectURL(targetUrl));
}
}
/**
* Redirects the current request to a new URL based on the given parameters and default values
* for unspecified parameters.
*
* @param request the servlet request.
* @param response the servlet response.
* @param url the URL to redirect the user to.
* @throws java.io.IOException if thrown by response methods.
*/
public static void issueRedirect(HttpServletRequest request, HttpServletResponse response, String url)
throws IOException {
issueRedirect(request, response, url, null, true, true);
}
/**
* Redirects the current request to a new URL based on the given parameters and default values
* for unspecified parameters.
*
* @param request the servlet request.
* @param response the servlet response.
* @param url the URL to redirect the user to.
* @param queryParams a map of parameters that should be set as request parameters for the new request.
* @throws java.io.IOException if thrown by response methods.
*/
public static void issueRedirect(HttpServletRequest request, HttpServletResponse response, String url,
Map queryParams)
throws IOException {
issueRedirect(request, response, url, queryParams, true, true);
}
/**
* Redirects the current request to a new URL based on the given parameters and default values
* for unspecified parameters.
*
* @param request the servlet request.
* @param response the servlet response.
* @param url the URL to redirect the user to.
* @param queryParams a map of parameters that should be set as request parameters for the new request.
* @param contextRelative true if the URL is relative to the servlet context path, or false if the URL is absolute.
* @throws java.io.IOException if thrown by response methods.
*/
public static void issueRedirect(HttpServletRequest request, HttpServletResponse response, String url,
Map queryParams,
boolean contextRelative) throws IOException {
issueRedirect(request, response, url, queryParams, contextRelative, true);
}
/**
*
Checks to see if a request param is considered true using a loose matching strategy for
* general values that indicate that something is true or enabled, etc.
*
* Values that are considered "true" include (case-insensitive): true, t, 1, enabled, y, yes, on.
*
* @param request the servlet request
* @param paramName @return true if the param value is considered true or false if it isn't.
* @return true if the given parameter is considered "true" - false otherwise.
*/
public static boolean isTrue(ServletRequest request, String paramName) {
String value = getCleanParam(request, paramName);
return value != null &&
(value.equalsIgnoreCase("true") ||
value.equalsIgnoreCase("t") ||
value.equalsIgnoreCase("1") ||
value.equalsIgnoreCase("enabled") ||
value.equalsIgnoreCase("y") ||
value.equalsIgnoreCase("yes") ||
value.equalsIgnoreCase("on"));
}
/**
* Convenience method that returns a request parameter value, first running it through
* {@link Strings#clean(String)}.
*
* @param request the servlet request.
* @param paramName the parameter name.
* @return the clean param value, or null if the param does not exist or is empty.
*/
public static String getCleanParam(ServletRequest request, String paramName) {
return Strings.clean(request.getParameter(paramName));
}
/*
public static void saveRequest(ServletRequest request) {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
HttpServletRequest httpRequest = toHttp(request);
SavedRequest savedRequest = new SavedRequest(httpRequest);
session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
}
public static SavedRequest getAndClearSavedRequest(ServletRequest request) {
SavedRequest savedRequest = getSavedRequest(request);
if (savedRequest != null) {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.removeAttribute(SAVED_REQUEST_KEY);
}
return savedRequest;
}
public static SavedRequest getSavedRequest(ServletRequest request) {
SavedRequest savedRequest = null;
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession(false);
if (session != null) {
savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_KEY);
}
return savedRequest;
}
/**
* Redirects the to the request url from a previously
* {@link #saveRequest(javax.servlet.ServletRequest) saved} request, or if there is no saved request, redirects the
* end user to the specified {@code fallbackUrl}. If there is no saved request or fallback url, this method
* throws an {@link IllegalStateException}.
*
* This method is primarily used to support a common login scenario - if an unauthenticated user accesses a
* page that requires authentication, it is expected that request is
* {@link #saveRequest(javax.servlet.ServletRequest) saved} first and then redirected to the login page. Then,
* after a successful login, this method can be called to redirect them back to their originally requested URL, a
* nice usability feature.
*
* @param request the incoming request
* @param response the outgoing response
* @param fallbackUrl the fallback url to redirect to if there is no saved request available.
* @throws IllegalStateException if there is no saved request and the {@code fallbackUrl} is {@code null}.
* @throws IOException if there is an error redirecting
* @since 1.0
*
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
throws IOException {
String successUrl = null;
boolean contextRelative = true;
SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
successUrl = savedRequest.getRequestUrl();
contextRelative = false;
}
if (successUrl == null) {
successUrl = fallbackUrl;
}
if (successUrl == null) {
throw new IllegalStateException("Success URL not available via saved request or via the " +
"successUrlFallback method parameter. One of these must be non-null for " +
"issueSuccessRedirect() to work.");
}
WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
}
*/
}