All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 results = new ArrayList<>(); if (values1 == null) { // Skip - nothing to merge } else { results.addAll(Arrays.asList(values1)); } if (values2 == null) { // Skip - nothing to merge } else { results.addAll(Arrays.asList(values2)); } String values[] = new String[results.size()]; return results.toArray(values); } // ------------------------------------------------------ Private Methods /** * Merge the parameters from the saved query parameter string (if any), and * the parameters already present on this request (if any), such that the * parameter values from the query string show up first if there are * duplicate parameter names. */ private void mergeParameters() { if ((queryParamString == null) || (queryParamString.length() < 1)) { return; } // Parse the query string from the dispatch target Parameters paramParser = new Parameters(); MessageBytes queryMB = MessageBytes.newInstance(); queryMB.setString(queryParamString); // TODO // - Should only use body encoding if useBodyEncodingForURI is true // - Otherwise, should use URIEncoding // - The problem is that the connector is not available... // - To add to the fun, the URI default changed in Servlet 4.0 to UTF-8 String encoding = getCharacterEncoding(); Charset charset = null; if (encoding != null) { try { charset = B2CConverter.getCharset(encoding); queryMB.setCharset(charset); } catch (UnsupportedEncodingException e) { // Fall-back to default (ISO-8859-1) charset = StandardCharsets.ISO_8859_1; } } paramParser.setQuery(queryMB); paramParser.setQueryStringCharset(charset); paramParser.handleQueryParameters(); // Insert the additional parameters from the dispatch target Enumeration dispParamNames = paramParser.getParameterNames(); while (dispParamNames.hasMoreElements()) { String dispParamName = dispParamNames.nextElement(); String[] dispParamValues = paramParser.getParameterValues(dispParamName); String[] originalValues = parameters.get(dispParamName); if (originalValues == null) { parameters.put(dispParamName, dispParamValues); continue; } parameters.put(dispParamName, mergeValues(dispParamValues, originalValues)); } } // ----------------------------------- AttributeNamesEnumerator Inner Class /** * Utility class used to expose the special attributes as being available * as request attributes. */ protected class AttributeNamesEnumerator implements Enumeration { protected int pos = -1; protected final int last; protected final Enumeration parentEnumeration; protected String next = null; public AttributeNamesEnumerator() { int last = -1; parentEnumeration = getRequest().getAttributeNames(); for (int i = specialAttributes.length - 1; i >= 0; i--) { if (getAttribute(specials[i]) != null) { last = i; break; } } this.last = last; } @Override public boolean hasMoreElements() { return ((pos != last) || (next != null) || ((next = findNext()) != null)); } @Override public String nextElement() { if (pos != last) { for (int i = pos + 1; i <= last; i++) { if (getAttribute(specials[i]) != null) { pos = i; return specials[i]; } } } String result = next; if (next != null) { next = findNext(); } else { throw new NoSuchElementException(); } return result; } protected String findNext() { String result = null; while ((result == null) && (parentEnumeration.hasMoreElements())) { String current = parentEnumeration.nextElement(); if (!isSpecial(current)) { result = current; } } return result; } } }