org.apache.catalina.core.ApplicationHttpRequest Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.PushBuilder;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.URLEncoder;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.Parameters;
import org.apache.tomcat.util.res.StringManager;
/**
* Wrapper around a javax.servlet.http.HttpServletRequest
* that transforms an application request object (which might be the original
* one passed to a servlet, or might be based on the 2.3
* javax.servlet.http.HttpServletRequestWrapper
class)
* back into an internal org.apache.catalina.HttpRequest
.
*
* WARNING: Due to Java's lack of support for multiple
* inheritance, all of the logic in ApplicationRequest
is
* duplicated in ApplicationHttpRequest
. Make sure that you
* keep these two classes in synchronization when making changes!
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
class ApplicationHttpRequest extends HttpServletRequestWrapper {
private static final StringManager sm = StringManager.getManager(ApplicationHttpRequest.class);
/**
* The set of attribute names that are special for request dispatchers.
*/
protected static final String specials[] =
{ RequestDispatcher.INCLUDE_REQUEST_URI,
RequestDispatcher.INCLUDE_CONTEXT_PATH,
RequestDispatcher.INCLUDE_SERVLET_PATH,
RequestDispatcher.INCLUDE_PATH_INFO,
RequestDispatcher.INCLUDE_QUERY_STRING,
RequestDispatcher.INCLUDE_MAPPING,
RequestDispatcher.FORWARD_REQUEST_URI,
RequestDispatcher.FORWARD_CONTEXT_PATH,
RequestDispatcher.FORWARD_SERVLET_PATH,
RequestDispatcher.FORWARD_PATH_INFO,
RequestDispatcher.FORWARD_QUERY_STRING,
RequestDispatcher.FORWARD_MAPPING};
private static final int SPECIALS_FIRST_FORWARD_INDEX = 6;
// ----------------------------------------------------------- Constructors
/**
* Construct a new wrapped request around the specified servlet request.
*
* @param request The servlet request being wrapped
* @param context The target context for the wrapped request
* @param crossContext {@code true} if the wrapped request will be a
* cross-context request, otherwise {@code false}
*/
public ApplicationHttpRequest(HttpServletRequest request, Context context,
boolean crossContext) {
super(request);
this.context = context;
this.crossContext = crossContext;
setRequest(request);
}
// ----------------------------------------------------- Instance Variables
/**
* The context for this request.
*/
protected final Context context;
/**
* The context path for this request.
*/
protected String contextPath = null;
/**
* If this request is cross context, since this changes session access
* behavior.
*/
protected final boolean crossContext;
/**
* The current dispatcher type.
*/
protected DispatcherType dispatcherType = null;
/**
* The request parameters for this request. This is initialized from the
* wrapped request.
*/
protected Map parameters = null;
/**
* Have the parameters for this request already been parsed?
*/
private boolean parsedParams = false;
/**
* The path information for this request.
*/
protected String pathInfo = null;
/**
* The query parameters for the current request.
*/
private String queryParamString = null;
/**
* The query string for this request.
*/
protected String queryString = null;
/**
* The current request dispatcher path.
*/
protected Object requestDispatcherPath = null;
/**
* The request URI for this request.
*/
protected String requestURI = null;
/**
* The servlet path for this request.
*/
protected String servletPath = null;
/**
* The mapping for this request.
*/
private HttpServletMapping mapping = null;
/**
* The currently active session for this request.
*/
protected Session session = null;
/**
* Special attributes.
*/
protected final Object[] specialAttributes = new Object[specials.length];
// ------------------------------------------------- ServletRequest Methods
@Override
public ServletContext getServletContext() {
if (context == null) {
return null;
}
return context.getServletContext();
}
/**
* Override the getAttribute()
method of the wrapped request.
*
* @param name Name of the attribute to retrieve
*/
@Override
public Object getAttribute(String name) {
if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
return dispatcherType;
} else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
if ( requestDispatcherPath != null ){
return requestDispatcherPath.toString();
} else {
return null;
}
}
int pos = getSpecial(name);
if (pos == -1) {
return getRequest().getAttribute(name);
} else {
if ((specialAttributes[pos] == null) &&
(specialAttributes[SPECIALS_FIRST_FORWARD_INDEX] == null) &&
(pos >= SPECIALS_FIRST_FORWARD_INDEX)) {
// If it's a forward special attribute, and null, it means this
// is an include, so we check the wrapped request since
// the request could have been forwarded before the include
return getRequest().getAttribute(name);
} else {
return specialAttributes[pos];
}
}
}
/**
* Override the getAttributeNames()
method of the wrapped
* request.
*/
@Override
public Enumeration getAttributeNames() {
return new AttributeNamesEnumerator();
}
/**
* Override the removeAttribute()
method of the
* wrapped request.
*
* @param name Name of the attribute to remove
*/
@Override
public void removeAttribute(String name) {
if (!removeSpecial(name)) {
getRequest().removeAttribute(name);
}
}
/**
* Override the setAttribute()
method of the
* wrapped request.
*
* @param name Name of the attribute to set
* @param value Value of the attribute to set
*/
@Override
public void setAttribute(String name, Object value) {
if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
dispatcherType = (DispatcherType)value;
return;
} else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
requestDispatcherPath = value;
return;
}
if (!setSpecial(name, value)) {
getRequest().setAttribute(name, value);
}
}
/**
* Return a RequestDispatcher that wraps the resource at the specified
* path, which may be interpreted as relative to the current request path.
*
* @param path Path of the resource to be wrapped
*/
@Override
public RequestDispatcher getRequestDispatcher(String path) {
if (context == null) {
return null;
}
if (path == null) {
return null;
}
int fragmentPos = path.indexOf('#');
if (fragmentPos > -1) {
context.getLogger().warn(sm.getString("applicationHttpRequest.fragmentInDispatchPath", path));
path = path.substring(0, fragmentPos);
}
// If the path is already context-relative, just pass it through
if (path.startsWith("/")) {
return context.getServletContext().getRequestDispatcher(path);
}
// Convert a request-relative path to a context-relative one
String servletPath =
(String) getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
if (servletPath == null) {
servletPath = getServletPath();
}
// Add the path info, if there is any
String pathInfo = getPathInfo();
String requestPath = null;
if (pathInfo == null) {
requestPath = servletPath;
} else {
requestPath = servletPath + pathInfo;
}
int pos = requestPath.lastIndexOf('/');
String relative = null;
if (context.getDispatchersUseEncodedPaths()) {
if (pos >= 0) {
relative = URLEncoder.DEFAULT.encode(
requestPath.substring(0, pos + 1), StandardCharsets.UTF_8) + path;
} else {
relative = URLEncoder.DEFAULT.encode(requestPath, StandardCharsets.UTF_8) + path;
}
} else {
if (pos >= 0) {
relative = requestPath.substring(0, pos + 1) + path;
} else {
relative = requestPath + path;
}
}
return context.getServletContext().getRequestDispatcher(relative);
}
/**
* Override the getDispatcherType() method of the wrapped request.
*
*/
@Override
public DispatcherType getDispatcherType() {
return dispatcherType;
}
// --------------------------------------------- HttpServletRequest Methods
/**
* Override the getContextPath()
method of the wrapped
* request.
*/
@Override
public String getContextPath() {
return this.contextPath;
}
/**
* Override the getParameter()
method of the wrapped request.
*
* @param name Name of the requested parameter
*/
@Override
public String getParameter(String name) {
parseParameters();
String[] value = parameters.get(name);
if (value == null) {
return null;
}
return value[0];
}
/**
* Override the getParameterMap()
method of the
* wrapped request.
*/
@Override
public Map getParameterMap() {
parseParameters();
return parameters;
}
/**
* Override the getParameterNames()
method of the
* wrapped request.
*/
@Override
public Enumeration getParameterNames() {
parseParameters();
return Collections.enumeration(parameters.keySet());
}
/**
* Override the getParameterValues()
method of the
* wrapped request.
*
* @param name Name of the requested parameter
*/
@Override
public String[] getParameterValues(String name) {
parseParameters();
return parameters.get(name);
}
/**
* Override the getPathInfo()
method of the wrapped request.
*/
@Override
public String getPathInfo() {
return this.pathInfo;
}
/**
* Override the getPathTranslated()
method of the wrapped
* request.
*/
@Override
public String getPathTranslated() {
if (getPathInfo() == null || getServletContext() == null) {
return null;
}
return getServletContext().getRealPath(getPathInfo());
}
/**
* Override the getQueryString()
method of the wrapped
* request.
*/
@Override
public String getQueryString() {
return this.queryString;
}
/**
* Override the getRequestURI()
method of the wrapped
* request.
*/
@Override
public String getRequestURI() {
return this.requestURI;
}
/**
* Override the getRequestURL()
method of the wrapped
* request.
*/
@Override
public StringBuffer getRequestURL() {
return RequestUtil.getRequestURL(this);
}
/**
* Override the getServletPath()
method of the wrapped
* request.
*/
@Override
public String getServletPath() {
return this.servletPath;
}
@Override
public HttpServletMapping getHttpServletMapping() {
return mapping;
}
/**
* Return the session associated with this Request, creating one
* if necessary.
*/
@Override
public HttpSession getSession() {
return getSession(true);
}
/**
* Return the session associated with this Request, creating one
* if necessary and requested.
*
* @param create Create a new session if one does not exist
*/
@Override
public HttpSession getSession(boolean create) {
if (crossContext) {
// There cannot be a session if no context has been assigned yet
if (context == null) {
return null;
}
// Return the current session if it exists and is valid
if (session != null && session.isValid()) {
return session.getSession();
}
HttpSession other = super.getSession(false);
if (create && (other == null)) {
// First create a session in the first context: the problem is
// that the top level request is the only one which can
// create the cookie safely
other = super.getSession(true);
}
if (other != null) {
Session localSession = null;
try {
localSession =
context.getManager().findSession(other.getId());
if (localSession != null && !localSession.isValid()) {
localSession = null;
}
} catch (IOException e) {
// Ignore
}
if (localSession == null && create) {
localSession =
context.getManager().createSession(other.getId());
}
if (localSession != null) {
localSession.access();
session = localSession;
return session.getSession();
}
}
return null;
} else {
return super.getSession(create);
}
}
/**
* Returns true if the request specifies a JSESSIONID that is valid within
* the context of this ApplicationHttpRequest, false otherwise.
*
* @return true if the request specifies a JSESSIONID that is valid within
* the context of this ApplicationHttpRequest, false otherwise.
*/
@Override
public boolean isRequestedSessionIdValid() {
if (crossContext) {
String requestedSessionId = getRequestedSessionId();
if (requestedSessionId == null) {
return false;
}
if (context == null) {
return false;
}
Manager manager = context.getManager();
if (manager == null) {
return false;
}
Session session = null;
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
// Ignore
}
if ((session != null) && session.isValid()) {
return true;
} else {
return false;
}
} else {
return super.isRequestedSessionIdValid();
}
}
@Override
public PushBuilder newPushBuilder() {
ServletRequest current = getRequest();
while (current instanceof ServletRequestWrapper) {
current = ((ServletRequestWrapper) current).getRequest();
}
if (current instanceof RequestFacade) {
return ((RequestFacade) current).newPushBuilder(this);
} else {
return null;
}
}
// -------------------------------------------------------- Package Methods
/**
* Recycle this request
*/
public void recycle() {
if (session != null) {
session.endAccess();
}
}
/**
* Set the context path for this request.
*
* @param contextPath The new context path
*/
void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
/**
* Set the path information for this request.
*
* @param pathInfo The new path info
*/
void setPathInfo(String pathInfo) {
this.pathInfo = pathInfo;
}
/**
* Set the query string for this request.
*
* @param queryString The new query string
*/
void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* Set the request that we are wrapping.
*
* @param request The new wrapped request
*/
void setRequest(HttpServletRequest request) {
super.setRequest(request);
// Initialize the attributes for this request
dispatcherType = (DispatcherType)request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
requestDispatcherPath =
request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
// Initialize the path elements for this request
contextPath = request.getContextPath();
pathInfo = request.getPathInfo();
queryString = request.getQueryString();
requestURI = request.getRequestURI();
servletPath = request.getServletPath();
mapping = request.getHttpServletMapping();
}
/**
* Set the request URI for this request.
*
* @param requestURI The new request URI
*/
void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
/**
* Set the servlet path for this request.
*
* @param servletPath The new servlet path
*/
void setServletPath(String servletPath) {
this.servletPath = servletPath;
}
/**
* Parses the parameters of this request.
*
* If parameters are present in both the query string and the request
* content, they are merged.
*/
void parseParameters() {
if (parsedParams) {
return;
}
parameters = new ParameterMap<>();
parameters.putAll(getRequest().getParameterMap());
mergeParameters();
((ParameterMap) parameters).setLocked(true);
parsedParams = true;
}
/**
* Save query parameters for this request.
*
* @param queryString The query string containing parameters for this
* request
*/
void setQueryParams(String queryString) {
this.queryParamString = queryString;
}
void setMapping(HttpServletMapping mapping) {
this.mapping = mapping;
}
// ------------------------------------------------------ Protected Methods
/**
* Is this attribute name one of the special ones that is added only for
* included servlets?
*
* @param name Attribute name to be tested
*/
protected boolean isSpecial(String name) {
for (String special : specials) {
if (special.equals(name)) {
return true;
}
}
return false;
}
/**
* Get a special attribute.
*
* @return the special attribute pos, or -1 if it is not a special
* attribute
*/
protected int getSpecial(String name) {
for (int i = 0; i < specials.length; i++) {
if (specials[i].equals(name)) {
return i;
}
}
return -1;
}
/**
* Set a special attribute.
*
* @return true if the attribute was a special attribute, false otherwise
*/
protected boolean setSpecial(String name, Object value) {
for (int i = 0; i < specials.length; i++) {
if (specials[i].equals(name)) {
specialAttributes[i] = value;
return true;
}
}
return false;
}
/**
* Remove a special attribute.
*
* @return true if the attribute was a special attribute, false otherwise
*/
protected boolean removeSpecial(String name) {
for (int i = 0; i < specials.length; i++) {
if (specials[i].equals(name)) {
specialAttributes[i] = null;
return true;
}
}
return false;
}
/**
* Merge the two sets of parameter values into a single String array.
*
* @param values1 First set of values
* @param values2 Second set of values
*/
private String[] mergeValues(String[] values1, String[] values2) {
List