Reintroduce Cross Context Dispatch in Jetty 12 (#11451)
Re-introduce some support for cross context dispatch
This commit is contained in:
parent
2aa93575e7
commit
2fc7ad87d8
|
@ -26,11 +26,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
/**
|
||||
* Cookie parser
|
||||
* <p>Optimized stateful cookie parser.
|
||||
* If the added fields are identical to those last added (as strings), then the
|
||||
* cookies are not re-parsed.
|
||||
*
|
||||
* @deprecated Use {@code org.eclipse.jetty.server.CookieCache}
|
||||
*/
|
||||
@Deprecated (forRemoval = true)
|
||||
public class CookieCache implements CookieParser.Handler, ComplianceViolation.Listener
|
||||
{
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(CookieCache.class);
|
||||
|
@ -39,11 +37,13 @@ public class CookieCache implements CookieParser.Handler, ComplianceViolation.Li
|
|||
private final CookieParser _parser;
|
||||
private List<ComplianceViolation.Event> _violations;
|
||||
|
||||
@Deprecated
|
||||
public CookieCache()
|
||||
{
|
||||
this(CookieCompliance.RFC6265);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CookieCache(CookieCompliance compliance)
|
||||
{
|
||||
_parser = CookieParser.newParser(this, compliance, this);
|
||||
|
|
|
@ -474,6 +474,10 @@ public abstract class SecurityHandler extends Handler.Wrapper implements Configu
|
|||
if (next == null)
|
||||
return false;
|
||||
|
||||
// Skip security check if this is a dispatch rather than a fresh request
|
||||
if (request.getContext().isCrossContextDispatch(request))
|
||||
return next.handle(request, response, callback);
|
||||
|
||||
String pathInContext = Request.getPathInContext(request);
|
||||
Constraint constraint = getConstraint(pathInContext, request);
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -117,6 +117,29 @@ public interface Context extends Attributes, Decorator, Executor
|
|||
*/
|
||||
File getTempDirectory();
|
||||
|
||||
/**
|
||||
* Check cross context dispatch status
|
||||
* @param request The request to check
|
||||
* @return {@code True} IFF this context {@link ContextHandler#isCrossContextDispatchSupported() supports cross context}
|
||||
* and the passed request is a cross context request.
|
||||
*/
|
||||
default boolean isCrossContextDispatch(Request request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any cross context dispatch type
|
||||
* @param request The request to get the type for
|
||||
* @return A String representation of a dispatcher type iff this context
|
||||
* {@link ContextHandler#isCrossContextDispatchSupported() supports cross context}
|
||||
* and the passed request is a cross context request, otherwise null.
|
||||
*/
|
||||
default String getCrossContextDispatchType(Request request)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the URI path scoped to the passed context path.</p>
|
||||
* <p>For example, if the context path passed is {@code /ctx} then a
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.ComplianceViolation;
|
||||
import org.eclipse.jetty.http.CookieCompliance;
|
||||
import org.eclipse.jetty.http.CookieParser;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Cookie parser
|
||||
* <p>Optimized stateful cookie parser.
|
||||
* If the added fields are identical to those last added (as strings), then the
|
||||
* cookies are not re-parsed.
|
||||
*
|
||||
*/
|
||||
public class CookieCache extends AbstractList<HttpCookie> implements CookieParser.Handler, ComplianceViolation.Listener
|
||||
{
|
||||
/**
|
||||
* Get the core HttpCookies for a request.
|
||||
* Cookies may be cached as a {@link Request#getAttribute(String) request attribute}, failing that they may be
|
||||
* cached in the {@link Components#getCache() Component cache}, in which case they will be checked to see if they
|
||||
* have changed since a previous request. Otherwise, they are parsed from the request headers and both caches updated.
|
||||
* @param request The request to obtain cookies from
|
||||
* @return A list of core {@link HttpCookie}s from the request.
|
||||
* @see #getApiCookies(Request, Class, Function)
|
||||
*/
|
||||
public static List<HttpCookie> getCookies(Request request)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HttpCookie> cookies = (List<HttpCookie>)request.getAttribute(Request.COOKIE_ATTRIBUTE);
|
||||
if (cookies != null)
|
||||
return cookies;
|
||||
|
||||
CookieCache cookieCache = (CookieCache)request.getComponents().getCache().getAttribute(Request.COOKIE_ATTRIBUTE);
|
||||
if (cookieCache == null)
|
||||
{
|
||||
cookieCache = new CookieCache(request.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
|
||||
request.getComponents().getCache().setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache);
|
||||
}
|
||||
|
||||
cookieCache.parseCookies(request.getHeaders(), HttpChannel.from(request).getComplianceViolationListener());
|
||||
request.setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache);
|
||||
return cookieCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API specific cookies for a request.
|
||||
* Internally the same caching/parsing is done as by {@link #getCookies(Request)} and the core {@link HttpCookie}s are
|
||||
* obtained. The passed {@code convertor} function is used to covert the core {@link HttpCookie}s to API specific cookies
|
||||
* and the results cached along with the core {@link HttpCookie}s
|
||||
* @param request The request to get the cookies from.
|
||||
* @param cookieClass The class of the cookie API
|
||||
* @param convertor A function to convert from a {@link HttpCookie} to an API cookie of type {@code cookieClass}. The
|
||||
* function may return null if the cookie is not compliant with the API.
|
||||
* @param <C> The class of the cookie API
|
||||
* @return An array of API specific cookies.
|
||||
*/
|
||||
public static <C> C[] getApiCookies(Request request, Class<C> cookieClass, Function<HttpCookie, C> convertor)
|
||||
{
|
||||
if (request == null)
|
||||
return null;
|
||||
|
||||
CookieCache cookieCache = (CookieCache)request.getAttribute(Request.COOKIE_ATTRIBUTE);
|
||||
if (cookieCache == null)
|
||||
{
|
||||
cookieCache = (CookieCache)request.getComponents().getCache().getAttribute(Request.COOKIE_ATTRIBUTE);
|
||||
if (cookieCache == null)
|
||||
{
|
||||
cookieCache = new CookieCache(request.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
|
||||
request.getComponents().getCache().setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache);
|
||||
}
|
||||
|
||||
cookieCache.parseCookies(request.getHeaders(), HttpChannel.from(request).getComplianceViolationListener());
|
||||
request.setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache);
|
||||
}
|
||||
|
||||
return cookieCache.getApiCookies(cookieClass, convertor);
|
||||
}
|
||||
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(CookieCache.class);
|
||||
protected final List<String> _rawFields = new ArrayList<>();
|
||||
private final CookieParser _parser;
|
||||
private List<HttpCookie> _httpCookies = Collections.emptyList();
|
||||
private Map<Class<?>, Object[]> _apiCookies;
|
||||
private List<ComplianceViolation.Event> _violations;
|
||||
|
||||
public CookieCache()
|
||||
{
|
||||
this(CookieCompliance.RFC6265);
|
||||
}
|
||||
|
||||
public CookieCache(CookieCompliance compliance)
|
||||
{
|
||||
_parser = CookieParser.newParser(this, compliance, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie get(int index)
|
||||
{
|
||||
return _httpCookies.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return _httpCookies.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplianceViolation(ComplianceViolation.Event event)
|
||||
{
|
||||
if (_violations == null)
|
||||
_violations = new ArrayList<>();
|
||||
_violations.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(String cookieName, String cookieValue, int cookieVersion, String cookieDomain, String cookiePath, String cookieComment)
|
||||
{
|
||||
if (StringUtil.isEmpty(cookieDomain) && StringUtil.isEmpty(cookiePath) && cookieVersion <= 0 && StringUtil.isEmpty(cookieComment))
|
||||
_httpCookies.add(HttpCookie.from(cookieName, cookieValue));
|
||||
else
|
||||
{
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
if (!StringUtil.isEmpty(cookieDomain))
|
||||
attributes.put(HttpCookie.DOMAIN_ATTRIBUTE, cookieDomain);
|
||||
if (!StringUtil.isEmpty(cookiePath))
|
||||
attributes.put(HttpCookie.PATH_ATTRIBUTE, cookiePath);
|
||||
if (!StringUtil.isEmpty(cookieComment))
|
||||
attributes.put(HttpCookie.COMMENT_ATTRIBUTE, cookieComment);
|
||||
_httpCookies.add(HttpCookie.from(cookieName, cookieValue, cookieVersion, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
List<HttpCookie> getCookies(HttpFields headers)
|
||||
{
|
||||
parseCookies(headers, ComplianceViolation.Listener.NOOP);
|
||||
return _httpCookies;
|
||||
}
|
||||
|
||||
public void parseCookies(HttpFields headers, ComplianceViolation.Listener complianceViolationListener)
|
||||
{
|
||||
boolean building = false;
|
||||
ListIterator<String> raw = _rawFields.listIterator();
|
||||
// For each of the headers
|
||||
for (HttpField field : headers)
|
||||
{
|
||||
// skip non cookie headers
|
||||
if (!HttpHeader.COOKIE.equals(field.getHeader()))
|
||||
continue;
|
||||
|
||||
// skip blank cookie headers
|
||||
String value = field.getValue();
|
||||
if (StringUtil.isBlank(value))
|
||||
continue;
|
||||
|
||||
// If we are building a new cookie list
|
||||
if (building)
|
||||
{
|
||||
// just add the raw string to the list to be parsed later
|
||||
_rawFields.add(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise we are checking against previous cookies.
|
||||
|
||||
// Is there a previous raw cookie to compare with?
|
||||
if (!raw.hasNext())
|
||||
{
|
||||
// No, so we will flip to building state and add to the raw fields we already have.
|
||||
building = true;
|
||||
_rawFields.add(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a previous raw cookie and it is the same, then continue checking
|
||||
if (value.equals(raw.next()))
|
||||
continue;
|
||||
|
||||
// otherwise there is a difference in the previous raw cookie field
|
||||
// so switch to building mode and remove all subsequent raw fields
|
||||
// then add the current raw field to be built later.
|
||||
building = true;
|
||||
raw.remove();
|
||||
while (raw.hasNext())
|
||||
{
|
||||
raw.next();
|
||||
raw.remove();
|
||||
}
|
||||
_rawFields.add(value);
|
||||
}
|
||||
|
||||
// If we are not building, but there are still more unmatched raw fields, then a field was deleted
|
||||
if (!building && raw.hasNext())
|
||||
{
|
||||
// switch to building mode and delete the unmatched raw fields
|
||||
building = true;
|
||||
while (raw.hasNext())
|
||||
{
|
||||
raw.next();
|
||||
raw.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// If we ended up in building mode, reparse the cookie list from the raw fields.
|
||||
if (building)
|
||||
{
|
||||
_httpCookies = new ArrayList<>();
|
||||
_apiCookies = null;
|
||||
try
|
||||
{
|
||||
if (_violations != null)
|
||||
_violations.clear();
|
||||
_parser.parseFields(_rawFields);
|
||||
}
|
||||
catch (CookieParser.InvalidCookieException invalidCookieException)
|
||||
{
|
||||
throw new BadMessageException(invalidCookieException.getMessage(), invalidCookieException);
|
||||
}
|
||||
}
|
||||
|
||||
if (_violations != null && !_violations.isEmpty())
|
||||
_violations.forEach(complianceViolationListener::onComplianceViolation);
|
||||
}
|
||||
|
||||
public <C> C[] getApiCookies(Class<C> apiClass, Function<HttpCookie, C> convertor)
|
||||
{
|
||||
// No APIs if no Cookies
|
||||
if (_httpCookies.isEmpty())
|
||||
return null;
|
||||
|
||||
// If only the core APIs have been used, then no apiCookie map has been created
|
||||
if (_apiCookies == null)
|
||||
{
|
||||
// When a cookie API is ued, the most common case in only a single API, so used a cheap Map
|
||||
C[] apiCookies = convert(apiClass, convertor);
|
||||
_apiCookies = Map.of(apiClass, apiCookies);
|
||||
return apiCookies;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
C[] apiCookies = (C[])_apiCookies.get(apiClass);
|
||||
if (apiCookies == null)
|
||||
{
|
||||
// Only in the case of cross environment dispatch will more than 1 API be needed, so only invest in a real
|
||||
// map when we know it is required.
|
||||
if (_apiCookies.size() == 1)
|
||||
_apiCookies = new HashMap<>(_apiCookies);
|
||||
apiCookies = convert(apiClass, convertor);
|
||||
_apiCookies.put(apiClass, apiCookies);
|
||||
}
|
||||
return apiCookies;
|
||||
}
|
||||
|
||||
private <C> C[] convert(Class<C> apiClass, Function<HttpCookie, C> convertor)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
C[] apiCookies = (C[])Array.newInstance(apiClass, _httpCookies.size());
|
||||
int i = 0;
|
||||
for (HttpCookie httpCookie : _httpCookies)
|
||||
{
|
||||
C apiCookie = convertor.apply(httpCookie);
|
||||
// Exclude any API cookies that are not convertable to that API
|
||||
if (apiCookie == null)
|
||||
apiCookies = Arrays.copyOf(apiCookies, apiCookies.length - 1);
|
||||
else
|
||||
apiCookies[i++] = apiCookie;
|
||||
}
|
||||
return apiCookies;
|
||||
}
|
||||
}
|
|
@ -98,6 +98,8 @@ public class FormFields extends ContentSourceCompletableFuture<Fields>
|
|||
Object attr = request.getAttribute(FormFields.class.getName());
|
||||
if (attr instanceof FormFields futureFormFields)
|
||||
return futureFormFields;
|
||||
else if (attr instanceof Fields fields)
|
||||
return CompletableFuture.completedFuture(fields);
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
|
@ -221,7 +223,7 @@ public class FormFields extends ContentSourceCompletableFuture<Fields>
|
|||
_maxFields = maxFields;
|
||||
_maxLength = maxSize;
|
||||
_builder = CharsetStringBuilder.forCharset(charset);
|
||||
_fields = new Fields();
|
||||
_fields = new Fields(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,7 +34,6 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jetty.http.CookieCache;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -130,7 +129,6 @@ public interface Request extends Attributes, Content.Source
|
|||
{
|
||||
Logger LOG = LoggerFactory.getLogger(Request.class);
|
||||
|
||||
String CACHE_ATTRIBUTE = Request.class.getCanonicalName() + ".CookieCache";
|
||||
String COOKIE_ATTRIBUTE = Request.class.getCanonicalName() + ".Cookies";
|
||||
List<Locale> DEFAULT_LOCALES = List.of(Locale.getDefault());
|
||||
|
||||
|
@ -580,22 +578,7 @@ public interface Request extends Attributes, Content.Source
|
|||
@SuppressWarnings("unchecked")
|
||||
static List<HttpCookie> getCookies(Request request)
|
||||
{
|
||||
// TODO modify Request and HttpChannel to be optimised for the known attributes
|
||||
List<HttpCookie> cookies = (List<HttpCookie>)request.getAttribute(COOKIE_ATTRIBUTE);
|
||||
if (cookies != null)
|
||||
return cookies;
|
||||
|
||||
// TODO: review whether to store the cookie cache at the connection level, or whether to cache them at all.
|
||||
CookieCache cookieCache = (CookieCache)request.getComponents().getCache().getAttribute(CACHE_ATTRIBUTE);
|
||||
if (cookieCache == null)
|
||||
{
|
||||
cookieCache = new CookieCache(request.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
|
||||
request.getComponents().getCache().setAttribute(CACHE_ATTRIBUTE, cookieCache);
|
||||
}
|
||||
|
||||
cookies = cookieCache.getCookies(request.getHeaders(), HttpChannel.from(request).getComplianceViolationListener());
|
||||
request.setAttribute(COOKIE_ATTRIBUTE, cookies);
|
||||
return cookies;
|
||||
return CookieCache.getCookies(request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,7 +72,7 @@ public class ResourceListing
|
|||
// check for query
|
||||
if (query != null)
|
||||
{
|
||||
Fields params = new Fields();
|
||||
Fields params = new Fields(true);
|
||||
UrlEncoded.decodeUtf8To(query, 0, query.length(), params);
|
||||
|
||||
String paramO = params.getValue("O");
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
@ -74,6 +75,12 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
|||
|
||||
public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
|
||||
|
||||
/**
|
||||
* The attribute name that is set as a {@link Request} attribute to indicate the request is a cross context
|
||||
* dispatch. The value can be set to a ServletDispatcher type if the target is known to be a servlet context.
|
||||
*/
|
||||
public static final String CROSS_CONTEXT_ATTRIBUTE = "org.eclipse.jetty.CrossContextDispatch";
|
||||
|
||||
/**
|
||||
* Get the current Context if any.
|
||||
*
|
||||
|
@ -136,6 +143,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
|||
private File _tempDirectory;
|
||||
private boolean _tempDirectoryPersisted = false;
|
||||
private boolean _tempDirectoryCreated = false;
|
||||
private boolean _crossContextDispatchSupported = false;
|
||||
|
||||
public enum Availability
|
||||
{
|
||||
|
@ -469,6 +477,83 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
|||
return _contextPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if this context support cross context dispatch, either as originator or target.
|
||||
* @return True if this context supports cross context dispatch.
|
||||
*/
|
||||
@ManagedAttribute(value = "Cross context dispatch is support by the context")
|
||||
public boolean isCrossContextDispatchSupported()
|
||||
{
|
||||
return _crossContextDispatchSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if this context support cross context dispatch, either as originator or target.
|
||||
* @param crossContextDispatchSupported True if this context supports cross context dispatch.
|
||||
*/
|
||||
public void setCrossContextDispatchSupported(boolean crossContextDispatchSupported)
|
||||
{
|
||||
_crossContextDispatchSupported = crossContextDispatchSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #isCrossContextDispatchSupported() cross context dispatch is supported} by this context
|
||||
* then find a context by {@link #getContextPath() contextPath} that also supports cross context dispatch.
|
||||
* If more than one context is found, then those with disjoint {@link #getVirtualHosts() virtual hosts} are
|
||||
* excluded and the first remaining context returned.
|
||||
*
|
||||
* @param path The path that will be served by the context
|
||||
* @return The found {@link ContextHandler} or null.
|
||||
*/
|
||||
public ContextHandler getCrossContextHandler(String path)
|
||||
{
|
||||
if (!isCrossContextDispatchSupported())
|
||||
return null;
|
||||
|
||||
List<ContextHandler> contexts = new ArrayList<>();
|
||||
for (ContextHandler contextHandler : getServer().getDescendants(ContextHandler.class))
|
||||
{
|
||||
if (contextHandler == null || !contextHandler.isCrossContextDispatchSupported())
|
||||
continue;
|
||||
String contextPath = contextHandler.getContextPath();
|
||||
if (path.equals(contextPath) ||
|
||||
(path.startsWith(contextPath) && path.charAt(contextPath.length()) == '/') ||
|
||||
"/".equals(contextPath))
|
||||
contexts.add(contextHandler);
|
||||
}
|
||||
|
||||
if (contexts.isEmpty())
|
||||
return null;
|
||||
if (contexts.size() == 1)
|
||||
return contexts.get(0);
|
||||
|
||||
// Remove non-matching virtual hosts
|
||||
List<String> vhosts = getVirtualHosts();
|
||||
if (vhosts != null && !vhosts.isEmpty())
|
||||
{
|
||||
for (ListIterator<ContextHandler> i = contexts.listIterator(); i.hasNext(); )
|
||||
{
|
||||
ContextHandler ch = i.next();
|
||||
|
||||
List<String> targetVhosts = ch.getVirtualHosts();
|
||||
if (targetVhosts == null || targetVhosts.isEmpty() || Collections.disjoint(vhosts, targetVhosts))
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (contexts.isEmpty())
|
||||
return null;
|
||||
|
||||
// return the first longest
|
||||
ContextHandler contextHandler = null;
|
||||
for (ContextHandler c : contexts)
|
||||
{
|
||||
if (contextHandler == null || c.getContextPath().length() > contextHandler.getContextPath().length())
|
||||
contextHandler = c;
|
||||
}
|
||||
return contextHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a context event listeners.
|
||||
*
|
||||
|
@ -1252,6 +1337,18 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
|||
{
|
||||
return _rootContext ? canonicallyEncodedPath : Context.getPathInContext(_contextPath, canonicallyEncodedPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCrossContextDispatch(Request request)
|
||||
{
|
||||
return isCrossContextDispatchSupported() && request.getAttribute(CROSS_CONTEXT_ATTRIBUTE) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCrossContextDispatchType(Request request)
|
||||
{
|
||||
return isCrossContextDispatchSupported() ? (String)request.getAttribute(CROSS_CONTEXT_ATTRIBUTE) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.eclipse.jetty.http.CookieCache;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -175,4 +176,25 @@ public class CookieCacheTest
|
|||
assertThat(cookie.getValue(), is("different"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiCookie()
|
||||
{
|
||||
_fields.put("Some", "Header");
|
||||
_fields.put("Cookie", "name=value");
|
||||
_fields.add("Cookie", "other=cookie");
|
||||
_fields.put("Other", "header");
|
||||
List<HttpCookie> cookies0 = _cache.getCookies(_fields.asImmutable());
|
||||
assertThat(cookies0, hasSize(2));
|
||||
HttpCookie cookie = cookies0.get(0);
|
||||
assertThat(cookie.getName(), is("name"));
|
||||
assertThat(cookie.getValue(), is("value"));
|
||||
cookie = cookies0.get(1);
|
||||
assertThat(cookie.getName(), is("other"));
|
||||
assertThat(cookie.getValue(), is("cookie"));
|
||||
|
||||
String[] strings = _cache.getApiCookies(String.class, c -> c.getName() + "=" + c.getValue());
|
||||
assertThat(Arrays.asList(strings), contains("name=value", "other=cookie"));
|
||||
assertThat(_cache.getApiCookies(String.class, null), sameInstance(strings));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ public interface Attributes
|
|||
* Get the immutable set of attribute names.
|
||||
* @return Set of attribute names
|
||||
*/
|
||||
// TODO: change to getAttributeNames() once jetty-core is cleaned of servlet-api usages
|
||||
Set<String> getAttributeNameSet();
|
||||
|
||||
default Map<String, Object> asAttributeMap()
|
||||
|
@ -566,14 +565,14 @@ public interface Attributes
|
|||
* but is instead calculated as needed. Modifications to synthetic attributes are maintained
|
||||
* in a separate layer and no modifications are made to the backing {@link Attributes}.
|
||||
* <p>
|
||||
* Non synthetic attributes are handled normally by the backing {@link Attributes}
|
||||
* Non-synthetic attributes are handled normally by the backing {@link Attributes}
|
||||
* <p>
|
||||
* Uses of this class must provide implementations for
|
||||
* {@link #getSyntheticNameSet()} amd {@link #getSyntheticAttribute(String)}.
|
||||
*/
|
||||
abstract class Synthetic extends Wrapper
|
||||
{
|
||||
private static final Object REMOVED = new Object()
|
||||
protected static final Object REMOVED = new Object()
|
||||
{
|
||||
@Override
|
||||
public String toString()
|
||||
|
@ -622,6 +621,8 @@ public interface Attributes
|
|||
|
||||
// Is there a synthetic value for the attribute? We just as for the value rather than checking the name.
|
||||
Object s = getSyntheticAttribute(name);
|
||||
if (s == REMOVED)
|
||||
return null;
|
||||
if (s != null)
|
||||
return s;
|
||||
|
||||
|
@ -696,9 +697,15 @@ public interface Attributes
|
|||
if (l == REMOVED)
|
||||
// it has been removed
|
||||
names.remove(s);
|
||||
else if (l != null || getSyntheticAttribute(s) != null)
|
||||
// else it was modified or has an original value
|
||||
else if (l != null)
|
||||
// else it was modified
|
||||
names.add(s);
|
||||
else
|
||||
{
|
||||
Object v = getSyntheticAttribute(s);
|
||||
if (v != null && v != REMOVED)
|
||||
names.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -28,7 +27,7 @@ import java.util.stream.Stream;
|
|||
|
||||
/**
|
||||
* <p>A container for name/value pairs, known as fields.</p>
|
||||
* <p>A {@link Field} is composed of a name string that can be case-sensitive
|
||||
* <p>A {@link Field} is immutable and is composed of a name string that can be case-sensitive
|
||||
* or case-insensitive (by specifying the option at the constructor) and
|
||||
* of a case-sensitive set of value strings.</p>
|
||||
* <p>The implementation of this class is not thread safe.</p>
|
||||
|
@ -57,14 +56,41 @@ public class Fields implements Iterable<Fields.Field>
|
|||
this(caseSensitive ? new LinkedHashMap<>() : new TreeMap<>(String::compareToIgnoreCase));
|
||||
}
|
||||
|
||||
public Fields(MultiMap<String> params)
|
||||
{
|
||||
this(multiMapToMapOfFields(params));
|
||||
}
|
||||
|
||||
public Fields(Map<String, Field> fields)
|
||||
{
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public Fields(Fields fields)
|
||||
{
|
||||
if (fields.fields instanceof TreeMap<String, Field>)
|
||||
{
|
||||
this.fields = new TreeMap<>(String::compareToIgnoreCase);
|
||||
this.fields.putAll(fields.fields);
|
||||
}
|
||||
else if (fields.fields instanceof LinkedHashMap<String, Field>)
|
||||
{
|
||||
this.fields = new LinkedHashMap<>(fields.fields);
|
||||
}
|
||||
else if (Collections.unmodifiableMap(fields.fields) == fields.fields)
|
||||
{
|
||||
this.fields = fields.fields;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("unknown case sensitivity");
|
||||
}
|
||||
}
|
||||
|
||||
public Fields asImmutable()
|
||||
{
|
||||
return new Fields(Collections.unmodifiableMap(fields));
|
||||
Map<String, Field> unmodifiable = Collections.unmodifiableMap(fields);
|
||||
return unmodifiable == fields ? this : new Fields(unmodifiable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -103,12 +129,7 @@ public class Fields implements Iterable<Fields.Field>
|
|||
*/
|
||||
public Set<String> getNames()
|
||||
{
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (Field field : fields.values())
|
||||
{
|
||||
result.add(field.getName());
|
||||
}
|
||||
return result;
|
||||
return fields.keySet();
|
||||
}
|
||||
|
||||
public Stream<Field> stream()
|
||||
|
@ -197,8 +218,7 @@ public class Fields implements Iterable<Fields.Field>
|
|||
*/
|
||||
public void add(String name, String value)
|
||||
{
|
||||
String key = name;
|
||||
fields.compute(key, (k, f) ->
|
||||
fields.compute(name, (k, f) ->
|
||||
{
|
||||
if (f == null)
|
||||
// Preserve the case for the field name
|
||||
|
@ -208,6 +228,31 @@ public class Fields implements Iterable<Fields.Field>
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds the given value to a field with the given name,
|
||||
* creating a {@link Field} is none exists for the given name.</p>
|
||||
*
|
||||
* @param name the field name
|
||||
* @param values the field values to add
|
||||
*/
|
||||
public void add(String name, String... values)
|
||||
{
|
||||
if (values == null || values.length == 0)
|
||||
return;
|
||||
if (values.length == 1)
|
||||
add(name, values[0]);
|
||||
else
|
||||
{
|
||||
fields.compute(name, (k, f) ->
|
||||
{
|
||||
if (f == null)
|
||||
return new Field(name, List.of(values));
|
||||
else
|
||||
return new Field(f.getName(), f.getValues(), List.of(values));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds the given field, storing it if none exists for the given name,
|
||||
* or adding all the values to the existing field with the given name.</p>
|
||||
|
@ -216,8 +261,7 @@ public class Fields implements Iterable<Fields.Field>
|
|||
*/
|
||||
public void add(Field field)
|
||||
{
|
||||
String s = field.getName();
|
||||
String key = s;
|
||||
String key = field.getName();
|
||||
fields.compute(key, (k, f) ->
|
||||
{
|
||||
if (f == null)
|
||||
|
@ -290,6 +334,16 @@ public class Fields implements Iterable<Fields.Field>
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fields (name and values) of this instance copied into a {@code MultiMap<String>}
|
||||
*/
|
||||
public MultiMap<String> toMultiMap()
|
||||
{
|
||||
MultiMap<String> multiMap = new MultiMap<>();
|
||||
fields.forEach((k, f) -> multiMap.addValues(k, f.getValues()));
|
||||
return multiMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -312,18 +366,75 @@ public class Fields implements Iterable<Fields.Field>
|
|||
this(name, List.of(value));
|
||||
}
|
||||
|
||||
private Field(String name, List<String> values, String... moreValues)
|
||||
public Field(String name, List<String> values)
|
||||
{
|
||||
this(name, values, List.of(moreValues));
|
||||
this.name = name;
|
||||
this.values = List.copyOf(values);
|
||||
}
|
||||
|
||||
private Field(String name, List<String> values, String extraValue)
|
||||
{
|
||||
this(name, append(values, extraValue));
|
||||
}
|
||||
|
||||
private Field(String name, List<String> values, List<String> moreValues)
|
||||
{
|
||||
this.name = name;
|
||||
List<String> list = new ArrayList<>(values.size() + moreValues.size());
|
||||
list.addAll(values);
|
||||
list.addAll(moreValues);
|
||||
this.values = List.copyOf(list);
|
||||
this(name, append(values, moreValues));
|
||||
}
|
||||
|
||||
private static List<String> append(List<String> values, String extraValue)
|
||||
{
|
||||
return switch (values.size())
|
||||
{
|
||||
case 0 -> List.of(extraValue);
|
||||
case 1 -> List.of(values.get(0), extraValue);
|
||||
case 2 -> List.of(values.get(0), values.get(1), extraValue);
|
||||
case 3 -> List.of(values.get(0), values.get(1), values.get(2), extraValue);
|
||||
case 4 -> List.of(values.get(0), values.get(1), values.get(2), values.get(3), extraValue);
|
||||
case 5 -> List.of(values.get(0), values.get(1), values.get(2), values.get(3), values.get(4), extraValue);
|
||||
default ->
|
||||
{
|
||||
List<String> list = new ArrayList<>(values.size() + 1);
|
||||
list.addAll(values);
|
||||
list.add(extraValue);
|
||||
yield list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<String> append(List<String> values, List<String> moreValues)
|
||||
{
|
||||
if (moreValues == null || moreValues.isEmpty())
|
||||
return values;
|
||||
|
||||
if (moreValues.size() == 1)
|
||||
return append(values, moreValues.get(0));
|
||||
|
||||
return switch (values.size())
|
||||
{
|
||||
case 0 -> moreValues;
|
||||
case 1 -> switch (moreValues.size())
|
||||
{
|
||||
case 2 -> List.of(values.get(0), moreValues.get(0), moreValues.get(1));
|
||||
case 3 -> List.of(values.get(0), moreValues.get(0), moreValues.get(1), moreValues.get(2));
|
||||
case 4 -> List.of(values.get(0), moreValues.get(0), moreValues.get(1), moreValues.get(2), moreValues.get(3));
|
||||
case 5 -> List.of(values.get(0), moreValues.get(0), moreValues.get(1), moreValues.get(2), moreValues.get(3), moreValues.get(4));
|
||||
default ->
|
||||
{
|
||||
List<String> list = new ArrayList<>(moreValues.size() + 1);
|
||||
list.add(values.get(0));
|
||||
list.addAll(moreValues);
|
||||
yield list;
|
||||
}
|
||||
};
|
||||
default ->
|
||||
{
|
||||
List<String> list = new ArrayList<>(values.size() + moreValues.size());
|
||||
list.addAll(values);
|
||||
list.addAll(moreValues);
|
||||
yield list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -399,7 +510,7 @@ public class Fields implements Iterable<Fields.Field>
|
|||
/**
|
||||
* <p>Combine two Fields</p>
|
||||
* @param a The base Fields or null
|
||||
* @param b The overlayed Fields or null
|
||||
* @param b The overlay Fields or null
|
||||
* @return Fields, which may be empty, but never null.
|
||||
*/
|
||||
public static Fields combine(Fields a, Fields b)
|
||||
|
@ -410,10 +521,20 @@ public class Fields implements Iterable<Fields.Field>
|
|||
if (a == null || a.isEmpty())
|
||||
return b;
|
||||
|
||||
Fields fields = new Fields();
|
||||
Fields fields = new Fields(a.fields instanceof LinkedHashMap<String, Field>);
|
||||
fields.addAll(a);
|
||||
fields.addAll(b);
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static Map<String, Field> multiMapToMapOfFields(MultiMap<String> params)
|
||||
{
|
||||
if (params.isEmpty())
|
||||
return Collections.emptyMap();
|
||||
|
||||
Map<String, Field> fields = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, List<String>> entry : params.entrySet())
|
||||
fields.put(entry.getKey(), new Field(entry.getKey(), entry.getValue()));
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.nio.charset.Charset;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -191,6 +192,23 @@ public class UrlEncoded
|
|||
}, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded parameters to Map.
|
||||
*
|
||||
* @param content the string containing the encoded parameters
|
||||
* @param adder Function to add parameter
|
||||
* @param charset the charset to use for decoding
|
||||
*/
|
||||
public static void decodeTo(String content, BiConsumer<String, String> adder, Charset charset, int maxKeys)
|
||||
{
|
||||
AtomicInteger keys = new AtomicInteger(0);
|
||||
decodeTo(content, (key, val) ->
|
||||
{
|
||||
adder.accept(key, val);
|
||||
checkMaxKeys(keys.incrementAndGet(), maxKeys);
|
||||
}, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded parameters to Map.
|
||||
*
|
||||
|
@ -395,6 +413,21 @@ public class UrlEncoded
|
|||
*/
|
||||
public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
decode88591To(in, map::add, maxLength, maxKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded parameters to MultiMap, using ISO8859-1 encodings.
|
||||
*
|
||||
* @param in InputSteam to read
|
||||
* @param adder Function to add parameter
|
||||
* @param maxLength maximum length of form to read or -1 for no limit
|
||||
* @param maxKeys maximum number of keys to read or -1 for no limit
|
||||
* @throws IOException if unable to decode the InputStream as ISO8859-1
|
||||
*/
|
||||
public static void decode88591To(InputStream in, BiConsumer<String, String> adder, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
String key = null;
|
||||
|
@ -403,6 +436,7 @@ public class UrlEncoded
|
|||
int b;
|
||||
|
||||
int totalLength = 0;
|
||||
int keys = 0;
|
||||
while ((b = in.read()) >= 0)
|
||||
{
|
||||
switch ((char)b)
|
||||
|
@ -412,14 +446,16 @@ public class UrlEncoded
|
|||
buffer.setLength(0);
|
||||
if (key != null)
|
||||
{
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (value.length() > 0)
|
||||
{
|
||||
map.add(value, "");
|
||||
adder.accept(value, "");
|
||||
keys++;
|
||||
}
|
||||
key = null;
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
|
@ -453,13 +489,15 @@ public class UrlEncoded
|
|||
{
|
||||
value = buffer.length() == 0 ? "" : buffer.toString();
|
||||
buffer.setLength(0);
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (buffer.length() > 0)
|
||||
{
|
||||
map.add(buffer.toString(), "");
|
||||
adder.accept(buffer.toString(), "");
|
||||
keys++;
|
||||
}
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -473,6 +511,21 @@ public class UrlEncoded
|
|||
*/
|
||||
public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
decodeUtf8To(in, map::add, maxLength, maxKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded parameters to Map.
|
||||
*
|
||||
* @param in InputSteam to read
|
||||
* @param adder Function to add parameters to
|
||||
* @param maxLength maximum form length to decode or -1 for no limit
|
||||
* @param maxKeys the maximum number of keys to read or -1 for no limit
|
||||
* @throws IOException if unable to decode the input stream
|
||||
*/
|
||||
public static void decodeUtf8To(InputStream in, BiConsumer<String, String> adder, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
Utf8StringBuilder buffer = new Utf8StringBuilder();
|
||||
String key = null;
|
||||
|
@ -481,6 +534,7 @@ public class UrlEncoded
|
|||
int b;
|
||||
|
||||
int totalLength = 0;
|
||||
int keys = 0;
|
||||
while ((b = in.read()) >= 0)
|
||||
{
|
||||
switch ((char)b)
|
||||
|
@ -490,14 +544,16 @@ public class UrlEncoded
|
|||
buffer.reset();
|
||||
if (key != null)
|
||||
{
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (value != null && value.length() > 0)
|
||||
{
|
||||
map.add(value, "");
|
||||
adder.accept(value, "");
|
||||
keys++;
|
||||
}
|
||||
key = null;
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
|
@ -531,22 +587,29 @@ public class UrlEncoded
|
|||
{
|
||||
value = buffer.toCompleteString();
|
||||
buffer.reset();
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (buffer.length() > 0)
|
||||
{
|
||||
map.add(buffer.toCompleteString(), "");
|
||||
adder.accept(buffer.toCompleteString(), "");
|
||||
keys++;
|
||||
}
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
}
|
||||
|
||||
public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
|
||||
{
|
||||
decodeUtf16To(in, map::add, maxLength, maxKeys);
|
||||
}
|
||||
|
||||
public static void decodeUtf16To(InputStream in, BiConsumer<String, String> adder, int maxLength, int maxKeys) throws IOException
|
||||
{
|
||||
InputStreamReader input = new InputStreamReader(in, StandardCharsets.UTF_16);
|
||||
StringWriter buf = new StringWriter(8192);
|
||||
IO.copy(input, buf, maxLength);
|
||||
|
||||
decodeTo(buf.getBuffer().toString(), map, StandardCharsets.UTF_16, maxKeys);
|
||||
decodeTo(buf.getBuffer().toString(), adder, StandardCharsets.UTF_16, maxKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -572,6 +635,22 @@ public class UrlEncoded
|
|||
*/
|
||||
public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
decodeTo(in, map::add, charset, maxLength, maxKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded parameters to Map.
|
||||
*
|
||||
* @param in the stream containing the encoded parameters
|
||||
* @param adder Function to add a parameter
|
||||
* @param charset the charset to use for decoding
|
||||
* @param maxLength the maximum length of the form to decode
|
||||
* @param maxKeys the maximum number of keys to decode
|
||||
* @throws IOException if unable to decode input stream
|
||||
*/
|
||||
public static void decodeTo(InputStream in, BiConsumer<String, String> adder, Charset charset, int maxLength, int maxKeys)
|
||||
throws IOException
|
||||
{
|
||||
//no charset present, use the configured default
|
||||
if (charset == null)
|
||||
|
@ -579,19 +658,19 @@ public class UrlEncoded
|
|||
|
||||
if (StandardCharsets.UTF_8.equals(charset))
|
||||
{
|
||||
decodeUtf8To(in, map, maxLength, maxKeys);
|
||||
decodeUtf8To(in, adder, maxLength, maxKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
if (StandardCharsets.ISO_8859_1.equals(charset))
|
||||
{
|
||||
decode88591To(in, map, maxLength, maxKeys);
|
||||
decode88591To(in, adder, maxLength, maxKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
|
||||
{
|
||||
decodeUtf16To(in, map, maxLength, maxKeys);
|
||||
decodeUtf16To(in, adder, maxLength, maxKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -604,6 +683,7 @@ public class UrlEncoded
|
|||
|
||||
try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2())
|
||||
{
|
||||
int keys = 0;
|
||||
int size;
|
||||
|
||||
while ((c = in.read()) > 0)
|
||||
|
@ -616,14 +696,16 @@ public class UrlEncoded
|
|||
output.setCount(0);
|
||||
if (key != null)
|
||||
{
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (value != null && value.length() > 0)
|
||||
{
|
||||
map.add(value, "");
|
||||
adder.accept(value, "");
|
||||
keys++;
|
||||
}
|
||||
key = null;
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
break;
|
||||
case '=':
|
||||
if (key != null)
|
||||
|
@ -655,16 +737,24 @@ public class UrlEncoded
|
|||
{
|
||||
value = size == 0 ? "" : output.toString(charset);
|
||||
output.setCount(0);
|
||||
map.add(key, value);
|
||||
adder.accept(key, value);
|
||||
keys++;
|
||||
}
|
||||
else if (size > 0)
|
||||
{
|
||||
map.add(output.toString(charset), "");
|
||||
adder.accept(output.toString(charset), "");
|
||||
keys++;
|
||||
}
|
||||
checkMaxKeys(map, maxKeys);
|
||||
checkMaxKeys(keys, maxKeys);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkMaxKeys(int size, int maxKeys)
|
||||
{
|
||||
if (maxKeys >= 0 && size > maxKeys)
|
||||
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", size, maxKeys));
|
||||
}
|
||||
|
||||
private static void checkMaxKeys(MultiMap<String> map, int maxKeys)
|
||||
{
|
||||
int size = map.size();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class Negotiated
|
|||
else
|
||||
{
|
||||
map = new HashMap<>();
|
||||
Fields fields = new Fields();
|
||||
Fields fields = new Fields(true);
|
||||
UrlEncoded.decodeUtf8To(rawQuery, fields);
|
||||
for (Fields.Field field : fields)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
class CrossContextDispatcher implements RequestDispatcher
|
||||
{
|
||||
public static final String ORIGINAL_URI = "org.eclipse.jetty.dispatch.originalURI";
|
||||
public static final String ORIGINAL_QUERY_STRING = "org.eclipse.jetty.dispatch.originalQueryString";
|
||||
public static final String ORIGINAL_SERVLET_MAPPING = "org.eclipse.jetty.dispatch.originalServletMapping";
|
||||
public static final String ORIGINAL_CONTEXT_PATH = "org.eclipse.jetty.dispatch.originalContextPath";
|
||||
|
||||
public static final Set<String> ATTRIBUTES = Set.of(
|
||||
RequestDispatcher.FORWARD_REQUEST_URI,
|
||||
RequestDispatcher.FORWARD_MAPPING,
|
||||
RequestDispatcher.FORWARD_CONTEXT_PATH,
|
||||
RequestDispatcher.FORWARD_SERVLET_PATH,
|
||||
RequestDispatcher.FORWARD_QUERY_STRING,
|
||||
RequestDispatcher.FORWARD_PATH_INFO,
|
||||
RequestDispatcher.INCLUDE_REQUEST_URI,
|
||||
RequestDispatcher.INCLUDE_MAPPING,
|
||||
RequestDispatcher.INCLUDE_CONTEXT_PATH,
|
||||
RequestDispatcher.INCLUDE_SERVLET_PATH,
|
||||
RequestDispatcher.INCLUDE_QUERY_STRING,
|
||||
RequestDispatcher.INCLUDE_PATH_INFO,
|
||||
"javax.servlet.include.request_uri",
|
||||
"javax.servlet.include.mapping",
|
||||
"javax.servlet.include.context_path",
|
||||
"javax.servlet.include.servlet_path",
|
||||
"javax.servlet.include.query_string",
|
||||
"javax.servlet.include.path_info",
|
||||
ServletContextRequest.MULTIPART_CONFIG_ELEMENT,
|
||||
ContextHandler.CROSS_CONTEXT_ATTRIBUTE,
|
||||
ORIGINAL_URI,
|
||||
ORIGINAL_QUERY_STRING,
|
||||
ORIGINAL_SERVLET_MAPPING,
|
||||
ORIGINAL_CONTEXT_PATH
|
||||
);
|
||||
|
||||
private final CrossContextServletContext _targetContext;
|
||||
private final HttpURI _uri;
|
||||
private final String _decodedPathInContext;
|
||||
|
||||
private class IncludeRequest extends ServletCoreRequest
|
||||
{
|
||||
public IncludeRequest(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
super(httpServletRequest, new Attributes.Synthetic(new ServletAttributes(httpServletRequest))
|
||||
{
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith("javax.servlet."))
|
||||
name = "jakarta.servlet." + name.substring(14);
|
||||
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getSyntheticAttribute(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//Servlet Spec 9.3.1 no include attributes if a named dispatcher
|
||||
/* if (_namedServlet != null && name.startsWith(Dispatcher.__INCLUDE_PREFIX))
|
||||
return null;*/
|
||||
|
||||
//Special include attributes refer to the target context and path
|
||||
return switch (name)
|
||||
{
|
||||
case RequestDispatcher.INCLUDE_MAPPING -> null;
|
||||
case RequestDispatcher.INCLUDE_SERVLET_PATH -> null;
|
||||
case RequestDispatcher.INCLUDE_PATH_INFO -> _decodedPathInContext;
|
||||
case RequestDispatcher.INCLUDE_REQUEST_URI -> (_uri == null) ? null : _uri.getPath();
|
||||
case RequestDispatcher.INCLUDE_CONTEXT_PATH -> _targetContext.getContextPath();
|
||||
case RequestDispatcher.INCLUDE_QUERY_STRING -> (_uri == null) ? null : _uri.getQuery();
|
||||
case ContextHandler.CROSS_CONTEXT_ATTRIBUTE -> DispatcherType.INCLUDE.toString();
|
||||
|
||||
case ORIGINAL_URI -> httpServletRequest.getRequestURI();
|
||||
case ORIGINAL_QUERY_STRING -> httpServletRequest.getQueryString();
|
||||
case ORIGINAL_SERVLET_MAPPING -> httpServletRequest.getHttpServletMapping();
|
||||
case ORIGINAL_CONTEXT_PATH -> httpServletRequest.getContextPath();
|
||||
|
||||
default -> httpServletRequest.getAttribute(name);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSyntheticNameSet()
|
||||
{
|
||||
return ATTRIBUTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith("javax.servlet."))
|
||||
name = "jakarta.servlet." + name.substring(14);
|
||||
|
||||
return super.setAttribute(name, attribute);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
//return the uri of the dispatch target
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
|
||||
private class IncludeResponse extends ServletCoreResponse
|
||||
{
|
||||
public IncludeResponse(Request coreRequest, HttpServletResponse httpServletResponse)
|
||||
{
|
||||
super(coreRequest, httpServletResponse, true);
|
||||
}
|
||||
}
|
||||
|
||||
private class ForwardRequest extends ServletCoreRequest
|
||||
{
|
||||
/**
|
||||
* @param httpServletRequest the request to wrap
|
||||
*/
|
||||
public ForwardRequest(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
super(httpServletRequest, new Attributes.Synthetic(new ServletAttributes(httpServletRequest))
|
||||
{
|
||||
@Override
|
||||
protected Object getSyntheticAttribute(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith("javax.servlet."))
|
||||
name = "jakarta.servlet." + name.substring(14);
|
||||
|
||||
return switch (name)
|
||||
{
|
||||
case RequestDispatcher.FORWARD_REQUEST_URI -> httpServletRequest.getRequestURI();
|
||||
case RequestDispatcher.FORWARD_SERVLET_PATH -> httpServletRequest.getServletPath();
|
||||
case RequestDispatcher.FORWARD_PATH_INFO -> httpServletRequest.getPathInfo();
|
||||
case RequestDispatcher.FORWARD_CONTEXT_PATH -> httpServletRequest.getContextPath();
|
||||
case RequestDispatcher.FORWARD_MAPPING -> httpServletRequest.getHttpServletMapping();
|
||||
case RequestDispatcher.FORWARD_QUERY_STRING -> httpServletRequest.getQueryString();
|
||||
case RequestDispatcher.INCLUDE_MAPPING -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_REQUEST_URI -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_CONTEXT_PATH -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_QUERY_STRING -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_SERVLET_PATH -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_PATH_INFO -> REMOVED;
|
||||
//TODO
|
||||
//case ServletContextRequest.MULTIPART_CONFIG_ELEMENT -> httpServletRequest.getAttribute(ServletMultiPartFormData.class.getName());
|
||||
case ContextHandler.CROSS_CONTEXT_ATTRIBUTE -> DispatcherType.FORWARD.toString();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSyntheticNameSet()
|
||||
{
|
||||
return ATTRIBUTES;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
|
||||
CrossContextDispatcher(CrossContextServletContext targetContext, HttpURI uri, String decodedPathInContext)
|
||||
{
|
||||
_targetContext = targetContext;
|
||||
_uri = uri;
|
||||
_decodedPathInContext = decodedPathInContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(ServletRequest servletRequest, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
HttpServletRequest httpServletRequest = (servletRequest instanceof HttpServletRequest) ? ((HttpServletRequest)servletRequest) : new ServletRequestHttpWrapper(servletRequest);
|
||||
HttpServletResponse httpResponse = (response instanceof HttpServletResponse) ? (HttpServletResponse)response : new ServletResponseHttpWrapper(response);
|
||||
|
||||
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(servletRequest);
|
||||
servletContextRequest.getServletContextResponse().resetForForward();
|
||||
|
||||
ForwardRequest forwardRequest = new ForwardRequest(httpServletRequest);
|
||||
|
||||
try (Blocker.Callback callback = Blocker.callback())
|
||||
{
|
||||
_targetContext.getTargetContext().getContextHandler().handle(forwardRequest, new ServletCoreResponse(forwardRequest, httpResponse, false), callback);
|
||||
callback.block();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// If we are not async and not closed already, then close via the possibly wrapped response.
|
||||
if (!servletContextRequest.getState().isAsync() && !servletContextRequest.getServletContextResponse().hasLastWrite())
|
||||
{
|
||||
Closeable closeable;
|
||||
try
|
||||
{
|
||||
closeable = response.getOutputStream();
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
closeable = response.getWriter();
|
||||
}
|
||||
IO.close(closeable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void include(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException
|
||||
{
|
||||
HttpServletRequest httpServletRequest = (servletRequest instanceof HttpServletRequest) ? ((HttpServletRequest)servletRequest) : new ServletRequestHttpWrapper(servletRequest);
|
||||
HttpServletResponse httpServletResponse = (servletResponse instanceof HttpServletResponse) ? (HttpServletResponse)servletResponse : new ServletResponseHttpWrapper(servletResponse);
|
||||
ServletContextResponse servletContextResponse = ServletContextResponse.getServletContextResponse(servletResponse);
|
||||
|
||||
IncludeRequest includeRequest = new IncludeRequest(httpServletRequest);
|
||||
IncludeResponse includeResponse = new IncludeResponse(includeRequest, httpServletResponse);
|
||||
|
||||
try (Blocker.Callback callback = Blocker.callback())
|
||||
{
|
||||
_targetContext.getTargetContext().getContextHandler().handle(includeRequest, includeResponse, callback);
|
||||
callback.block();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.EventListener;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterRegistration;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.SessionCookieConfig;
|
||||
import jakarta.servlet.SessionTrackingMode;
|
||||
import jakarta.servlet.descriptor.JspConfigDescriptor;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link ServletContext} used for cross context dispatch, which wraps an arbitrary {@link ContextHandler}
|
||||
*/
|
||||
class CrossContextServletContext implements ServletContext
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CrossContextServletContext.class);
|
||||
|
||||
private final ServletContextHandler _servletContextHandler;
|
||||
private final ContextHandler.ScopedContext _targetContext;
|
||||
|
||||
protected CrossContextServletContext(ServletContextHandler servletContextHandler, ContextHandler.ScopedContext targetContext)
|
||||
{
|
||||
_servletContextHandler = servletContextHandler;
|
||||
_targetContext = Objects.requireNonNull(targetContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextPath()
|
||||
{
|
||||
return _targetContext.getContextPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getContext(String uripath)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMajorVersion()
|
||||
{
|
||||
return _servletContextHandler.getServletContext().getMajorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinorVersion()
|
||||
{
|
||||
return _servletContextHandler.getServletContext().getMinorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEffectiveMajorVersion()
|
||||
{
|
||||
return _servletContextHandler.getServletContext().getEffectiveMajorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEffectiveMinorVersion()
|
||||
{
|
||||
return _servletContextHandler.getServletContext().getEffectiveMajorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType(String file)
|
||||
{
|
||||
return _targetContext.getMimeTypes().getMimeByExtension(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getResourcePaths(String path)
|
||||
{
|
||||
Resource resource = _targetContext.getBaseResource().resolve(path);
|
||||
if (resource != null && resource.isDirectory())
|
||||
return resource.list().stream().map(Resource::getPath).map(Path::getFileName).map(Path::toString).collect(Collectors.toSet());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String path) throws MalformedURLException
|
||||
{
|
||||
return _targetContext.getBaseResource().resolve(path).getURI().toURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(String path)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String uriInContext)
|
||||
{
|
||||
// uriInContext is encoded, potentially with query.
|
||||
if (uriInContext == null)
|
||||
return null;
|
||||
|
||||
if (!uriInContext.startsWith("/"))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
String contextPath = getContextPath();
|
||||
// uriInContext is canonical by HttpURI.
|
||||
HttpURI.Mutable uri = HttpURI.build(uriInContext);
|
||||
String encodedPathInContext = uri.getCanonicalPath();
|
||||
if (StringUtil.isEmpty(encodedPathInContext))
|
||||
return null;
|
||||
|
||||
if (!StringUtil.isEmpty(contextPath))
|
||||
{
|
||||
uri.path(URIUtil.addPaths(contextPath, uri.getPath()));
|
||||
encodedPathInContext = uri.getCanonicalPath().substring(contextPath.length());
|
||||
}
|
||||
return new CrossContextDispatcher(this, uri, URIUtil.decodePath(encodedPathInContext));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.trace("IGNORED", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getNamedDispatcher(String name)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String msg)
|
||||
{
|
||||
LOG.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message, Throwable throwable)
|
||||
{
|
||||
LOG.warn(message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealPath(String path)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerInfo()
|
||||
{
|
||||
return _servletContextHandler.getServer().getServerInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitParameter(String name)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getInitParameterNames()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setInitParameter(String name, String value)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _targetContext.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames()
|
||||
{
|
||||
return Collections.enumeration(_targetContext.getAttributeNameSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object object)
|
||||
{
|
||||
_targetContext.setAttribute(name, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name)
|
||||
{
|
||||
_targetContext.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletContextName()
|
||||
{
|
||||
return _targetContext.getContextHandler().getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration getServletRegistration(String servletName)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends ServletRegistration> getServletRegistrations()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration getFilterRegistration(String filterName)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends FilterRegistration> getFilterRegistrations()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionCookieConfig getSessionCookieConfig()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends EventListener> void addListener(T t)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Class<? extends EventListener> listenerClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JspConfigDescriptor getJspConfigDescriptor()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader()
|
||||
{
|
||||
return _targetContext.getClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareRoles(String... roleNames)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVirtualServerName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionTimeout()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTimeout(int sessionTimeout)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestCharacterEncoding()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestCharacterEncoding(String encoding)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCharacterEncoding()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResponseCharacterEncoding(String encoding)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
ContextHandler.ScopedContext getTargetContext()
|
||||
{
|
||||
return _targetContext;
|
||||
}
|
||||
}
|
|
@ -487,7 +487,7 @@ public class DefaultServlet extends HttpServlet
|
|||
// otherwise wrap the servlet request as a core request
|
||||
Request coreRequest = httpServletRequest instanceof ServletApiRequest
|
||||
? servletChannel.getRequest()
|
||||
: new ServletCoreRequest(httpServletRequest);
|
||||
: ServletCoreRequest.wrap(httpServletRequest);
|
||||
|
||||
// If the servlet response has been wrapped and has been written to,
|
||||
// then the servlet response must be wrapped as a core response
|
||||
|
|
|
@ -42,8 +42,8 @@ import org.eclipse.jetty.ee10.servlet.util.ServletOutputStreamWrapper;
|
|||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedResource;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
|
||||
|
@ -64,6 +64,8 @@ public class Dispatcher implements RequestDispatcher
|
|||
*/
|
||||
public static final String __ORIGINAL_REQUEST = "org.eclipse.jetty.originalRequest";
|
||||
|
||||
public static final String JETTY_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
|
||||
|
||||
private final ServletContextHandler _contextHandler;
|
||||
private final HttpURI _uri;
|
||||
private final String _decodedPathInContext;
|
||||
|
@ -168,31 +170,48 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
public class ParameterRequestWrapper extends HttpServletRequestWrapper
|
||||
{
|
||||
private MultiMap<String> _params = null;
|
||||
private Fields _parameters = null;
|
||||
|
||||
public ParameterRequestWrapper(HttpServletRequest request)
|
||||
{
|
||||
super(request);
|
||||
}
|
||||
|
||||
private MultiMap<String> getParams()
|
||||
private Fields getParameters()
|
||||
{
|
||||
if (_params == null)
|
||||
if (_parameters == null)
|
||||
{
|
||||
_params = new MultiMap<>();
|
||||
|
||||
// Have to assume ENCODING because we can't know otherwise.
|
||||
String targetQuery = (_uri == null) ? null : _uri.getQuery();
|
||||
if (targetQuery != null)
|
||||
UrlEncoded.decodeTo(targetQuery, _params, UrlEncoded.ENCODING);
|
||||
|
||||
for (Enumeration<String> names = getRequest().getParameterNames(); names.hasMoreElements(); )
|
||||
if (getRequest() instanceof ServletApiRequest servletApiRequest)
|
||||
{
|
||||
String name = names.nextElement();
|
||||
_params.addValues(name, getRequest().getParameterValues(name));
|
||||
Fields parameters = servletApiRequest.getParameters();
|
||||
|
||||
if (targetQuery == null)
|
||||
{
|
||||
_parameters = parameters;
|
||||
}
|
||||
else
|
||||
{
|
||||
_parameters = new Fields(true);
|
||||
UrlEncoded.decodeTo(targetQuery, _parameters::add, UrlEncoded.ENCODING);
|
||||
_parameters.addAll(parameters);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parameters = new Fields(true);
|
||||
// Have to assume ENCODING because we can't know otherwise.
|
||||
if (targetQuery != null)
|
||||
UrlEncoded.decodeTo(targetQuery, _parameters::add, UrlEncoded.ENCODING);
|
||||
for (Enumeration<String> names = getRequest().getParameterNames(); names.hasMoreElements(); )
|
||||
{
|
||||
String name = names.nextElement();
|
||||
_parameters.add(name, getRequest().getParameterValues(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return _params;
|
||||
return _parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -216,25 +235,25 @@ public class Dispatcher implements RequestDispatcher
|
|||
@Override
|
||||
public String getParameter(String name)
|
||||
{
|
||||
return getParams().getValue(name);
|
||||
return getParameters().getValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap()
|
||||
{
|
||||
return Collections.unmodifiableMap(getParams().toStringArrayMap());
|
||||
return Collections.unmodifiableMap(getParameters().toStringArrayMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getParameterNames()
|
||||
{
|
||||
return Collections.enumeration(getParams().keySet());
|
||||
return Collections.enumeration(getParameters().getNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name)
|
||||
{
|
||||
List<String> vals = getParams().getValues(name);
|
||||
List<String> vals = getParameters().getValues(name);
|
||||
if (vals == null)
|
||||
return null;
|
||||
return vals.toArray(new String[0]);
|
||||
|
@ -434,7 +453,6 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
private static class IncludeResponse extends HttpServletResponseWrapper
|
||||
{
|
||||
public static final String JETTY_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
|
||||
ServletOutputStream _servletOutputStream;
|
||||
PrintWriter _printWriter;
|
||||
PrintWriter _mustFlush;
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.nio.charset.IllegalCharsetNameException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.security.Principal;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
@ -58,7 +57,6 @@ import jakarta.servlet.http.Part;
|
|||
import jakarta.servlet.http.PushBuilder;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler.ServletRequestInfo;
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.CookieCache;
|
||||
import org.eclipse.jetty.http.CookieCompliance;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpException;
|
||||
|
@ -70,11 +68,13 @@ import org.eclipse.jetty.http.HttpURI;
|
|||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.SetCookieParser;
|
||||
import org.eclipse.jetty.http.pathmap.MatchedResource;
|
||||
import org.eclipse.jetty.io.QuietException;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.security.AuthenticationState;
|
||||
import org.eclipse.jetty.security.UserIdentity;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
import org.eclipse.jetty.server.CookieCache;
|
||||
import org.eclipse.jetty.server.FormFields;
|
||||
import org.eclipse.jetty.server.HttpCookieUtils;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -89,6 +89,7 @@ import org.eclipse.jetty.util.HostPort;
|
|||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -99,6 +100,132 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
public class ServletApiRequest implements HttpServletRequest
|
||||
{
|
||||
public static class CrossContextForwarded extends ServletApiRequest
|
||||
{
|
||||
protected CrossContextForwarded(ServletContextRequest servletContextRequest)
|
||||
{
|
||||
super(servletContextRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extractQueryParameters() throws BadMessageException
|
||||
{
|
||||
// Extract query string parameters; these may be replaced by a forward()
|
||||
// and may have already been extracted by mergeQueryParameters().
|
||||
if (_queryParameters == null)
|
||||
{
|
||||
String forwardQueryString = (String)getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
|
||||
String originalQueryString = getQueryString();
|
||||
if (StringUtil.isBlank(forwardQueryString))
|
||||
{
|
||||
if (StringUtil.isBlank(originalQueryString))
|
||||
{
|
||||
_queryParameters = ServletContextRequest.NO_PARAMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
_queryParameters = new Fields(true);
|
||||
UrlEncoded.decodeTo(forwardQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_queryParameters = new Fields(true);
|
||||
if (!StringUtil.isBlank(originalQueryString))
|
||||
UrlEncoded.decodeTo(originalQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
UrlEncoded.decodeTo(forwardQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CrossContextIncluded extends ServletApiRequest
|
||||
{
|
||||
private final ServletPathMapping _originalMapping;
|
||||
|
||||
protected CrossContextIncluded(ServletContextRequest servletContextRequest)
|
||||
{
|
||||
super(servletContextRequest);
|
||||
Request dispatchedRequest = servletContextRequest.getWrapped();
|
||||
|
||||
_originalMapping = ServletPathMapping.from(dispatchedRequest.getAttribute(CrossContextDispatcher.ORIGINAL_SERVLET_MAPPING));
|
||||
|
||||
//ensure the request is set up with the correct INCLUDE attributes now we know the matchedResource
|
||||
MatchedResource<ServletHandler.MappedServlet> matchedResource = servletContextRequest.getMatchedResource();
|
||||
dispatchedRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, matchedResource.getResource().getServletPathMapping(getServletRequestInfo().getDecodedPathInContext()));
|
||||
dispatchedRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, matchedResource.getMatchedPath().getPathMatch());
|
||||
dispatchedRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, matchedResource.getMatchedPath().getPathInfo());
|
||||
dispatchedRequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH, servletContextRequest.getContext().getContextPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathInfo()
|
||||
{
|
||||
return _originalMapping == null ? null : _originalMapping.getPathInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextPath()
|
||||
{
|
||||
return (String)getAttribute(CrossContextDispatcher.ORIGINAL_CONTEXT_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
return (String)getAttribute(CrossContextDispatcher.ORIGINAL_QUERY_STRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletPath()
|
||||
{
|
||||
return _originalMapping == null ? null : _originalMapping.getServletPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpServletMapping getHttpServletMapping()
|
||||
{
|
||||
return _originalMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestURI()
|
||||
{
|
||||
return (String)getAttribute(CrossContextDispatcher.ORIGINAL_URI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extractQueryParameters() throws BadMessageException
|
||||
{
|
||||
// Extract query string parameters; these may be replaced by a forward()
|
||||
// and may have already been extracted by mergeQueryParameters().
|
||||
if (_queryParameters == null)
|
||||
{
|
||||
String includedQueryString = (String)getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
|
||||
String originalQueryString = getQueryString();
|
||||
if (StringUtil.isBlank(includedQueryString))
|
||||
{
|
||||
if (StringUtil.isBlank(originalQueryString))
|
||||
{
|
||||
_queryParameters = ServletContextRequest.NO_PARAMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
_queryParameters = new Fields(true);
|
||||
UrlEncoded.decodeTo(includedQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_queryParameters = new Fields(true);
|
||||
if (!StringUtil.isBlank(originalQueryString))
|
||||
UrlEncoded.decodeTo(originalQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
UrlEncoded.decodeTo(includedQueryString, _queryParameters::add, getServletRequestInfo().getQueryEncoding());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServletApiRequest.class);
|
||||
private static final SetCookieParser SET_COOKIE_PARSER = SetCookieParser.newInstance();
|
||||
|
||||
|
@ -110,10 +237,9 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
private int _inputState = ServletContextRequest.INPUT_NONE;
|
||||
private BufferedReader _reader;
|
||||
private String _contentType;
|
||||
private boolean _contentParamsExtracted;
|
||||
private Fields _contentParameters;
|
||||
protected Fields _contentParameters;
|
||||
private Fields _parameters;
|
||||
private Fields _queryParameters;
|
||||
protected Fields _queryParameters;
|
||||
private ServletMultiPartFormData.Parts _parts;
|
||||
private boolean _asyncSupported = true;
|
||||
|
||||
|
@ -242,17 +368,20 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public Cookie[] getCookies()
|
||||
{
|
||||
List<HttpCookie> httpCookies = Request.getCookies(getRequest());
|
||||
if (httpCookies.isEmpty())
|
||||
return null;
|
||||
if (httpCookies instanceof ServletCookieList servletCookieList)
|
||||
return servletCookieList.getServletCookies();
|
||||
return CookieCache.getApiCookies(getRequest(), Cookie.class, this::convertCookie);
|
||||
}
|
||||
|
||||
ServletCookieList servletCookieList = new ServletCookieList(httpCookies, getRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
|
||||
getRequest().setAttribute(Request.COOKIE_ATTRIBUTE, servletCookieList);
|
||||
if (getRequest().getComponents().getCache().getAttribute(Request.CACHE_ATTRIBUTE) instanceof CookieCache cookieCache)
|
||||
cookieCache.replaceCookieList(servletCookieList);
|
||||
return servletCookieList.getServletCookies();
|
||||
private Cookie convertCookie(HttpCookie cookie)
|
||||
{
|
||||
CookieCompliance compliance = getRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance();
|
||||
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
|
||||
//RFC2965 defines the cookie header as supporting path and domain but RFC6265 permits only name=value
|
||||
if (CookieCompliance.RFC2965.equals(compliance))
|
||||
{
|
||||
result.setPath(cookie.getPath());
|
||||
result.setDomain(cookie.getDomain());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -500,6 +629,7 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public Collection<Part> getParts() throws IOException, ServletException
|
||||
{
|
||||
//TODO support parts read during a cross context dispatch to environment other than EE10
|
||||
if (_parts == null)
|
||||
{
|
||||
try
|
||||
|
@ -564,8 +694,8 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
try (InputStream is = p.getInputStream())
|
||||
{
|
||||
String content = IO.toString(is, charset == null ? defaultCharset : Charset.forName(charset));
|
||||
if (_contentParameters == null)
|
||||
_contentParameters = new Fields();
|
||||
if (_contentParameters == null || _contentParameters.isEmpty())
|
||||
_contentParameters = new Fields(true);
|
||||
_contentParameters.add(p.getName(), content);
|
||||
}
|
||||
}
|
||||
|
@ -821,119 +951,115 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
return Collections.unmodifiableMap(getParameters().toStringArrayMap());
|
||||
}
|
||||
|
||||
private Fields getParameters()
|
||||
public Fields getParameters()
|
||||
{
|
||||
extractContentParameters();
|
||||
extractQueryParameters();
|
||||
|
||||
// Do parameters need to be combined?
|
||||
if (ServletContextRequest.isNoParams(_queryParameters) || _queryParameters.getSize() == 0)
|
||||
_parameters = _contentParameters;
|
||||
else if (ServletContextRequest.isNoParams(_contentParameters) || _contentParameters.getSize() == 0)
|
||||
_parameters = _queryParameters;
|
||||
else if (_parameters == null)
|
||||
{
|
||||
_parameters = new Fields(true);
|
||||
_parameters.addAll(_queryParameters);
|
||||
_parameters.addAll(_contentParameters);
|
||||
}
|
||||
|
||||
// protect against calls to recycled requests (which is illegal, but
|
||||
// this gives better failures
|
||||
Fields parameters = _parameters;
|
||||
if (parameters == null)
|
||||
{
|
||||
extractContentParameters();
|
||||
extractQueryParameters();
|
||||
|
||||
// Do parameters need to be combined?
|
||||
if (ServletContextRequest.isNoParams(_queryParameters) || _queryParameters.getSize() == 0)
|
||||
_parameters = _contentParameters;
|
||||
else if (ServletContextRequest.isNoParams(_contentParameters) || _contentParameters.getSize() == 0)
|
||||
_parameters = _queryParameters;
|
||||
else if (_parameters == null)
|
||||
{
|
||||
_parameters = new Fields(true);
|
||||
_parameters.addAll(_queryParameters);
|
||||
_parameters.addAll(_contentParameters);
|
||||
}
|
||||
parameters = _parameters;
|
||||
}
|
||||
return parameters == null ? ServletContextRequest.NO_PARAMS : parameters;
|
||||
}
|
||||
|
||||
private void extractContentParameters() throws BadMessageException
|
||||
{
|
||||
if (!_contentParamsExtracted)
|
||||
// Extract content parameters; these cannot be replaced by a forward()
|
||||
// once extracted and may have already been extracted by getParts() or
|
||||
// by a processing happening after a form-based authentication.
|
||||
if (_contentParameters == null)
|
||||
{
|
||||
// content parameters need boolean protection as they can only be read
|
||||
// once, but may be reset to null by a reset
|
||||
_contentParamsExtracted = true;
|
||||
|
||||
// Extract content parameters; these cannot be replaced by a forward()
|
||||
// once extracted and may have already been extracted by getParts() or
|
||||
// by a processing happening after a form-based authentication.
|
||||
if (_contentParameters == null)
|
||||
try
|
||||
{
|
||||
try
|
||||
int contentLength = getContentLength();
|
||||
if (contentLength != 0 && _inputState == ServletContextRequest.INPUT_NONE)
|
||||
{
|
||||
int contentLength = getContentLength();
|
||||
if (contentLength != 0 && _inputState == ServletContextRequest.INPUT_NONE)
|
||||
String baseType = HttpField.getValueParameters(getContentType(), null);
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(baseType) &&
|
||||
getRequest().getConnectionMetaData().getHttpConfiguration().isFormEncodedMethod(getMethod()))
|
||||
{
|
||||
String baseType = HttpField.getValueParameters(getContentType(), null);
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(baseType) &&
|
||||
getRequest().getConnectionMetaData().getHttpConfiguration().isFormEncodedMethod(getMethod()))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
ServletContextHandler contextHandler = getServletRequestInfo().getServletContextHandler();
|
||||
int maxKeys = contextHandler.getMaxFormKeys();
|
||||
int maxContentSize = contextHandler.getMaxFormContentSize();
|
||||
_contentParameters = FormFields.from(getRequest(), maxKeys, maxContentSize).get();
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
|
||||
InterruptedException e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
ServletContextHandler contextHandler = getServletRequestInfo().getServletContextHandler();
|
||||
int maxKeys = contextHandler.getMaxFormKeys();
|
||||
int maxContentSize = contextHandler.getMaxFormContentSize();
|
||||
_contentParameters = FormFields.from(getRequest(), maxKeys, maxContentSize).get();
|
||||
}
|
||||
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) &&
|
||||
getAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT) != null)
|
||||
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
|
||||
InterruptedException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
getParts();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
String msg = "Unable to extract content parameters";
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(msg, e);
|
||||
throw new RuntimeIOException(msg, e);
|
||||
}
|
||||
catch (ServletException e)
|
||||
{
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof BadMessageException badMessageException)
|
||||
throw badMessageException;
|
||||
|
||||
String msg = "Unable to extract content parameters";
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(msg, e);
|
||||
throw new RuntimeIOException(msg, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_contentParameters = FormFields.get(getRequest()).get();
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
|
||||
InterruptedException e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
}
|
||||
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) &&
|
||||
getAttribute(ServletContextRequest.MULTIPART_CONFIG_ELEMENT) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
getParts();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
String msg = "Unable to extract content parameters";
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(msg, e);
|
||||
throw new RuntimeIOException(msg, e);
|
||||
}
|
||||
catch (ServletException e)
|
||||
{
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof BadMessageException badMessageException)
|
||||
throw badMessageException;
|
||||
|
||||
if (_contentParameters == null || _contentParameters.isEmpty())
|
||||
_contentParameters = ServletContextRequest.NO_PARAMS;
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
String msg = "Unable to extract content parameters";
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(msg, e);
|
||||
throw new RuntimeIOException(msg, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_contentParameters = FormFields.get(getRequest()).get();
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
|
||||
InterruptedException e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_contentParameters == null || _contentParameters.isEmpty())
|
||||
_contentParameters = ServletContextRequest.NO_PARAMS;
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractQueryParameters() throws BadMessageException
|
||||
protected void extractQueryParameters() throws BadMessageException
|
||||
{
|
||||
// Extract query string parameters; these may be replaced by a forward()
|
||||
// and may have already been extracted by mergeQueryParameters().
|
||||
|
@ -1286,7 +1412,9 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
@Override
|
||||
public DispatcherType getDispatcherType()
|
||||
{
|
||||
return DispatcherType.REQUEST;
|
||||
Request request = getRequest();
|
||||
String dispatchType = request.getContext().getCrossContextDispatchType(request);
|
||||
return dispatchType == null ? DispatcherType.REQUEST : DispatcherType.valueOf(dispatchType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1327,53 +1455,4 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
throw new HttpException.IllegalArgumentException(HttpStatus.BAD_REQUEST_400, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended list of HttpCookies that converts and caches a servlet Cookie array.
|
||||
*/
|
||||
private static class ServletCookieList extends AbstractList<HttpCookie>
|
||||
{
|
||||
private final List<HttpCookie> _httpCookies;
|
||||
private final Cookie[] _cookies;
|
||||
|
||||
ServletCookieList(List<HttpCookie> httpCookies, CookieCompliance compliance)
|
||||
{
|
||||
_httpCookies = httpCookies;
|
||||
_cookies = new Cookie[_httpCookies.size()];
|
||||
int i = 0;
|
||||
for (HttpCookie httpCookie : _httpCookies)
|
||||
{
|
||||
_cookies[i++] = convertCookie(httpCookie, compliance);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie get(int index)
|
||||
{
|
||||
return _httpCookies.get(index);
|
||||
}
|
||||
|
||||
public Cookie[] getServletCookies()
|
||||
{
|
||||
return _cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return _cookies.length;
|
||||
}
|
||||
|
||||
private static Cookie convertCookie(HttpCookie cookie, CookieCompliance compliance)
|
||||
{
|
||||
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
|
||||
//RFC2965 defines the cookie header as supporting path and domain but RFC6265 permits only name=value
|
||||
if (CookieCompliance.RFC2965.equals(compliance))
|
||||
{
|
||||
result.setPath(cookie.getPath());
|
||||
result.setDomain(cookie.getDomain());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1132,11 +1132,15 @@ public class ServletContextHandler extends ContextHandler
|
|||
@Override
|
||||
protected ContextRequest wrapRequest(Request request, Response response)
|
||||
{
|
||||
String decodedPathInContext;
|
||||
MatchedResource<ServletHandler.MappedServlet> matchedResource;
|
||||
|
||||
// Need to ask directly to the Context for the pathInContext, rather than using
|
||||
// Request.getPathInContext(), as the request is not yet wrapped in this Context.
|
||||
String decodedPathInContext = URIUtil.decodePath(getContext().getPathInContext(request.getHttpURI().getCanonicalPath()));
|
||||
decodedPathInContext = URIUtil.decodePath(getContext().getPathInContext(request.getHttpURI().getCanonicalPath()));
|
||||
matchedResource = _servletHandler.getMatchedServlet(decodedPathInContext);
|
||||
|
||||
|
||||
MatchedResource<ServletHandler.MappedServlet> matchedResource = _servletHandler.getMatchedServlet(decodedPathInContext);
|
||||
if (matchedResource == null)
|
||||
return wrapNoServlet(request, response);
|
||||
ServletHandler.MappedServlet mappedServlet = matchedResource.getResource();
|
||||
|
@ -2702,10 +2706,14 @@ public class ServletContextHandler extends ContextHandler
|
|||
}
|
||||
|
||||
@Override
|
||||
public jakarta.servlet.ServletContext getContext(String uripath)
|
||||
public jakarta.servlet.ServletContext getContext(String path)
|
||||
{
|
||||
//TODO jetty-12 does not currently support cross context dispatch
|
||||
return null;
|
||||
ContextHandler context = getContextHandler().getCrossContextHandler(path);
|
||||
if (context == null)
|
||||
return null;
|
||||
if (context == ServletContextHandler.this)
|
||||
return this;
|
||||
return new CrossContextServletContext(ServletContextHandler.this, context.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.AsyncListener;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletRequestAttributeListener;
|
||||
import jakarta.servlet.ServletRequestWrapper;
|
||||
|
@ -125,12 +126,12 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
|||
{
|
||||
super(servletContextApi.getContext(), request);
|
||||
_servletChannel = servletChannel;
|
||||
_servletApiRequest = newServletApiRequest();
|
||||
_matchedResource = matchedResource;
|
||||
_httpInput = _servletChannel.getHttpInput();
|
||||
_decodedPathInContext = decodedPathInContext;
|
||||
_response = newServletContextResponse(response);
|
||||
_sessionManager = sessionManager;
|
||||
_servletApiRequest = newServletApiRequest();
|
||||
_response = newServletContextResponse(response);
|
||||
_attributes = new Attributes.Synthetic(request)
|
||||
{
|
||||
@Override
|
||||
|
@ -155,6 +156,7 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
|||
return ATTRIBUTES;
|
||||
}
|
||||
};
|
||||
|
||||
addIdleTimeoutListener(_servletChannel.getServletRequestState()::onIdleTimeout);
|
||||
}
|
||||
|
||||
|
@ -219,7 +221,17 @@ public class ServletContextRequest extends ContextRequest implements ServletCont
|
|||
return new ServletApiRequest.AmbiguousURI(this, msg.toString());
|
||||
}
|
||||
|
||||
return new ServletApiRequest(this);
|
||||
if (getServletContextHandler().isCrossContextDispatchSupported())
|
||||
{
|
||||
if (DispatcherType.INCLUDE.toString().equals(getContext().getCrossContextDispatchType(getWrapped())))
|
||||
return new ServletApiRequest.CrossContextIncluded(this);
|
||||
else if (DispatcherType.FORWARD.toString().equals(getContext().getCrossContextDispatchType(getWrapped())))
|
||||
return new ServletApiRequest.CrossContextForwarded(this);
|
||||
else
|
||||
return new ServletApiRequest(this);
|
||||
}
|
||||
else
|
||||
return new ServletApiRequest(this);
|
||||
}
|
||||
|
||||
protected ServletContextResponse newServletContextResponse(Response response)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
@ -28,6 +29,7 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.content.InputStreamContentSource;
|
||||
import org.eclipse.jetty.server.Components;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
|
@ -35,6 +37,8 @@ import org.eclipse.jetty.server.HttpStream;
|
|||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.TunnelSupport;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
import static org.eclipse.jetty.util.URIUtil.addEncodedPaths;
|
||||
|
@ -51,17 +55,27 @@ import static org.eclipse.jetty.util.URIUtil.encodePath;
|
|||
* The current implementation does not support any read operations.
|
||||
* </p>
|
||||
*/
|
||||
class ServletCoreRequest implements Request
|
||||
public class ServletCoreRequest implements Request
|
||||
{
|
||||
public static Request wrap(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
return new ServletCoreRequest(httpServletRequest, null);
|
||||
}
|
||||
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private final ServletContextRequest _servletContextRequest;
|
||||
private final HttpFields _httpFields;
|
||||
private final HttpURI _uri;
|
||||
private final Attributes _attributes;
|
||||
private final boolean _wrapped;
|
||||
private Content.Source _source;
|
||||
|
||||
ServletCoreRequest(HttpServletRequest request)
|
||||
ServletCoreRequest(HttpServletRequest request, Attributes attributes)
|
||||
{
|
||||
_servletRequest = request;
|
||||
_wrapped = !(request instanceof ServletApiRequest);
|
||||
_servletContextRequest = ServletContextRequest.getServletContextRequest(_servletRequest);
|
||||
_attributes = attributes == null ? _servletContextRequest : attributes;
|
||||
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
|
||||
|
@ -93,6 +107,15 @@ class ServletCoreRequest implements Request
|
|||
builder.path(request.getRequestURI());
|
||||
builder.query(request.getQueryString());
|
||||
_uri = builder.asImmutable();
|
||||
|
||||
_source = _wrapped ? null : _servletContextRequest;
|
||||
}
|
||||
|
||||
private Content.Source source() throws IOException
|
||||
{
|
||||
if (_source == null)
|
||||
_source = _wrapped ? new InputStreamContentSource(getServletRequest().getInputStream()) : _servletContextRequest;
|
||||
return _source;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -133,51 +156,44 @@ class ServletCoreRequest implements Request
|
|||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.removeAttribute(name);
|
||||
return value;
|
||||
return _attributes.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
_servletRequest.setAttribute(name, attribute);
|
||||
return value;
|
||||
return _attributes.setAttribute(name, attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _servletRequest.getAttribute(name);
|
||||
return _attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
Set<String> set = new HashSet<>();
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
set.add(e.nextElement());
|
||||
}
|
||||
return set;
|
||||
return _attributes.getAttributeNameSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
_servletRequest.removeAttribute(e.nextElement());
|
||||
}
|
||||
_attributes.clearAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable failure)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
try
|
||||
{
|
||||
source().fail(failure);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(failure, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -201,7 +217,14 @@ class ServletCoreRequest implements Request
|
|||
@Override
|
||||
public void demand(Runnable demandCallback)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
try
|
||||
{
|
||||
source().demand(demandCallback);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
demandCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,13 +248,35 @@ class ServletCoreRequest implements Request
|
|||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
try
|
||||
{
|
||||
return source().read();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
return Content.Chunk.from(t, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeAvailable()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
if (_wrapped)
|
||||
{
|
||||
try
|
||||
{
|
||||
Content.Source.consumeAll(source());
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _servletContextRequest.consumeAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -263,4 +308,67 @@ class ServletCoreRequest implements Request
|
|||
{
|
||||
return Session.getSession(_servletRequest.getSession(create));
|
||||
}
|
||||
|
||||
public static class ServletAttributes implements Attributes
|
||||
{
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private Set<String> _attributeNames;
|
||||
|
||||
public ServletAttributes(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
_servletRequest = httpServletRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
if (value != null)
|
||||
_attributeNames = null;
|
||||
_servletRequest.removeAttribute(name);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
if (value == null)
|
||||
_attributeNames = null;
|
||||
_servletRequest.setAttribute(name, attribute);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _servletRequest.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
Set<String> set = _attributeNames;
|
||||
if (set == null)
|
||||
{
|
||||
set = new HashSet<>();
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
set.add(e.nextElement());
|
||||
_attributeNames = set;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
_attributeNames = null;
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
_servletRequest.removeAttribute(e.nextElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,25 +34,33 @@ import org.eclipse.jetty.server.Response;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A {@link HttpServletResponse} wrapped as a core {@link Response}.
|
||||
* All write operations are internally converted to blocking writes on the servlet API.
|
||||
*/
|
||||
class ServletCoreResponse implements Response
|
||||
public class ServletCoreResponse implements Response
|
||||
{
|
||||
private final HttpServletResponse _response;
|
||||
public Response wrap(Request coreRequest, HttpServletResponse httpServletResponse, boolean included)
|
||||
{
|
||||
return new ServletCoreResponse(coreRequest, httpServletResponse, included);
|
||||
}
|
||||
|
||||
private final HttpServletResponse _httpServletResponse;
|
||||
private final Request _coreRequest;
|
||||
private final HttpFields.Mutable _httpFields;
|
||||
private final boolean _included;
|
||||
private final ServletContextResponse _servletContextResponse;
|
||||
private final boolean _wrapped;
|
||||
|
||||
public ServletCoreResponse(Request coreRequest, HttpServletResponse response, boolean included)
|
||||
ServletCoreResponse(Request coreRequest, HttpServletResponse httpServletResponse, boolean included)
|
||||
{
|
||||
_coreRequest = coreRequest;
|
||||
_response = response;
|
||||
_servletContextResponse = ServletContextResponse.getServletContextResponse(response);
|
||||
HttpFields.Mutable fields = new HttpServletResponseHttpFields(response);
|
||||
_httpServletResponse = httpServletResponse;
|
||||
_servletContextResponse = ServletContextResponse.getServletContextResponse(httpServletResponse);
|
||||
_wrapped = !(httpServletResponse instanceof ServletApiResponse);
|
||||
HttpFields.Mutable fields = new HttpServletResponseHttpFields(httpServletResponse);
|
||||
if (included)
|
||||
{
|
||||
// If included, accept but ignore mutations.
|
||||
|
@ -61,6 +69,12 @@ class ServletCoreResponse implements Response
|
|||
@Override
|
||||
public HttpField onAddField(HttpField field)
|
||||
{
|
||||
String name = field == null ? null : field.getName();
|
||||
if (!StringUtil.isBlank(name) && name.startsWith(Dispatcher.JETTY_INCLUDE_HEADER_PREFIX))
|
||||
{
|
||||
return new HttpField(name.substring(Dispatcher.JETTY_INCLUDE_HEADER_PREFIX.length()), field.getValue());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -69,6 +83,12 @@ class ServletCoreResponse implements Response
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField onReplaceField(HttpField oldField, HttpField newField)
|
||||
{
|
||||
return oldField;
|
||||
}
|
||||
};
|
||||
}
|
||||
_httpFields = fields;
|
||||
|
@ -83,7 +103,7 @@ class ServletCoreResponse implements Response
|
|||
|
||||
public HttpServletResponse getServletResponse()
|
||||
{
|
||||
return _response;
|
||||
return _httpServletResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -101,7 +121,7 @@ class ServletCoreResponse implements Response
|
|||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _response.isCommitted();
|
||||
return _httpServletResponse.isCommitted();
|
||||
}
|
||||
|
||||
private boolean isWriting()
|
||||
|
@ -116,29 +136,36 @@ class ServletCoreResponse implements Response
|
|||
last = false;
|
||||
try
|
||||
{
|
||||
if (BufferUtil.hasContent(byteBuffer))
|
||||
if (!_wrapped && !_servletContextResponse.isWritingOrStreaming())
|
||||
{
|
||||
if (isWriting())
|
||||
{
|
||||
String characterEncoding = _response.getCharacterEncoding();
|
||||
try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
|
||||
InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
|
||||
{
|
||||
IO.copy(reader, _response.getWriter());
|
||||
}
|
||||
|
||||
if (last)
|
||||
_response.getWriter().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
|
||||
if (last)
|
||||
_response.getOutputStream().close();
|
||||
}
|
||||
_servletContextResponse.write(last, byteBuffer, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BufferUtil.hasContent(byteBuffer))
|
||||
{
|
||||
if (isWriting())
|
||||
{
|
||||
String characterEncoding = _httpServletResponse.getCharacterEncoding();
|
||||
try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
|
||||
InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
|
||||
{
|
||||
IO.copy(reader, _httpServletResponse.getWriter());
|
||||
}
|
||||
|
||||
callback.succeeded();
|
||||
if (last)
|
||||
_httpServletResponse.getWriter().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, _httpServletResponse.getOutputStream());
|
||||
if (last)
|
||||
_httpServletResponse.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
|
@ -155,7 +182,7 @@ class ServletCoreResponse implements Response
|
|||
@Override
|
||||
public int getStatus()
|
||||
{
|
||||
return _response.getStatus();
|
||||
return _httpServletResponse.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -163,7 +190,7 @@ class ServletCoreResponse implements Response
|
|||
{
|
||||
if (_included)
|
||||
return;
|
||||
_response.setStatus(code);
|
||||
_httpServletResponse.setStatus(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -180,19 +207,19 @@ class ServletCoreResponse implements Response
|
|||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_response.reset();
|
||||
_httpServletResponse.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
return _servletContextResponse.writeInterim(status, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _response);
|
||||
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _httpServletResponse);
|
||||
}
|
||||
|
||||
private static class HttpServletResponseHttpFields implements HttpFields.Mutable
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
|
@ -111,9 +114,10 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
case PREFIX_GLOB:
|
||||
_mappingMatch = MappingMatch.PATH;
|
||||
_servletPath = pathSpec.getPrefix();
|
||||
// TODO avoid the substring on the known servletPath!
|
||||
_matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
|
||||
_pathInfo = matchedPath != null ? matchedPath.getPathInfo() : null;
|
||||
_pathInfo = matchedPath != null
|
||||
? matchedPath.getPathInfo()
|
||||
: _servletPath.length() == pathInContext.length() ? null : pathInContext.substring(_servletPath.length());
|
||||
break;
|
||||
|
||||
case SUFFIX_GLOB:
|
||||
|
@ -135,6 +139,16 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
this(pathSpec, servletName, pathInContext, null);
|
||||
}
|
||||
|
||||
private ServletPathMapping(MappingMatch mappingMatch, String matchValue, String pattern, String servletName, String servletPath, String pathInfo)
|
||||
{
|
||||
_mappingMatch = mappingMatch;
|
||||
_matchValue = matchValue;
|
||||
_pattern = pattern;
|
||||
_servletName = servletName;
|
||||
_servletPath = servletPath;
|
||||
_pathInfo = pathInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMatchValue()
|
||||
{
|
||||
|
@ -173,12 +187,46 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
public String toString()
|
||||
{
|
||||
return "ServletPathMapping{" +
|
||||
"matchValue=" + _matchValue +
|
||||
"mappingMatch=" + _mappingMatch +
|
||||
", matchValue=" + _matchValue +
|
||||
", pattern=" + _pattern +
|
||||
", servletName=" + _servletName +
|
||||
", mappingMatch=" + _mappingMatch +
|
||||
", servletPath=" + _servletPath +
|
||||
", pathInfo=" + _pathInfo +
|
||||
"}";
|
||||
}
|
||||
|
||||
private static final Pattern DESERIALIZE = Pattern.compile("ServletPathMapping\\{" +
|
||||
"mappingMatch=(?<mappingMatch>[^,]+), " +
|
||||
"matchValue=(?<matchValue>[^,]+), " +
|
||||
"pattern=(?<pattern>[^,]+), " +
|
||||
"servletName=(?<servletName>[^,]+), " +
|
||||
"servletPath=(?<servletPath>[^,]+), " +
|
||||
"pathInfo=(?<pathInfo>[^}]+)}");
|
||||
|
||||
/**
|
||||
* Obtain a {@link ServletPathMapping} instance from an object which may be an instance of a mapping
|
||||
* from a different EE version obtained from a cross context cross environment dispatch
|
||||
* @param o The object to caste or deserialize from the string representation.
|
||||
* @return A ServletPathMapping
|
||||
*/
|
||||
public static ServletPathMapping from(Object o)
|
||||
{
|
||||
if (o == null)
|
||||
return null;
|
||||
if (o instanceof ServletPathMapping mapping)
|
||||
return mapping;
|
||||
Matcher matcher = DESERIALIZE.matcher(o.toString());
|
||||
if (matcher.find())
|
||||
return new ServletPathMapping(
|
||||
MappingMatch.valueOf(matcher.group("mappingMatch")),
|
||||
matcher.group("matchValue"),
|
||||
matcher.group("pattern"),
|
||||
matcher.group("servletName"),
|
||||
matcher.group("servletPath"),
|
||||
matcher.group("pathInfo")
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
@ -48,17 +49,12 @@ import org.eclipse.jetty.logging.StacklessLogging;
|
|||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.ResourceHandler;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
|
@ -91,17 +87,10 @@ public class DispatcherTest
|
|||
_connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
|
||||
_connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
|
||||
|
||||
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
|
||||
_contextHandler = new ServletContextHandler();
|
||||
_contextHandler.setContextPath("/context");
|
||||
_contextHandler.setBaseResourceAsPath(MavenTestingUtils.getTestResourcePathDir("contextResources"));
|
||||
contextCollection.addHandler(_contextHandler);
|
||||
ResourceHandler resourceHandler = new ResourceHandler();
|
||||
resourceHandler.setBaseResource(ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePathDir("dispatchResourceTest")));
|
||||
ContextHandler resourceContextHandler = new ContextHandler("/resource");
|
||||
resourceContextHandler.setHandler(resourceHandler);
|
||||
contextCollection.addHandler(resourceContextHandler);
|
||||
_server.setHandler(contextCollection);
|
||||
_server.setHandler(_contextHandler);
|
||||
_server.addConnector(_connector);
|
||||
|
||||
_server.start();
|
||||
|
@ -194,7 +183,7 @@ public class DispatcherTest
|
|||
|
||||
assertEquals(expected, rawResponse);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testForwardNonUTF8() throws Exception
|
||||
{
|
||||
|
@ -376,7 +365,7 @@ public class DispatcherTest
|
|||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
assertThat(rawResponse, containsString(" 500 "));
|
||||
assertThat(rawResponse, containsString(" 400 "));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,7 +383,7 @@ public class DispatcherTest
|
|||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
|
||||
String expected = """
|
||||
HTTP/1.1 200 OK\r
|
||||
specialSetHeader: specialSetHeader\r
|
||||
|
@ -827,98 +816,6 @@ public class DispatcherTest
|
|||
assertEquals(expected, rawResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkingResourceHandler() throws Exception
|
||||
{
|
||||
String rawResponse = _connector.getResponse("""
|
||||
GET /resource/content.txt HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
// from inside the context.txt file
|
||||
assertThat(response.getContent(), containsString("content goes here")); // from inside the context.txt file
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Cross context dispatch not yet supported in jetty-12")
|
||||
public void testIncludeToResourceHandler() throws Exception
|
||||
{
|
||||
_contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
|
||||
|
||||
String rawResponse = _connector.getResponse("""
|
||||
GET /context/resourceServlet/content.txt?do=include HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
// from inside the context.txt file
|
||||
assertThat(response.getContent(), containsString("content goes here"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Cross context dispatch not yet supported in jetty-12")
|
||||
public void testForwardToResourceHandler() throws Exception
|
||||
{
|
||||
_contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
|
||||
|
||||
String rawResponse = _connector.getResponse("""
|
||||
GET /context/resourceServlet/content.txt?do=forward HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
// from inside the context.txt file
|
||||
assertThat(response.getContent(), containsString("content goes here"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Cross context dispatch not yet supported in jetty-12")
|
||||
public void testWrappedIncludeToResourceHandler() throws Exception
|
||||
{
|
||||
_contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
|
||||
|
||||
String rawResponse = _connector.getResponse("""
|
||||
GET /context/resourceServlet/content.txt?do=include&wrapped=true HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
// from inside the context.txt file
|
||||
assertThat(response.getContent(), containsString("content goes here"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Cross context dispatch not yet supported in jetty-12")
|
||||
public void testWrappedForwardToResourceHandler() throws Exception
|
||||
{
|
||||
_contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
|
||||
|
||||
String rawResponse = _connector.getResponse("""
|
||||
GET /context/resourceServlet/content.txt?do=forward&wrapped=true HTTP/1.1
|
||||
Host: localhost\r
|
||||
Connection: close\r
|
||||
\r
|
||||
""");
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
// from inside the context.txt file
|
||||
assertThat(response.getContent(), containsString("content goes here"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForwardFilterToRogerServlet() throws Exception
|
||||
{
|
||||
|
@ -1145,6 +1042,18 @@ public class DispatcherTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class ParameterReadingFilter implements Filter
|
||||
{
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
//case the params to be parsed on the request
|
||||
Map<String, String[]> params = request.getParameterMap();
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forward filter works with roger, echo and reverse echo servlets to test various
|
||||
* forwarding bits using filters.
|
||||
|
@ -1213,6 +1122,35 @@ public class DispatcherTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class CrossContextDispatchServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
doGet(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
RequestDispatcher dispatcher;
|
||||
|
||||
if (request.getParameter("forward") != null)
|
||||
{
|
||||
ServletContext foreign = getServletContext().getContext("/foreign");
|
||||
assertNotNull(foreign);
|
||||
dispatcher = foreign.getRequestDispatcher(request.getParameter("forward"));
|
||||
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.forward(new HttpServletRequestWrapper(request), new HttpServletResponseWrapper(response));
|
||||
}
|
||||
else
|
||||
response.sendError(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class IncludeServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
// The logic linked to this field be deleted and the writer always used once #10155 is fixed.
|
||||
|
@ -1319,6 +1257,46 @@ public class DispatcherTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class ParameterReadingServlet extends GenericServlet
|
||||
{
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
|
||||
{
|
||||
Map<String, String[]> params = req.getParameterMap();
|
||||
|
||||
for (String key : params.keySet())
|
||||
{
|
||||
res.getWriter().print(key + "=");
|
||||
String[] val = params.get(key);
|
||||
if (val == null)
|
||||
res.getWriter().println();
|
||||
else if (val.length == 1)
|
||||
res.getWriter().println(val[0]);
|
||||
else
|
||||
{
|
||||
res.getWriter().println(Arrays.asList(val));
|
||||
}
|
||||
}
|
||||
|
||||
/* System.err.println(req.getAttribute("jakarta.servlet.forward.mapping"));
|
||||
System.err.println(req.getAttribute("jakarta.servlet.forward.request_uri"));
|
||||
System.err.println(req.getAttribute("jakarta.servlet.forward.context_path"));
|
||||
System.err.println(req.getAttribute("jakarta.servlet.forward.servlet_path"));
|
||||
System.err.println(req.getAttribute("jakarta.servlet.forward.path_info"));
|
||||
System.err.println(req.getAttribute("jakarta.servlet.forward.query_string"));*/
|
||||
}
|
||||
}
|
||||
|
||||
public static class VerifyForwardServlet extends GenericServlet
|
||||
{
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
|
||||
{
|
||||
if (DispatcherType.FORWARD.equals(req.getDispatcherType()))
|
||||
res.getWriter().print("Verified!");
|
||||
}
|
||||
}
|
||||
|
||||
public static class RogerThatServlet extends GenericServlet
|
||||
{
|
||||
@Override
|
||||
|
@ -1447,7 +1425,7 @@ public class DispatcherTest
|
|||
response.getOutputStream().println(request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class IncludeEchoURIServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
@ -1468,7 +1446,7 @@ public class DispatcherTest
|
|||
response.getOutputStream().println((String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ForwardEchoURIServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
|
|
@ -18,8 +18,10 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
@ -29,6 +31,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
public class ServletTest
|
||||
|
@ -193,4 +196,41 @@ public class ServletTest
|
|||
assertFalse(endPoint.isOpen());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpServletMapping() throws Exception
|
||||
{
|
||||
_context.addServlet(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
HttpServletMapping mapping = req.getHttpServletMapping();
|
||||
assertThat(mapping.getMappingMatch(), is(MappingMatch.EXACT));
|
||||
assertThat(mapping.getMatchValue(), is("get"));
|
||||
assertThat(mapping.getPattern(), is("/get"));
|
||||
|
||||
mapping = ServletPathMapping.from(mapping);
|
||||
assertThat(mapping.getMappingMatch(), is(MappingMatch.EXACT));
|
||||
assertThat(mapping.getMatchValue(), is("get"));
|
||||
assertThat(mapping.getPattern(), is("/get"));
|
||||
|
||||
mapping = ServletPathMapping.from(mapping.toString());
|
||||
assertThat(mapping.getMappingMatch(), is(MappingMatch.EXACT));
|
||||
assertThat(mapping.getMatchValue(), is("get"));
|
||||
assertThat(mapping.getPattern(), is("/get"));
|
||||
|
||||
resp.getWriter().println("Hello!");
|
||||
}
|
||||
}, "/get");
|
||||
|
||||
_server.start();
|
||||
|
||||
String response = _connector.getResponse("""
|
||||
GET /ctx/get HTTP/1.0
|
||||
|
||||
""");
|
||||
assertThat(response, containsString(" 200 OK"));
|
||||
assertThat(response, containsString("Hello!"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ import org.eclipse.jetty.util.security.Password;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
|
@ -1051,7 +1050,6 @@ public class ConstraintTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Dispatch unsupported")
|
||||
public void testFormDispatch() throws Exception
|
||||
{
|
||||
_security.setAuthenticator(new FormAuthenticator("/testLoginPage", "/testErrorPage", true));
|
||||
|
@ -1680,7 +1678,6 @@ public class ConstraintTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Dispatch unsupported")
|
||||
public void testStrictFormDispatch()
|
||||
throws Exception
|
||||
{
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.eclipse.jetty.session.SessionDataStoreFactory;
|
|||
import org.eclipse.jetty.session.test.TestSessionDataStoreFactory;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -303,8 +302,6 @@ public class CreationTest
|
|||
* session in it too. Check that both sessions exist after the response
|
||||
* completes.
|
||||
*/
|
||||
//TODO - no cross context support in jetty-12
|
||||
@Disabled
|
||||
@Test
|
||||
public void testSessionCreateForward() throws Exception
|
||||
{
|
||||
|
@ -322,8 +319,10 @@ public class CreationTest
|
|||
TestServlet servlet = new TestServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||
contextHandler.setCrossContextDispatchSupported(true);
|
||||
contextHandler.addServlet(holder, servletMapping);
|
||||
ServletContextHandler ctxB = server1.addContext(contextB);
|
||||
ctxB.setCrossContextDispatchSupported(true);
|
||||
ctxB.addServlet(TestServletB.class, servletMapping);
|
||||
server1.start();
|
||||
int port1 = server1.getPort();
|
||||
|
@ -353,8 +352,6 @@ public class CreationTest
|
|||
* in it, then invalidate the session in the original context: that should invalidate the
|
||||
* session in both contexts and no session should exist after the response completes.
|
||||
*/
|
||||
//TODO no cross context dispatch in jetty-12
|
||||
@Disabled
|
||||
@Test
|
||||
public void testSessionCreateForwardAndInvalidate() throws Exception
|
||||
{
|
||||
|
@ -373,8 +370,10 @@ public class CreationTest
|
|||
TestServlet servlet = new TestServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||
contextHandler.setCrossContextDispatchSupported(true);
|
||||
contextHandler.addServlet(holder, servletMapping);
|
||||
ServletContextHandler ctxB = server1.addContext(contextB);
|
||||
ctxB.setCrossContextDispatchSupported(true);
|
||||
ctxB.addServlet(TestServletB.class, servletMapping);
|
||||
server1.start();
|
||||
int port1 = server1.getPort();
|
||||
|
|
|
@ -1349,15 +1349,15 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
public class ServletApiContext extends ServletContextHandler.ServletContextApi
|
||||
{
|
||||
@Override
|
||||
public jakarta.servlet.ServletContext getContext(String uripath)
|
||||
public jakarta.servlet.ServletContext getContext(String path)
|
||||
{
|
||||
jakarta.servlet.ServletContext servletContext = super.getContext(uripath);
|
||||
jakarta.servlet.ServletContext servletContext = super.getContext(path);
|
||||
|
||||
if (servletContext != null && _contextWhiteList != null)
|
||||
{
|
||||
for (String context : _contextWhiteList)
|
||||
{
|
||||
if (context.equals(uripath))
|
||||
if (context.equals(path))
|
||||
{
|
||||
return servletContext;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.parallel.Isolated;
|
||||
|
@ -251,20 +250,20 @@ public class WebAppContextTest
|
|||
*
|
||||
* @throws Exception on test failure
|
||||
*/
|
||||
@Disabled // Reenabled when cross context dispatch is implemented.
|
||||
@Test
|
||||
public void testContextWhiteList() throws Exception
|
||||
{
|
||||
Server server = newServer();
|
||||
Handler.Sequence handlers = new Handler.Sequence();
|
||||
WebAppContext contextA = new WebAppContext(".", "/A");
|
||||
|
||||
contextA.addServlet(ServletA.class, "/s");
|
||||
contextA.setCrossContextDispatchSupported(true);
|
||||
handlers.addHandler(contextA);
|
||||
WebAppContext contextB = new WebAppContext(".", "/B");
|
||||
|
||||
WebAppContext contextB = new WebAppContext(".", "/B");
|
||||
contextB.addServlet(ServletB.class, "/s");
|
||||
contextB.setContextWhiteList("/doesnotexist", "/B/s");
|
||||
contextB.setCrossContextDispatchSupported(true);
|
||||
handlers.addHandler(contextB);
|
||||
|
||||
server.setHandler(handlers);
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<javax.servlet.jsp.jstl.impl.version>1.2.5</javax.servlet.jsp.jstl.impl.version>
|
||||
<jetty.servlet.api.version>4.0.6</jetty.servlet.api.version>
|
||||
<jsp.impl.version>9.0.83.1</jsp.impl.version>
|
||||
<modify-sources-plugin.version>1.0.9</modify-sources-plugin.version>
|
||||
<modify-sources-plugin.version>1.0.10</modify-sources-plugin.version>
|
||||
<sonar.skip>true</sonar.skip>
|
||||
<weld.version>3.1.9.Final</weld.version>
|
||||
</properties>
|
||||
|
|
|
@ -83,8 +83,8 @@ import org.eclipse.jetty.session.SessionManager;
|
|||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
@ -324,6 +324,26 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
_coreContextHandler.setAllowNullPathInContext(allowNullPathInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross context dispatch support.
|
||||
* @param supported {@code True} if cross context dispatch is supported
|
||||
* @see org.eclipse.jetty.server.handler.ContextHandler#setCrossContextDispatchSupported(boolean)
|
||||
*/
|
||||
public void setCrossContextDispatchSupported(boolean supported)
|
||||
{
|
||||
getCoreContextHandler().setCrossContextDispatchSupported(supported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross context dispatch support.
|
||||
* @return {@code True} if cross context dispatch is supported
|
||||
* @see org.eclipse.jetty.server.handler.ContextHandler#isCrossContextDispatchSupported()
|
||||
*/
|
||||
public boolean isCrossContextDispatchSupported()
|
||||
{
|
||||
return getCoreContextHandler().isCrossContextDispatchSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServer(Server server)
|
||||
{
|
||||
|
@ -1705,7 +1725,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
// this is a dispatch with either a provided URI and/or a dispatched path
|
||||
// We will have to modify the request and then revert
|
||||
final HttpURI oldUri = baseRequest.getHttpURI();
|
||||
final MultiMap<String> oldQueryParams = baseRequest.getQueryParameters();
|
||||
final Fields oldQueryParams = baseRequest.getQueryFields();
|
||||
try
|
||||
{
|
||||
if (encodedPathQuery == null)
|
||||
|
@ -1747,7 +1767,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
finally
|
||||
{
|
||||
baseRequest.setHttpURI(oldUri);
|
||||
baseRequest.setQueryParameters(oldQueryParams);
|
||||
baseRequest.setQueryFields(oldQueryParams);
|
||||
baseRequest.resetParameters();
|
||||
}
|
||||
}
|
||||
|
@ -1836,10 +1856,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getContext(String uripath)
|
||||
public ServletContext getContext(String path)
|
||||
{
|
||||
// TODO No cross context dispatch
|
||||
return null;
|
||||
org.eclipse.jetty.server.handler.ContextHandler context = getContextHandler().getCoreContextHandler().getCrossContextHandler(path);
|
||||
|
||||
if (context == null)
|
||||
return null;
|
||||
if (context == _coreContextHandler)
|
||||
return this;
|
||||
return new CrossContextServletContext(_coreContextHandler, context.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2694,10 +2719,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
|
|||
private class CoreToNestedHandler extends Abstract
|
||||
{
|
||||
@Override
|
||||
public boolean handle(org.eclipse.jetty.server.Request coreRequest, Response response, Callback callback) throws Exception
|
||||
public boolean handle(org.eclipse.jetty.server.Request coreRequest, Response response, Callback callback)
|
||||
{
|
||||
HttpChannel httpChannel = org.eclipse.jetty.server.Request.get(coreRequest, CoreContextRequest.class, CoreContextRequest::getHttpChannel);
|
||||
httpChannel.onProcess(response, callback);
|
||||
Objects.requireNonNull(httpChannel).onProcess(response, callback);
|
||||
httpChannel.handle();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.FormFields;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
class CrossContextDispatcher implements RequestDispatcher
|
||||
{
|
||||
public static final String ORIGINAL_URI = "org.eclipse.jetty.dispatch.originalURI";
|
||||
public static final String ORIGINAL_QUERY_STRING = "org.eclipse.jetty.dispatch.originalQueryString";
|
||||
public static final String ORIGINAL_SERVLET_MAPPING = "org.eclipse.jetty.dispatch.originalServletMapping";
|
||||
public static final String ORIGINAL_CONTEXT_PATH = "org.eclipse.jetty.dispatch.originalContextPath";
|
||||
|
||||
private static final String FOREIGN_SERVLET_PACKAGE = "javax.servlet."; //EE8EE9-TRANSLATE
|
||||
|
||||
public static final Set<String> ATTRIBUTES = Set.of(
|
||||
RequestDispatcher.FORWARD_REQUEST_URI,
|
||||
RequestDispatcher.FORWARD_MAPPING,
|
||||
RequestDispatcher.FORWARD_CONTEXT_PATH,
|
||||
RequestDispatcher.FORWARD_SERVLET_PATH,
|
||||
RequestDispatcher.FORWARD_QUERY_STRING,
|
||||
RequestDispatcher.FORWARD_PATH_INFO,
|
||||
RequestDispatcher.INCLUDE_REQUEST_URI,
|
||||
RequestDispatcher.INCLUDE_MAPPING,
|
||||
RequestDispatcher.INCLUDE_CONTEXT_PATH,
|
||||
RequestDispatcher.INCLUDE_SERVLET_PATH,
|
||||
RequestDispatcher.INCLUDE_QUERY_STRING,
|
||||
RequestDispatcher.INCLUDE_PATH_INFO,
|
||||
// TODO MULTIPART_CONFIG_ELEMENT,
|
||||
org.eclipse.jetty.server.handler.ContextHandler.CROSS_CONTEXT_ATTRIBUTE,
|
||||
ORIGINAL_URI,
|
||||
ORIGINAL_QUERY_STRING,
|
||||
ORIGINAL_SERVLET_MAPPING,
|
||||
ORIGINAL_CONTEXT_PATH,
|
||||
FormFields.class.getName()
|
||||
);
|
||||
|
||||
private final CrossContextServletContext _targetContext;
|
||||
private final HttpURI _uri;
|
||||
private final String _decodedPathInContext;
|
||||
|
||||
private class IncludeRequest extends ServletCoreRequest
|
||||
{
|
||||
private Request _baseRequest;
|
||||
|
||||
public IncludeRequest(ContextHandler.CoreContextRequest coreContextRequest, Request request, HttpServletRequest httpServletRequest)
|
||||
{
|
||||
super(coreContextRequest, httpServletRequest, new Attributes.Synthetic(new ServletAttributes(httpServletRequest))
|
||||
{
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith(FOREIGN_SERVLET_PACKAGE))
|
||||
name = "jakarta.servlet." + name.substring(FOREIGN_SERVLET_PACKAGE.length());
|
||||
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getSyntheticAttribute(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//Special include attributes refer to the target context and path
|
||||
return switch (name)
|
||||
{
|
||||
case RequestDispatcher.INCLUDE_MAPPING -> null;
|
||||
case RequestDispatcher.INCLUDE_SERVLET_PATH -> null;
|
||||
case RequestDispatcher.INCLUDE_PATH_INFO -> _decodedPathInContext;
|
||||
case RequestDispatcher.INCLUDE_REQUEST_URI -> (_uri == null) ? null : _uri.getPath();
|
||||
case RequestDispatcher.INCLUDE_CONTEXT_PATH -> _targetContext.getContextPath();
|
||||
case RequestDispatcher.INCLUDE_QUERY_STRING -> (_uri == null) ? null : _uri.getQuery();
|
||||
case org.eclipse.jetty.server.handler.ContextHandler.CROSS_CONTEXT_ATTRIBUTE -> DispatcherType.INCLUDE.toString();
|
||||
|
||||
case ORIGINAL_URI -> httpServletRequest.getRequestURI();
|
||||
case ORIGINAL_QUERY_STRING -> httpServletRequest.getQueryString();
|
||||
case ORIGINAL_SERVLET_MAPPING -> httpServletRequest.getHttpServletMapping();
|
||||
case ORIGINAL_CONTEXT_PATH -> httpServletRequest.getContextPath();
|
||||
|
||||
default -> httpServletRequest.getAttribute(name);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSyntheticNameSet()
|
||||
{
|
||||
return ATTRIBUTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith(FOREIGN_SERVLET_PACKAGE))
|
||||
name = "jakarta.servlet." + name.substring(FOREIGN_SERVLET_PACKAGE.length());
|
||||
|
||||
return super.setAttribute(name, attribute);
|
||||
}
|
||||
});
|
||||
_baseRequest = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
//return the uri of the dispatch target
|
||||
return _uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (MultiPart.Parser.class.getName().equals(name))
|
||||
return _baseRequest.getAttribute(name);
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
private class IncludeResponse extends ServletCoreResponse
|
||||
{
|
||||
public IncludeResponse(ServletCoreRequest servletCoreRequest, HttpServletResponse httpServletResponse, Response baseResponse, org.eclipse.jetty.server.Response coreResponse)
|
||||
{
|
||||
super(servletCoreRequest, httpServletResponse, baseResponse, coreResponse, true);
|
||||
}
|
||||
}
|
||||
|
||||
private class ForwardRequest extends ServletCoreRequest
|
||||
{
|
||||
private Request _baseRequest;
|
||||
|
||||
public ForwardRequest(ContextHandler.CoreContextRequest coreContextRequest, Request request, HttpServletRequest httpServletRequest)
|
||||
{
|
||||
super(coreContextRequest, httpServletRequest, new Attributes.Synthetic(new ServletAttributes(httpServletRequest))
|
||||
{
|
||||
@Override
|
||||
protected Object getSyntheticAttribute(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
//handle cross-environment dispatch from ee8
|
||||
if (name.startsWith(FOREIGN_SERVLET_PACKAGE))
|
||||
name = "jakarta.servlet." + name.substring(FOREIGN_SERVLET_PACKAGE.length());
|
||||
|
||||
return switch (name)
|
||||
{
|
||||
case RequestDispatcher.FORWARD_REQUEST_URI -> httpServletRequest.getRequestURI();
|
||||
case RequestDispatcher.FORWARD_SERVLET_PATH -> httpServletRequest.getServletPath();
|
||||
case RequestDispatcher.FORWARD_PATH_INFO -> httpServletRequest.getPathInfo();
|
||||
case RequestDispatcher.FORWARD_CONTEXT_PATH -> httpServletRequest.getContextPath();
|
||||
case RequestDispatcher.FORWARD_MAPPING -> httpServletRequest.getHttpServletMapping();
|
||||
case RequestDispatcher.FORWARD_QUERY_STRING -> httpServletRequest.getQueryString();
|
||||
case RequestDispatcher.INCLUDE_MAPPING -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_REQUEST_URI -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_CONTEXT_PATH -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_QUERY_STRING -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_SERVLET_PATH -> REMOVED;
|
||||
case RequestDispatcher.INCLUDE_PATH_INFO -> REMOVED;
|
||||
// TODO case ServletContextRequest.MULTIPART_CONFIG_ELEMENT -> httpServletRequest.getAttribute(ServletMultiPartFormData.class.getName());
|
||||
case org.eclipse.jetty.server.handler.ContextHandler.CROSS_CONTEXT_ATTRIBUTE -> DispatcherType.FORWARD.toString();
|
||||
default ->
|
||||
{
|
||||
if (FormFields.class.getName().equals(name))
|
||||
{
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(httpServletRequest));
|
||||
yield baseRequest.peekParameters();
|
||||
}
|
||||
yield null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSyntheticNameSet()
|
||||
{
|
||||
return ATTRIBUTES;
|
||||
}
|
||||
});
|
||||
_baseRequest = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
if (MultiPart.Parser.class.getName().equals(name))
|
||||
return _baseRequest.getAttribute(name);
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
|
||||
CrossContextDispatcher(CrossContextServletContext targetContext, HttpURI uri, String decodedPathInContext)
|
||||
{
|
||||
_targetContext = targetContext;
|
||||
_uri = uri;
|
||||
_decodedPathInContext = decodedPathInContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(ServletRequest servletRequest, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
HttpServletRequest httpServletRequest = (servletRequest instanceof HttpServletRequest) ? ((HttpServletRequest)servletRequest) : new ServletRequestHttpWrapper(servletRequest);
|
||||
HttpServletResponse httpServletResponse = (response instanceof HttpServletResponse) ? (HttpServletResponse)response : new ServletResponseHttpWrapper(response);
|
||||
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(servletRequest));
|
||||
Response baseResponse = baseRequest.getResponse();
|
||||
|
||||
ContextHandler.CoreContextRequest coreContextRequest = baseRequest.getCoreRequest();
|
||||
org.eclipse.jetty.server.Response coreResponse = coreContextRequest.getHttpChannel().getCoreResponse();
|
||||
baseResponse.resetForForward();
|
||||
|
||||
ForwardRequest forwardRequest = new ForwardRequest(coreContextRequest, baseRequest, httpServletRequest);
|
||||
ServletCoreResponse servletCoreResponse = new ServletCoreResponse(forwardRequest, httpServletResponse, baseResponse, coreResponse, false);
|
||||
|
||||
try (Blocker.Callback callback = Blocker.callback())
|
||||
{
|
||||
_targetContext.getTargetContext().getContextHandler().handle(forwardRequest, servletCoreResponse, callback);
|
||||
callback.block();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// If we are not async and not closed already, then close via the possibly wrapped response.
|
||||
if (!baseRequest.isAsyncStarted() && !servletCoreResponse.hasLastWrite())
|
||||
{
|
||||
Closeable closeable;
|
||||
try
|
||||
{
|
||||
closeable = response.getOutputStream();
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
closeable = response.getWriter();
|
||||
}
|
||||
IO.close(closeable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void include(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException
|
||||
{
|
||||
HttpServletRequest httpServletRequest = (servletRequest instanceof HttpServletRequest) ? ((HttpServletRequest)servletRequest) : new ServletRequestHttpWrapper(servletRequest);
|
||||
HttpServletResponse httpServletResponse = (servletResponse instanceof HttpServletResponse) ? (HttpServletResponse)servletResponse : new ServletResponseHttpWrapper(servletResponse);
|
||||
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(servletRequest));
|
||||
Response baseResponse = baseRequest.getResponse();
|
||||
|
||||
ContextHandler.CoreContextRequest coreContextRequest = baseRequest.getCoreRequest();
|
||||
org.eclipse.jetty.server.Response coreResponse = coreContextRequest.getHttpChannel().getCoreResponse();
|
||||
|
||||
IncludeRequest includeRequest = new IncludeRequest(coreContextRequest, baseRequest, httpServletRequest);
|
||||
IncludeResponse includeResponse = new IncludeResponse(includeRequest, httpServletResponse, baseResponse, coreResponse);
|
||||
|
||||
try (Blocker.Callback callback = Blocker.callback())
|
||||
{
|
||||
_targetContext.getTargetContext().getContextHandler().handle(includeRequest, includeResponse, callback);
|
||||
callback.block();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,454 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.EventListener;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterRegistration;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.SessionCookieConfig;
|
||||
import jakarta.servlet.SessionTrackingMode;
|
||||
import jakarta.servlet.descriptor.JspConfigDescriptor;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link ServletContext} used for cross context dispatch, which wraps an arbitrary {@link ContextHandler}
|
||||
*/
|
||||
class CrossContextServletContext implements ServletContext
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CrossContextServletContext.class);
|
||||
|
||||
private final ContextHandler _servletContextHandler;
|
||||
private final ContextHandler.ScopedContext _targetContext;
|
||||
|
||||
protected CrossContextServletContext(ContextHandler servletContextHandler, ContextHandler.ScopedContext targetContext)
|
||||
{
|
||||
_servletContextHandler = servletContextHandler;
|
||||
_targetContext = Objects.requireNonNull(targetContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextPath()
|
||||
{
|
||||
return _targetContext.getContextPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getContext(String uripath)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Servlet getServlet(String name) throws ServletException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Enumeration<Servlet> getServlets()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Enumeration<String> getServletNames()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void log(Exception exception, String msg)
|
||||
{
|
||||
log(msg, exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMajorVersion()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinorVersion()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEffectiveMajorVersion()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEffectiveMinorVersion()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType(String file)
|
||||
{
|
||||
return _targetContext.getMimeTypes().getMimeByExtension(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getResourcePaths(String path)
|
||||
{
|
||||
Resource resource = _targetContext.getBaseResource().resolve(path);
|
||||
if (resource != null && resource.isDirectory())
|
||||
return resource.list().stream().map(Resource::getPath).map(Path::getFileName).map(Path::toString).collect(Collectors.toSet());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String path) throws MalformedURLException
|
||||
{
|
||||
return _targetContext.getBaseResource().resolve(path).getURI().toURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(String path)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String uriInContext)
|
||||
{
|
||||
// uriInContext is encoded, potentially with query.
|
||||
if (uriInContext == null)
|
||||
return null;
|
||||
|
||||
if (!uriInContext.startsWith("/"))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
String contextPath = getContextPath();
|
||||
// uriInContext is canonical by HttpURI.
|
||||
HttpURI.Mutable uri = HttpURI.build(uriInContext);
|
||||
String encodedPathInContext = uri.getCanonicalPath();
|
||||
if (StringUtil.isEmpty(encodedPathInContext))
|
||||
return null;
|
||||
|
||||
if (!StringUtil.isEmpty(contextPath))
|
||||
{
|
||||
uri.path(URIUtil.addPaths(contextPath, uri.getPath()));
|
||||
encodedPathInContext = uri.getCanonicalPath().substring(contextPath.length());
|
||||
}
|
||||
return new CrossContextDispatcher(this, uri, URIUtil.decodePath(encodedPathInContext));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.trace("IGNORED", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getNamedDispatcher(String name)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String msg)
|
||||
{
|
||||
LOG.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message, Throwable throwable)
|
||||
{
|
||||
LOG.warn(message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealPath(String path)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerInfo()
|
||||
{
|
||||
return _servletContextHandler.getServer().getServerInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitParameter(String name)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getInitParameterNames()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setInitParameter(String name, String value)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _targetContext.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames()
|
||||
{
|
||||
return Collections.enumeration(_targetContext.getAttributeNameSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object object)
|
||||
{
|
||||
_targetContext.setAttribute(name, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name)
|
||||
{
|
||||
_targetContext.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletContextName()
|
||||
{
|
||||
return _targetContext.getContextHandler().getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration getServletRegistration(String servletName)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends ServletRegistration> getServletRegistrations()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterRegistration getFilterRegistration(String filterName)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends FilterRegistration> getFilterRegistrations()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionCookieConfig getSessionCookieConfig()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(String className)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends EventListener> void addListener(T t)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Class<? extends EventListener> listenerClass)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JspConfigDescriptor getJspConfigDescriptor()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader()
|
||||
{
|
||||
return _targetContext.getClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareRoles(String... roleNames)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVirtualServerName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionTimeout()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTimeout(int sessionTimeout)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestCharacterEncoding()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestCharacterEncoding(String encoding)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseCharacterEncoding()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResponseCharacterEncoding(String encoding)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
ContextHandler.ScopedContext getTargetContext()
|
||||
{
|
||||
return _targetContext;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.http.HttpException;
|
|||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -87,7 +87,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
final DispatcherType old_type = baseRequest.getDispatcherType();
|
||||
final Attributes old_attr = baseRequest.getAttributes();
|
||||
final MultiMap<String> old_query_params = baseRequest.getQueryParameters();
|
||||
final Fields old_query_fields = baseRequest.getQueryFields();
|
||||
final ContextHandler.APIContext old_context = baseRequest.getContext();
|
||||
final ServletPathMapping old_mapping = baseRequest.getServletPathMapping();
|
||||
try
|
||||
|
@ -123,7 +123,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
{
|
||||
baseRequest.setAttributes(old_attr);
|
||||
baseRequest.getResponse().included();
|
||||
baseRequest.setQueryParameters(old_query_params);
|
||||
baseRequest.setQueryFields(old_query_fields);
|
||||
baseRequest.resetParameters();
|
||||
baseRequest.setDispatcherType(old_type);
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
final String old_path_in_context = baseRequest.getPathInContext();
|
||||
final ServletPathMapping old_mapping = baseRequest.getServletPathMapping();
|
||||
final ServletPathMapping source_mapping = baseRequest.findServletPathMapping();
|
||||
final MultiMap<String> old_query_params = baseRequest.getQueryParameters();
|
||||
final Fields old_query_params = baseRequest.getQueryFields();
|
||||
final Attributes old_attr = baseRequest.getAttributes();
|
||||
final DispatcherType old_type = baseRequest.getDispatcherType();
|
||||
|
||||
|
@ -233,7 +233,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
baseRequest.setHttpURI(old_uri);
|
||||
baseRequest.setContext(old_context, old_path_in_context);
|
||||
baseRequest.setServletPathMapping(old_mapping);
|
||||
baseRequest.setQueryParameters(old_query_params);
|
||||
baseRequest.setQueryFields(old_query_params);
|
||||
baseRequest.resetParameters();
|
||||
baseRequest.setAttributes(old_attr);
|
||||
baseRequest.setDispatcherType(old_type);
|
||||
|
|
|
@ -26,9 +26,7 @@ import java.net.SocketAddress;
|
|||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
@ -65,7 +63,6 @@ import jakarta.servlet.http.Part;
|
|||
import jakarta.servlet.http.PushBuilder;
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.ComplianceViolation;
|
||||
import org.eclipse.jetty.http.CookieCache;
|
||||
import org.eclipse.jetty.http.CookieCompliance;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -83,6 +80,8 @@ import org.eclipse.jetty.http.SetCookieParser;
|
|||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.security.UserIdentity;
|
||||
import org.eclipse.jetty.server.CookieCache;
|
||||
import org.eclipse.jetty.server.FormFields;
|
||||
import org.eclipse.jetty.server.HttpCookieUtils;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
|
@ -90,6 +89,7 @@ import org.eclipse.jetty.session.AbstractSessionManager;
|
|||
import org.eclipse.jetty.session.ManagedSession;
|
||||
import org.eclipse.jetty.session.SessionManager;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
|
@ -126,8 +126,8 @@ public class Request implements HttpServletRequest
|
|||
private static final int INPUT_NONE = 0;
|
||||
private static final int INPUT_STREAM = 1;
|
||||
private static final int INPUT_READER = 2;
|
||||
private static final MultiMap<String> NO_PARAMS = new MultiMap<>();
|
||||
private static final MultiMap<String> BAD_PARAMS = new MultiMap<>();
|
||||
private static final Fields NO_PARAMS = Fields.EMPTY;
|
||||
private static final Fields BAD_PARAMS = new Fields(true);
|
||||
|
||||
/**
|
||||
* Compare inputParameters to NO_PARAMS by Reference
|
||||
|
@ -135,7 +135,7 @@ public class Request implements HttpServletRequest
|
|||
* @param inputParameters The parameters to compare to NO_PARAMS
|
||||
* @return True if the inputParameters reference is equal to NO_PARAMS otherwise False
|
||||
*/
|
||||
private static boolean isNoParams(MultiMap<String> inputParameters)
|
||||
private static boolean isNoParams(Fields inputParameters)
|
||||
{
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
boolean isNoParams = (inputParameters == NO_PARAMS);
|
||||
|
@ -173,6 +173,7 @@ public class Request implements HttpServletRequest
|
|||
private ContextHandler.APIContext _context;
|
||||
private final List<ServletRequestAttributeListener> _requestAttributeListeners = new ArrayList<>();
|
||||
private final HttpInput _input;
|
||||
private final boolean _crossContextDispatchSupported;
|
||||
private ContextHandler.CoreContextRequest _coreRequest;
|
||||
private MetaData.Request _metaData;
|
||||
private HttpFields _httpFields;
|
||||
|
@ -184,7 +185,6 @@ public class Request implements HttpServletRequest
|
|||
private Object _asyncNotSupportedSource = null;
|
||||
private boolean _secure;
|
||||
private boolean _handled = false;
|
||||
private boolean _contentParamsExtracted;
|
||||
private Attributes _attributes;
|
||||
private Authentication _authentication;
|
||||
private String _contentType;
|
||||
|
@ -193,9 +193,9 @@ public class Request implements HttpServletRequest
|
|||
private int _inputState = INPUT_NONE;
|
||||
private BufferedReader _reader;
|
||||
private String _readerEncoding;
|
||||
private MultiMap<String> _queryParameters;
|
||||
private MultiMap<String> _contentParameters;
|
||||
private MultiMap<String> _parameters;
|
||||
private Fields _queryParameters;
|
||||
private Fields _contentParameters;
|
||||
private Fields _parameters;
|
||||
private Charset _queryEncoding;
|
||||
private UserIdentityScope _scope;
|
||||
private long _timeStamp;
|
||||
|
@ -208,6 +208,7 @@ public class Request implements HttpServletRequest
|
|||
{
|
||||
_channel = channel;
|
||||
_input = input;
|
||||
_crossContextDispatchSupported = _channel.getContextHandler().getCoreContextHandler().isCrossContextDispatchSupported();
|
||||
}
|
||||
|
||||
public HttpFields getHttpFields()
|
||||
|
@ -353,14 +354,18 @@ public class Request implements HttpServletRequest
|
|||
throw new IllegalArgumentException(listener.getClass().toString());
|
||||
}
|
||||
|
||||
private MultiMap<String> getParameters()
|
||||
Fields peekParameters()
|
||||
{
|
||||
if (!_contentParamsExtracted)
|
||||
{
|
||||
// content parameters need boolean protection as they can only be read
|
||||
// once, but may be reset to null by a reset
|
||||
_contentParamsExtracted = true;
|
||||
return _parameters;
|
||||
}
|
||||
|
||||
private Fields getParameters()
|
||||
{
|
||||
// protect against calls to recycled requests (which is illegal, but
|
||||
// this gives better failures
|
||||
Fields parameters = _parameters;
|
||||
if (parameters == null)
|
||||
{
|
||||
// Extract content parameters; these cannot be replaced by a forward()
|
||||
// once extracted and may have already been extracted by getParts() or
|
||||
// by a processing happening after a form-based authentication.
|
||||
|
@ -376,41 +381,47 @@ public class Request implements HttpServletRequest
|
|||
throw new BadMessageException("Unable to parse form content", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract query string parameters; these may be replaced by a forward()
|
||||
// and may have already been extracted by mergeQueryParameters().
|
||||
if (_queryParameters == null)
|
||||
extractQueryParameters();
|
||||
|
||||
// Do parameters need to be combined?
|
||||
if (isNoParams(_queryParameters) || _queryParameters.getSize() == 0)
|
||||
_parameters = _contentParameters;
|
||||
else if (isNoParams(_contentParameters) || _contentParameters.getSize() == 0)
|
||||
_parameters = _queryParameters;
|
||||
else if (_parameters == null)
|
||||
{
|
||||
_parameters = new Fields(true);
|
||||
_parameters.addAll(_queryParameters);
|
||||
_parameters.addAll(_contentParameters);
|
||||
}
|
||||
parameters = _parameters;
|
||||
}
|
||||
|
||||
// Extract query string parameters; these may be replaced by a forward()
|
||||
// and may have already been extracted by mergeQueryParameters().
|
||||
if (_queryParameters == null)
|
||||
extractQueryParameters();
|
||||
|
||||
// Do parameters need to be combined?
|
||||
if (isNoParams(_queryParameters) || _queryParameters.size() == 0)
|
||||
_parameters = _contentParameters;
|
||||
else if (isNoParams(_contentParameters) || _contentParameters.size() == 0)
|
||||
_parameters = _queryParameters;
|
||||
else if (_parameters == null)
|
||||
{
|
||||
_parameters = new MultiMap<>();
|
||||
_parameters.addAllValues(_queryParameters);
|
||||
_parameters.addAllValues(_contentParameters);
|
||||
}
|
||||
|
||||
// protect against calls to recycled requests (which is illegal, but
|
||||
// this gives better failures
|
||||
MultiMap<String> parameters = _parameters;
|
||||
return parameters == null ? NO_PARAMS : parameters;
|
||||
}
|
||||
|
||||
private void extractQueryParameters()
|
||||
{
|
||||
if (_uri == null || StringUtil.isEmpty(_uri.getQuery()))
|
||||
if (_uri == null)
|
||||
{
|
||||
_queryParameters = NO_PARAMS;
|
||||
return;
|
||||
}
|
||||
|
||||
String query = _uri.getQuery();
|
||||
if (StringUtil.isEmpty(query))
|
||||
_queryParameters = NO_PARAMS;
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_queryParameters = new MultiMap<>();
|
||||
UrlEncoded.decodeTo(_uri.getQuery(), _queryParameters, _queryEncoding);
|
||||
_queryParameters = new Fields(true);
|
||||
UrlEncoded.decodeTo(query, _queryParameters::add, _queryEncoding);
|
||||
}
|
||||
catch (IllegalStateException | IllegalArgumentException e)
|
||||
{
|
||||
|
@ -418,6 +429,25 @@ public class Request implements HttpServletRequest
|
|||
throw new BadMessageException("Unable to parse URI query", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (_crossContextDispatchSupported)
|
||||
{
|
||||
String dispatcherType = _coreRequest.getContext().getCrossContextDispatchType(_coreRequest);
|
||||
if (dispatcherType != null)
|
||||
{
|
||||
String sourceQuery = (String)_coreRequest.getAttribute(
|
||||
DispatcherType.valueOf(dispatcherType) == DispatcherType.FORWARD
|
||||
? RequestDispatcher.FORWARD_QUERY_STRING : CrossContextDispatcher.ORIGINAL_QUERY_STRING);
|
||||
|
||||
if (!StringUtil.isBlank(sourceQuery))
|
||||
{
|
||||
if (_queryParameters == NO_PARAMS)
|
||||
_queryParameters = new Fields(true);
|
||||
|
||||
UrlEncoded.decodeTo(sourceQuery, _queryParameters::add, _queryEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isContentEncodingSupported()
|
||||
|
@ -436,7 +466,24 @@ public class Request implements HttpServletRequest
|
|||
_contentParameters = NO_PARAMS;
|
||||
else
|
||||
{
|
||||
_contentParameters = new MultiMap<>();
|
||||
if (_crossContextDispatchSupported && _coreRequest.getContext().isCrossContextDispatch(_coreRequest))
|
||||
{
|
||||
try
|
||||
{
|
||||
_contentParameters = FormFields.get(_coreRequest).get();
|
||||
return;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
_contentParameters = new Fields(true);
|
||||
int contentLength = getContentLength();
|
||||
if (contentLength != 0 && _inputState == INPUT_NONE)
|
||||
{
|
||||
|
@ -475,7 +522,7 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
}
|
||||
|
||||
public void extractFormParameters(MultiMap<String> params)
|
||||
public void extractFormParameters(Fields params)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -497,7 +544,7 @@ public class Request implements HttpServletRequest
|
|||
if (_input.isAsync())
|
||||
throw new IllegalStateException("Cannot extract parameters with async IO");
|
||||
|
||||
UrlEncoded.decodeTo(in, params, UrlEncoded.decodeCharset(getCharacterEncoding()), maxFormContentSize, maxFormKeys);
|
||||
UrlEncoded.decodeTo(in, params::add, UrlEncoded.decodeCharset(getCharacterEncoding()), maxFormContentSize, maxFormKeys);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -567,6 +614,8 @@ public class Request implements HttpServletRequest
|
|||
return _channel;
|
||||
if (Connection.class.getName().equals(name))
|
||||
return _channel.getCoreRequest().getConnectionMetaData().getConnection();
|
||||
if (MultiPart.Parser.class.getName().equals(name))
|
||||
return _multiParts;
|
||||
}
|
||||
return (_attributes == null) ? null : _attributes.getAttribute(name);
|
||||
}
|
||||
|
@ -706,7 +755,18 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getContextPath()
|
||||
{
|
||||
// The context path returned is normally for the current context. Except during a cross context
|
||||
//During a cross context INCLUDE dispatch, the context path is that of the originating
|
||||
//request
|
||||
if (_crossContextDispatchSupported)
|
||||
{
|
||||
String crossContextDispatchType = _coreRequest.getContext().getCrossContextDispatchType(_coreRequest);
|
||||
if (DispatcherType.INCLUDE.toString().equals(crossContextDispatchType))
|
||||
{
|
||||
return (String)_coreRequest.getAttribute(CrossContextDispatcher.ORIGINAL_CONTEXT_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
// The context path returned is normally for the current context. Except during an
|
||||
// INCLUDE dispatch, in which case this method returns the context path of the source context,
|
||||
// which we recover from the IncludeAttributes wrapper.
|
||||
ContextHandler.APIContext context;
|
||||
|
@ -740,22 +800,40 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public Cookie[] getCookies()
|
||||
{
|
||||
ContextHandler.CoreContextRequest coreRequest = getCoreRequest();
|
||||
if (coreRequest == null)
|
||||
return null;
|
||||
return CookieCache.getApiCookies(getCoreRequest(), Cookie.class, this::convertCookie);
|
||||
}
|
||||
|
||||
List<HttpCookie> httpCookies = org.eclipse.jetty.server.Request.getCookies(coreRequest);
|
||||
if (httpCookies.isEmpty())
|
||||
return null;
|
||||
private Cookie convertCookie(HttpCookie cookie)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
|
||||
|
||||
if (httpCookies instanceof ServletCookieList servletCookieList)
|
||||
return servletCookieList.getServletCookies();
|
||||
if (getCoreRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance().allows(CookieCompliance.Violation.ATTRIBUTE_VALUES))
|
||||
{
|
||||
if (cookie.getVersion() > 0)
|
||||
result.setVersion(cookie.getVersion());
|
||||
|
||||
ServletCookieList servletCookieList = new ServletCookieList(httpCookies, coreRequest.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
|
||||
coreRequest.setAttribute(org.eclipse.jetty.server.Request.COOKIE_ATTRIBUTE, servletCookieList);
|
||||
if (coreRequest.getComponents().getCache().getAttribute(org.eclipse.jetty.server.Request.CACHE_ATTRIBUTE) instanceof CookieCache cookieCache)
|
||||
cookieCache.replaceCookieList(servletCookieList);
|
||||
return servletCookieList.getServletCookies();
|
||||
String path = cookie.getPath();
|
||||
if (StringUtil.isNotBlank(path))
|
||||
result.setPath(path);
|
||||
|
||||
String domain = cookie.getDomain();
|
||||
if (StringUtil.isNotBlank(domain))
|
||||
result.setDomain(domain);
|
||||
|
||||
String comment = cookie.getComment();
|
||||
if (StringUtil.isNotBlank(comment))
|
||||
result.setComment(comment);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Bad Cookie", x);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -929,7 +1007,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getParameter(String name)
|
||||
{
|
||||
return getParameters().getValue(name, 0);
|
||||
return getParameters().getValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -941,7 +1019,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public Enumeration<String> getParameterNames()
|
||||
{
|
||||
return Collections.enumeration(getParameters().keySet());
|
||||
return Collections.enumeration(getParameters().getNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -953,21 +1031,39 @@ public class Request implements HttpServletRequest
|
|||
return vals.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public MultiMap<String> getQueryParameters()
|
||||
{
|
||||
return _queryParameters == null ? null : _queryParameters.toMultiMap();
|
||||
}
|
||||
|
||||
public Fields getQueryFields()
|
||||
{
|
||||
return _queryParameters;
|
||||
}
|
||||
|
||||
public void setQueryParameters(MultiMap<String> queryParameters)
|
||||
public void setQueryFields(Fields queryParameters)
|
||||
{
|
||||
_queryParameters = queryParameters;
|
||||
}
|
||||
|
||||
public void setContentParameters(MultiMap<String> contentParameters)
|
||||
@Deprecated
|
||||
public void setQueryParameters(MultiMap<String> queryParameters)
|
||||
{
|
||||
_queryParameters = queryParameters == null ? null : new Fields(queryParameters);
|
||||
}
|
||||
|
||||
public void setContentFields(Fields contentParameters)
|
||||
{
|
||||
_contentParameters = contentParameters;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setContentParameters(MultiMap<String> contentParameters)
|
||||
{
|
||||
_contentParameters = contentParameters == null ? NO_PARAMS : new Fields(contentParameters);
|
||||
}
|
||||
|
||||
public void resetParameters()
|
||||
{
|
||||
_parameters = null;
|
||||
|
@ -1026,6 +1122,15 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
if (_crossContextDispatchSupported)
|
||||
{
|
||||
String crossContextDispatchType = _coreRequest.getContext().getCrossContextDispatchType(_coreRequest);
|
||||
if (DispatcherType.INCLUDE.toString().equals(crossContextDispatchType))
|
||||
{
|
||||
return (String)_coreRequest.getAttribute(CrossContextDispatcher.ORIGINAL_QUERY_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
return _uri == null ? null : _uri.getQuery();
|
||||
}
|
||||
|
||||
|
@ -1147,6 +1252,14 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public String getRequestURI()
|
||||
{
|
||||
if (_crossContextDispatchSupported)
|
||||
{
|
||||
String crossContextDispatchType = _coreRequest.getContext().getCrossContextDispatchType(_coreRequest);
|
||||
if (DispatcherType.INCLUDE.toString().equals(crossContextDispatchType))
|
||||
{
|
||||
return (String)_coreRequest.getAttribute(CrossContextDispatcher.ORIGINAL_URI);
|
||||
}
|
||||
}
|
||||
return _uri == null ? null : _uri.getPath();
|
||||
}
|
||||
|
||||
|
@ -1222,6 +1335,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public ServletContext getServletContext()
|
||||
{
|
||||
//TODO cross context include must return the context of the originating request
|
||||
return _context;
|
||||
}
|
||||
|
||||
|
@ -1271,7 +1385,8 @@ public class Request implements HttpServletRequest
|
|||
{
|
||||
try
|
||||
{
|
||||
_multiParts.deleteParts();
|
||||
if (!_crossContextDispatchSupported || !_coreRequest.getContext().isCrossContextDispatch(_coreRequest))
|
||||
_multiParts.deleteParts();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
|
@ -1548,7 +1663,6 @@ public class Request implements HttpServletRequest
|
|||
_asyncNotSupportedSource = null;
|
||||
_secure = false;
|
||||
_handled = false;
|
||||
_contentParamsExtracted = false;
|
||||
_attributes = null;
|
||||
_authentication = Authentication.NOT_CHECKED;
|
||||
_contentType = null;
|
||||
|
@ -1893,10 +2007,22 @@ public class Request implements HttpServletRequest
|
|||
return getParts(null);
|
||||
}
|
||||
|
||||
private Collection<Part> getParts(MultiMap<String> params) throws IOException
|
||||
private Collection<Part> getParts(Fields params) throws IOException
|
||||
{
|
||||
if (_multiParts == null)
|
||||
{
|
||||
if (_crossContextDispatchSupported)
|
||||
{
|
||||
// the request prior or after dispatch may have parsed the multipart
|
||||
Object multipart = _coreRequest.getAttribute(MultiPart.Parser.class.getName());
|
||||
//TODO support cross environment multipart
|
||||
if (multipart instanceof MultiPart.Parser multiPartParser)
|
||||
{
|
||||
_multiParts = multiPartParser;
|
||||
return _multiParts.getParts();
|
||||
}
|
||||
}
|
||||
|
||||
MultipartConfigElement config = (MultipartConfigElement)getAttribute(MULTIPART_CONFIG_ELEMENT);
|
||||
if (config == null)
|
||||
throw new IllegalStateException("No multipart config for servlet");
|
||||
|
@ -1974,12 +2100,15 @@ public class Request implements HttpServletRequest
|
|||
|
||||
String content = os.toString(charset == null ? defaultCharset : Charset.forName(charset));
|
||||
if (_contentParameters == null)
|
||||
_contentParameters = params == null ? new MultiMap<>() : params;
|
||||
_contentParameters = params == null ? new Fields(true) : params;
|
||||
_contentParameters.add(p.getName(), content);
|
||||
}
|
||||
os.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (_crossContextDispatchSupported)
|
||||
_coreRequest.setAttribute(MultiPart.Parser.class.getName(), _multiParts);
|
||||
}
|
||||
|
||||
return _multiParts.getParts();
|
||||
|
@ -2025,21 +2154,21 @@ public class Request implements HttpServletRequest
|
|||
|
||||
public void mergeQueryParameters(String oldQuery, String newQuery)
|
||||
{
|
||||
MultiMap<String> newQueryParams = null;
|
||||
Fields newQueryParams = null;
|
||||
// Have to assume ENCODING because we can't know otherwise.
|
||||
if (newQuery != null)
|
||||
{
|
||||
newQueryParams = new MultiMap<>();
|
||||
UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING);
|
||||
newQueryParams = new Fields(true);
|
||||
UrlEncoded.decodeTo(newQuery, newQueryParams::add, UrlEncoded.ENCODING);
|
||||
}
|
||||
|
||||
MultiMap<String> oldQueryParams = _queryParameters;
|
||||
Fields oldQueryParams = _queryParameters;
|
||||
if (oldQueryParams == null && oldQuery != null)
|
||||
{
|
||||
oldQueryParams = new MultiMap<>();
|
||||
oldQueryParams = new Fields(true);
|
||||
try
|
||||
{
|
||||
UrlEncoded.decodeTo(oldQuery, oldQueryParams, getQueryCharset());
|
||||
UrlEncoded.decodeTo(oldQuery, oldQueryParams::add, getQueryCharset());
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
|
@ -2048,19 +2177,19 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
}
|
||||
|
||||
MultiMap<String> mergedQueryParams;
|
||||
if (newQueryParams == null || newQueryParams.size() == 0)
|
||||
Fields mergedQueryParams;
|
||||
if (newQueryParams == null || newQueryParams.getSize() == 0)
|
||||
mergedQueryParams = oldQueryParams == null ? NO_PARAMS : oldQueryParams;
|
||||
else if (oldQueryParams == null || oldQueryParams.size() == 0)
|
||||
else if (oldQueryParams == null || oldQueryParams.getSize() == 0)
|
||||
mergedQueryParams = newQueryParams;
|
||||
else
|
||||
{
|
||||
// Parameters values are accumulated.
|
||||
mergedQueryParams = new MultiMap<>(newQueryParams);
|
||||
mergedQueryParams.addAllValues(oldQueryParams);
|
||||
mergedQueryParams = new Fields(newQueryParams);
|
||||
mergedQueryParams.addAll(oldQueryParams);
|
||||
}
|
||||
|
||||
setQueryParameters(mergedQueryParams);
|
||||
setQueryFields(mergedQueryParams);
|
||||
resetParameters();
|
||||
}
|
||||
|
||||
|
@ -2077,6 +2206,30 @@ public class Request implements HttpServletRequest
|
|||
*/
|
||||
public void setServletPathMapping(ServletPathMapping servletPathMapping)
|
||||
{
|
||||
// Change request to cross context dispatch
|
||||
if (_crossContextDispatchSupported && _dispatcherType == DispatcherType.REQUEST && _servletPathMapping == null)
|
||||
{
|
||||
String crossContextDispatchType = _coreRequest.getContext().getCrossContextDispatchType(_coreRequest);
|
||||
if (crossContextDispatchType != null)
|
||||
{
|
||||
_dispatcherType = DispatcherType.valueOf(crossContextDispatchType);
|
||||
if (_dispatcherType == DispatcherType.INCLUDE)
|
||||
{
|
||||
// make a ServletPathMapping with the original data returned by findServletPathMapping method
|
||||
Object attribute = _coreRequest.getAttribute(CrossContextDispatcher.ORIGINAL_SERVLET_MAPPING);
|
||||
ServletPathMapping originalMapping = ServletPathMapping.from(attribute);
|
||||
if (originalMapping != attribute)
|
||||
_coreRequest.setAttribute(CrossContextDispatcher.ORIGINAL_SERVLET_MAPPING, originalMapping);
|
||||
|
||||
// Set the include attributes to the target mapping
|
||||
_coreRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, servletPathMapping);
|
||||
_coreRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPathMapping.getServletPath());
|
||||
_coreRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, servletPathMapping.getPathInfo());
|
||||
_coreRequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH, getContext().getContextPath());
|
||||
_coreRequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING, getHttpURI().getQuery());
|
||||
}
|
||||
}
|
||||
}
|
||||
_servletPathMapping = servletPathMapping;
|
||||
}
|
||||
|
||||
|
@ -2099,8 +2252,15 @@ public class Request implements HttpServletRequest
|
|||
ServletPathMapping mapping;
|
||||
if (_dispatcherType == DispatcherType.INCLUDE)
|
||||
{
|
||||
Dispatcher.IncludeAttributes include = Attributes.unwrap(_attributes, Dispatcher.IncludeAttributes.class);
|
||||
mapping = (include == null) ? _servletPathMapping : include.getSourceMapping();
|
||||
if (_crossContextDispatchSupported && DispatcherType.INCLUDE.toString().equals(_coreRequest.getContext().getCrossContextDispatchType(_coreRequest)))
|
||||
{
|
||||
mapping = (ServletPathMapping)_coreRequest.getAttribute(CrossContextDispatcher.ORIGINAL_SERVLET_MAPPING);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.IncludeAttributes include = Attributes.unwrap(_attributes, Dispatcher.IncludeAttributes.class);
|
||||
mapping = (include == null) ? _servletPathMapping : include.getSourceMapping();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2125,79 +2285,4 @@ public class Request implements HttpServletRequest
|
|||
// which we recover from the IncludeAttributes wrapper.
|
||||
return findServletPathMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended list of HttpCookies that converts and caches a servlet Cookie array.
|
||||
*/
|
||||
private static class ServletCookieList extends AbstractList<HttpCookie>
|
||||
{
|
||||
private final List<HttpCookie> _httpCookies;
|
||||
private final Cookie[] _cookies;
|
||||
|
||||
ServletCookieList(List<HttpCookie> httpCookies, CookieCompliance compliance)
|
||||
{
|
||||
_httpCookies = httpCookies;
|
||||
Cookie[] cookies = new Cookie[_httpCookies.size()];
|
||||
int i = 0;
|
||||
for (HttpCookie httpCookie : _httpCookies)
|
||||
{
|
||||
Cookie cookie = convertCookie(httpCookie, compliance);
|
||||
if (cookie == null)
|
||||
cookies = Arrays.copyOf(cookies, cookies.length - 1);
|
||||
else
|
||||
cookies[i++] = cookie;
|
||||
}
|
||||
_cookies = cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCookie get(int index)
|
||||
{
|
||||
return _httpCookies.get(index);
|
||||
}
|
||||
|
||||
public Cookie[] getServletCookies()
|
||||
{
|
||||
return _cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return _cookies.length;
|
||||
}
|
||||
|
||||
private static Cookie convertCookie(HttpCookie cookie, CookieCompliance compliance)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
|
||||
|
||||
if (compliance.allows(CookieCompliance.Violation.ATTRIBUTE_VALUES))
|
||||
{
|
||||
if (cookie.getVersion() > 0)
|
||||
result.setVersion(cookie.getVersion());
|
||||
|
||||
String path = cookie.getPath();
|
||||
if (StringUtil.isNotBlank(path))
|
||||
result.setPath(path);
|
||||
|
||||
String domain = cookie.getDomain();
|
||||
if (StringUtil.isNotBlank(domain))
|
||||
result.setDomain(domain);
|
||||
|
||||
String comment = cookie.getComment();
|
||||
if (StringUtil.isNotBlank(comment))
|
||||
result.setComment(comment);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Bad Cookie", x);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,19 @@ public class Response implements HttpServletResponse
|
|||
private static final EnumSet<EncodingFrom> __localeOverride = EnumSet.of(EncodingFrom.NOT_SET, EncodingFrom.DEFAULT, EncodingFrom.INFERRED, EncodingFrom.SET_LOCALE);
|
||||
private static final EnumSet<EncodingFrom> __explicitCharset = EnumSet.of(EncodingFrom.SET_LOCALE, EncodingFrom.SET_CHARACTER_ENCODING, EncodingFrom.SET_CONTENT_TYPE);
|
||||
|
||||
public static Response getBaseResponse(ServletResponse servletResponse)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (servletResponse instanceof Response response)
|
||||
return response;
|
||||
if (servletResponse instanceof ServletResponseWrapper wrapper)
|
||||
servletResponse = wrapper.getResponse();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Response(HttpChannel channel, HttpOutput out)
|
||||
{
|
||||
_channel = channel;
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.content.InputStreamContentSource;
|
||||
import org.eclipse.jetty.server.Components;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.HttpStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.TunnelSupport;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.ExceptionUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
import static org.eclipse.jetty.util.URIUtil.addEncodedPaths;
|
||||
import static org.eclipse.jetty.util.URIUtil.encodePath;
|
||||
|
||||
/**
|
||||
* Wrap a {@link jakarta.servlet.ServletRequest} as a core {@link Request}.
|
||||
* <p>
|
||||
* Whilst similar to a {@link Wrapper}, this class is not a {@code Wrapper}
|
||||
* as callers should not be able to access {@link Wrapper#getWrapped()} and bypass
|
||||
* the {@link jakarta.servlet.ServletRequest}.
|
||||
* </p>
|
||||
* <p>
|
||||
* The current implementation does not support any read operations.
|
||||
* </p>
|
||||
*/
|
||||
public class ServletCoreRequest implements Request
|
||||
{
|
||||
public Request wrap(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
org.eclipse.jetty.ee9.nested.Request baseRequest = Objects.requireNonNull(org.eclipse.jetty.ee9.nested.Request.getBaseRequest(httpServletRequest));
|
||||
ContextHandler.CoreContextRequest coreContextRequest = baseRequest.getCoreRequest();
|
||||
return new ServletCoreRequest(coreContextRequest, httpServletRequest, coreContextRequest);
|
||||
}
|
||||
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private final ContextHandler.CoreContextRequest _coreContextRequest;
|
||||
private final HttpFields _httpFields;
|
||||
private final HttpURI _uri;
|
||||
private final Attributes _attributes;
|
||||
private final boolean _wrapped;
|
||||
private Content.Source _source;
|
||||
|
||||
ServletCoreRequest(ContextHandler.CoreContextRequest coreContextRequest, HttpServletRequest request, Attributes attributes)
|
||||
{
|
||||
_servletRequest = request;
|
||||
_wrapped = !(request instanceof Request);
|
||||
_coreContextRequest = coreContextRequest;
|
||||
_attributes = attributes;
|
||||
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements())
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
Enumeration<String> headerValues = request.getHeaders(headerName);
|
||||
while (headerValues.hasMoreElements())
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
fields.add(new HttpField(headerName, headerValue));
|
||||
}
|
||||
}
|
||||
|
||||
_httpFields = fields.asImmutable();
|
||||
String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
boolean included = includedServletPath != null;
|
||||
|
||||
HttpURI.Mutable builder = HttpURI.build();
|
||||
builder.scheme(request.getScheme())
|
||||
.authority(request.getServerName(), request.getServerPort());
|
||||
|
||||
// TODO if (included)
|
||||
// builder.path(addEncodedPaths(request.getContextPath(), encodePath(DefaultServlet.getIncludedPathInContext(request, includedServletPath, false))));
|
||||
// else
|
||||
if (request.getDispatcherType() != DispatcherType.REQUEST)
|
||||
builder.path(addEncodedPaths(request.getContextPath(), encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo()))));
|
||||
else
|
||||
builder.path(request.getRequestURI());
|
||||
builder.query(request.getQueryString());
|
||||
_uri = builder.asImmutable();
|
||||
|
||||
_source = _wrapped ? null : _coreContextRequest;
|
||||
}
|
||||
|
||||
private Content.Source source() throws IOException
|
||||
{
|
||||
if (_source == null)
|
||||
_source = _wrapped ? new InputStreamContentSource(getServletRequest().getInputStream()) : _coreContextRequest;
|
||||
return _source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
return _coreContextRequest.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return _servletRequest.getMethod();
|
||||
}
|
||||
|
||||
public HttpServletRequest getServletRequest()
|
||||
{
|
||||
return _servletRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return _servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
return _attributes.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
return _attributes.setAttribute(name, attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
return _attributes.getAttributeNameSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
_attributes.clearAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable failure)
|
||||
{
|
||||
try
|
||||
{
|
||||
source().fail(failure);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
ExceptionUtil.addSuppressedIfNotAssociated(failure, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Components getComponents()
|
||||
{
|
||||
return _coreContextRequest.getComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionMetaData getConnectionMetaData()
|
||||
{
|
||||
return _coreContextRequest.getConnectionMetaData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext()
|
||||
{
|
||||
return _coreContextRequest.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void demand(Runnable demandCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
source().demand(demandCallback);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
demandCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getTrailers()
|
||||
{
|
||||
return _coreContextRequest.getTrailers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBeginNanoTime()
|
||||
{
|
||||
return _coreContextRequest.getBeginNanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeadersNanoTime()
|
||||
{
|
||||
return _coreContextRequest.getHeadersNanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
try
|
||||
{
|
||||
return source().read();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
return Content.Chunk.from(t, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeAvailable()
|
||||
{
|
||||
if (_wrapped)
|
||||
{
|
||||
try
|
||||
{
|
||||
Content.Source.consumeAll(source());
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _coreContextRequest.consumeAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIdleTimeoutListener(Predicate<TimeoutException> onIdleTimeout)
|
||||
{
|
||||
_coreContextRequest.addIdleTimeoutListener(onIdleTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFailureListener(Consumer<Throwable> onFailure)
|
||||
{
|
||||
_coreContextRequest.addFailureListener(onFailure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunnelSupport getTunnelSupport()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHttpStreamWrapper(Function<HttpStream, HttpStream> wrapper)
|
||||
{
|
||||
_coreContextRequest.addHttpStreamWrapper(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
return Session.getSession(_servletRequest.getSession(create));
|
||||
}
|
||||
|
||||
public static class ServletAttributes implements Attributes
|
||||
{
|
||||
private final HttpServletRequest _servletRequest;
|
||||
private Set<String> _attributeNames;
|
||||
|
||||
public ServletAttributes(HttpServletRequest httpServletRequest)
|
||||
{
|
||||
_servletRequest = httpServletRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
if (value != null)
|
||||
_attributeNames = null;
|
||||
_servletRequest.removeAttribute(name);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
Object value = _servletRequest.getAttribute(name);
|
||||
if (value == null)
|
||||
_attributeNames = null;
|
||||
_servletRequest.setAttribute(name, attribute);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return _servletRequest.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
Set<String> set = _attributeNames;
|
||||
if (set == null)
|
||||
{
|
||||
set = new HashSet<>();
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
set.add(e.nextElement());
|
||||
_attributeNames = set;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
Enumeration<String> e = _servletRequest.getAttributeNames();
|
||||
_attributeNames = null;
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
_servletRequest.removeAttribute(e.nextElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.io.ByteBufferInputStream;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
/**
|
||||
* A {@link HttpServletResponse} wrapped as a core {@link Response}.
|
||||
* All write operations are internally converted to blocking writes on the servlet API.
|
||||
*/
|
||||
public class ServletCoreResponse implements org.eclipse.jetty.server.Response
|
||||
{
|
||||
public static org.eclipse.jetty.server.Response wrap(org.eclipse.jetty.server.Request coreRequest, HttpServletResponse httpServletResponse, boolean included)
|
||||
{
|
||||
Response baseResponse = Objects.requireNonNull(Response.getBaseResponse(httpServletResponse));
|
||||
return new ServletCoreResponse(coreRequest, httpServletResponse, baseResponse, baseResponse.getHttpChannel().getCoreResponse(), included);
|
||||
}
|
||||
|
||||
private final HttpServletResponse _httpServletResponse;
|
||||
private final org.eclipse.jetty.server.Request _coreRequest;
|
||||
private final HttpFields.Mutable _httpFields;
|
||||
private final boolean _included;
|
||||
private final Response _baseResponse;
|
||||
private final org.eclipse.jetty.server.Response _coreResponse;
|
||||
private final boolean _wrapped;
|
||||
|
||||
ServletCoreResponse(org.eclipse.jetty.server.Request coreRequest, HttpServletResponse httpServletResponse, Response baseResponse, org.eclipse.jetty.server.Response coreResponse, boolean included)
|
||||
{
|
||||
_coreRequest = coreRequest;
|
||||
_httpServletResponse = httpServletResponse;
|
||||
_baseResponse = baseResponse;
|
||||
_coreResponse = coreResponse;
|
||||
_wrapped = !(httpServletResponse instanceof Response);
|
||||
HttpFields.Mutable fields = new HttpServletResponseHttpFields(httpServletResponse);
|
||||
if (included)
|
||||
{
|
||||
// If included, accept but ignore mutations.
|
||||
fields = new HttpFields.Mutable.Wrapper(fields)
|
||||
{
|
||||
@Override
|
||||
public HttpField onAddField(HttpField field)
|
||||
{
|
||||
/* TODO
|
||||
String name = field == null ? null : field.getName();
|
||||
if (!StringUtil.isBlank(name) && name.startsWith(Dispatcher.JETTY_INCLUDE_HEADER_PREFIX))
|
||||
{
|
||||
return new HttpField(name.substring(Dispatcher.JETTY_INCLUDE_HEADER_PREFIX.length()), field.getValue());
|
||||
}*/
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRemoveField(HttpField field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField onReplaceField(HttpField oldField, HttpField newField)
|
||||
{
|
||||
return oldField;
|
||||
}
|
||||
};
|
||||
}
|
||||
_httpFields = fields;
|
||||
_included = included;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields.Mutable getHeaders()
|
||||
{
|
||||
return _httpFields;
|
||||
}
|
||||
|
||||
public HttpServletResponse getServletResponse()
|
||||
{
|
||||
return _httpServletResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLastWrite()
|
||||
{
|
||||
return _coreResponse.hasLastWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompletedSuccessfully()
|
||||
{
|
||||
return _coreResponse.isCompletedSuccessfully();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _httpServletResponse.isCommitted();
|
||||
}
|
||||
|
||||
private boolean isWriting()
|
||||
{
|
||||
return _baseResponse.isWriting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
|
||||
{
|
||||
if (_included)
|
||||
last = false;
|
||||
try
|
||||
{
|
||||
if (!_wrapped && !_baseResponse.isWritingOrStreaming())
|
||||
{
|
||||
_coreResponse.write(last, byteBuffer, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BufferUtil.hasContent(byteBuffer))
|
||||
{
|
||||
if (isWriting())
|
||||
{
|
||||
String characterEncoding = _httpServletResponse.getCharacterEncoding();
|
||||
try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
|
||||
InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
|
||||
{
|
||||
IO.copy(reader, _httpServletResponse.getWriter());
|
||||
}
|
||||
|
||||
if (last)
|
||||
_httpServletResponse.getWriter().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferUtil.writeTo(byteBuffer, _httpServletResponse.getOutputStream());
|
||||
if (last)
|
||||
_httpServletResponse.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
callback.failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.server.Request getRequest()
|
||||
{
|
||||
return _coreRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus()
|
||||
{
|
||||
return _httpServletResponse.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int code)
|
||||
{
|
||||
if (_included)
|
||||
return;
|
||||
_httpServletResponse.setStatus(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<HttpFields> getTrailersSupplier()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrailersSupplier(Supplier<HttpFields> trailers)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_httpServletResponse.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
return _coreResponse.writeInterim(status, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _httpServletResponse);
|
||||
}
|
||||
|
||||
private static class HttpServletResponseHttpFields implements HttpFields.Mutable
|
||||
{
|
||||
private final HttpServletResponse _response;
|
||||
|
||||
private HttpServletResponseHttpFields(HttpServletResponse response)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<HttpField> listIterator(int index)
|
||||
{
|
||||
// The minimum requirement is to implement the listIterator, but it is inefficient.
|
||||
// Other methods are implemented for efficiency.
|
||||
final ListIterator<HttpField> list = _response.getHeaderNames().stream()
|
||||
.map(n -> new HttpField(n, _response.getHeader(n)))
|
||||
.collect(Collectors.toList())
|
||||
.listIterator(index);
|
||||
|
||||
return new ListIterator<>()
|
||||
{
|
||||
HttpField _last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return list.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField next()
|
||||
{
|
||||
return _last = list.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious()
|
||||
{
|
||||
return list.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpField previous()
|
||||
{
|
||||
return _last = list.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex()
|
||||
{
|
||||
return list.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex()
|
||||
{
|
||||
return list.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
if (_last != null)
|
||||
{
|
||||
// This is not exactly the right semantic for repeated field names
|
||||
list.remove();
|
||||
_response.setHeader(_last.getName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(HttpField httpField)
|
||||
{
|
||||
list.set(httpField);
|
||||
_response.setHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(HttpField httpField)
|
||||
{
|
||||
list.add(httpField);
|
||||
_response.addHeader(httpField.getName(), httpField.getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(String name, String value)
|
||||
{
|
||||
_response.addHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpHeader header, String value)
|
||||
{
|
||||
_response.addHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable add(HttpField field)
|
||||
{
|
||||
_response.addHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpField field)
|
||||
{
|
||||
_response.setHeader(field.getName(), field.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, String value)
|
||||
{
|
||||
_response.setHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value.asString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(HttpHeader header, String value)
|
||||
{
|
||||
_response.setHeader(header.asString(), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable put(String name, List<String> list)
|
||||
{
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(list);
|
||||
boolean first = true;
|
||||
for (String s : list)
|
||||
{
|
||||
if (first)
|
||||
_response.setHeader(name, s);
|
||||
else
|
||||
_response.addHeader(name, s);
|
||||
first = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(HttpHeader header)
|
||||
{
|
||||
_response.setHeader(header.asString(), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(EnumSet<HttpHeader> fields)
|
||||
{
|
||||
for (HttpHeader header : fields)
|
||||
remove(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutable remove(String name)
|
||||
{
|
||||
_response.setHeader(name, null);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
package org.eclipse.jetty.ee9.nested;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
|
@ -113,7 +116,9 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
_servletPath = pathSpec.getPrefix();
|
||||
// TODO avoid the substring on the known servletPath!
|
||||
_matchValue = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
|
||||
_pathInfo = matchedPath != null ? matchedPath.getPathInfo() : null;
|
||||
_pathInfo = matchedPath != null
|
||||
? matchedPath.getPathInfo()
|
||||
: _servletPath.length() == pathInContext.length() ? null : pathInContext.substring(_servletPath.length());
|
||||
break;
|
||||
|
||||
case SUFFIX_GLOB:
|
||||
|
@ -135,6 +140,16 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
this(pathSpec, servletName, pathInContext, null);
|
||||
}
|
||||
|
||||
private ServletPathMapping(MappingMatch mappingMatch, String matchValue, String pattern, String servletName, String servletPath, String pathInfo)
|
||||
{
|
||||
_mappingMatch = mappingMatch;
|
||||
_matchValue = matchValue;
|
||||
_pattern = pattern;
|
||||
_servletName = servletName;
|
||||
_servletPath = servletPath;
|
||||
_pathInfo = pathInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMatchValue()
|
||||
{
|
||||
|
@ -173,12 +188,46 @@ public class ServletPathMapping implements HttpServletMapping
|
|||
public String toString()
|
||||
{
|
||||
return "ServletPathMapping{" +
|
||||
"matchValue=" + _matchValue +
|
||||
"mappingMatch=" + _mappingMatch +
|
||||
", matchValue=" + _matchValue +
|
||||
", pattern=" + _pattern +
|
||||
", servletName=" + _servletName +
|
||||
", mappingMatch=" + _mappingMatch +
|
||||
", servletPath=" + _servletPath +
|
||||
", pathInfo=" + _pathInfo +
|
||||
"}";
|
||||
}
|
||||
|
||||
private static final Pattern DESERIALIZE = Pattern.compile("ServletPathMapping\\{" +
|
||||
"mappingMatch=(?<mappingMatch>[^,]+), " +
|
||||
"matchValue=(?<matchValue>[^,]+), " +
|
||||
"pattern=(?<pattern>[^,]+), " +
|
||||
"servletName=(?<servletName>[^,]+), " +
|
||||
"servletPath=(?<servletPath>[^,]+), " +
|
||||
"pathInfo=(?<pathInfo>[^}]+)}");
|
||||
|
||||
/**
|
||||
* Obtain a {@link ServletPathMapping} instance from an object which may be an instance of a mapping
|
||||
* from a different EE version obtained from a cross context cross environment dispatch
|
||||
* @param o The object to caste or deserialize from the string representation.
|
||||
* @return A ServletPathMapping
|
||||
*/
|
||||
public static ServletPathMapping from(Object o)
|
||||
{
|
||||
if (o == null)
|
||||
return null;
|
||||
if (o instanceof ServletPathMapping mapping)
|
||||
return mapping;
|
||||
Matcher matcher = DESERIALIZE.matcher(o.toString());
|
||||
if (matcher.find())
|
||||
return new ServletPathMapping(
|
||||
MappingMatch.valueOf(matcher.group("mappingMatch")),
|
||||
matcher.group("matchValue"),
|
||||
matcher.group("pattern"),
|
||||
matcher.group("servletName"),
|
||||
matcher.group("servletPath"),
|
||||
matcher.group("pathInfo")
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ import org.eclipse.jetty.security.UserIdentity;
|
|||
import org.eclipse.jetty.security.openid.OpenIdConfiguration;
|
||||
import org.eclipse.jetty.security.openid.OpenIdCredentials;
|
||||
import org.eclipse.jetty.security.openid.OpenIdLoginService;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -512,12 +512,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
if (jUri.equals(buf.toString()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
MultiMap<String> jPost = (MultiMap<String>)session.getAttribute(J_POST);
|
||||
Fields jPost = (Fields)session.getAttribute(J_POST);
|
||||
if (jPost != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("auth rePOST {}->{}", authentication, jUri);
|
||||
baseRequest.setContentParameters(jPost);
|
||||
baseRequest.setContentFields(jPost);
|
||||
}
|
||||
session.removeAttribute(J_URI);
|
||||
session.removeAttribute(J_METHOD);
|
||||
|
@ -700,7 +700,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
|
||||
private final String _uri;
|
||||
private final String _method;
|
||||
private final MultiMap<String> _formParameters;
|
||||
private final Fields _formParameters;
|
||||
|
||||
public UriRedirectInfo(Request request)
|
||||
{
|
||||
|
@ -709,7 +709,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(request.getContentType()) && HttpMethod.POST.is(request.getMethod()))
|
||||
{
|
||||
MultiMap<String> formParameters = new MultiMap<>();
|
||||
Fields formParameters = new Fields(true);
|
||||
request.extractFormParameters(formParameters);
|
||||
_formParameters = formParameters;
|
||||
}
|
||||
|
@ -729,7 +729,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
return _method;
|
||||
}
|
||||
|
||||
public MultiMap<String> getFormParameters()
|
||||
public Fields getFormParameters()
|
||||
{
|
||||
return _formParameters;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.eclipse.jetty.http.HttpHeaderValue;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.security.UserIdentity;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -340,11 +340,11 @@ public class FormAuthenticator extends LoginAuthenticator
|
|||
|
||||
if (jUri.equals(buf.toString()))
|
||||
{
|
||||
MultiMap<String> jPost = (MultiMap<String>)session.getAttribute(__J_POST);
|
||||
Fields jPost = (Fields)session.getAttribute(__J_POST);
|
||||
if (jPost != null)
|
||||
{
|
||||
LOG.debug("auth rePOST {}->{}", authentication, jUri);
|
||||
baseRequest.setContentParameters(jPost);
|
||||
baseRequest.setContentFields(jPost);
|
||||
}
|
||||
session.removeAttribute(__J_URI);
|
||||
session.removeAttribute(__J_METHOD);
|
||||
|
@ -379,7 +379,7 @@ public class FormAuthenticator extends LoginAuthenticator
|
|||
|
||||
if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
|
||||
{
|
||||
MultiMap<String> formParameters = new MultiMap<>();
|
||||
Fields formParameters = new Fields(true);
|
||||
baseRequest.extractFormParameters(formParameters);
|
||||
session.setAttribute(__J_POST, formParameters);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -41,9 +41,7 @@ import jakarta.servlet.ServletContext;
|
|||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletRequestWrapper;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.ServletResponseWrapper;
|
||||
import jakarta.servlet.WriteListener;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
|
@ -314,7 +312,6 @@ public class DispatcherTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO
|
||||
public void testForwardWithParam() throws Exception
|
||||
{
|
||||
createDefaultContextHandlerCollection();
|
||||
|
@ -841,7 +838,6 @@ public class DispatcherTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO
|
||||
public void testDispatchMapping() throws Exception
|
||||
{
|
||||
createDefaultContextHandlerCollection();
|
||||
|
|
|
@ -299,8 +299,6 @@ public class CreationTest
|
|||
* session in it too. Check that both sessions exist after the response
|
||||
* completes.
|
||||
*/
|
||||
//TODO - no cross context support in jetty-12
|
||||
@Disabled
|
||||
@Test
|
||||
public void testSessionCreateForward() throws Exception
|
||||
{
|
||||
|
@ -317,8 +315,10 @@ public class CreationTest
|
|||
TestServlet servlet = new TestServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||
contextHandler.setCrossContextDispatchSupported(true);
|
||||
contextHandler.addServlet(holder, servletMapping);
|
||||
ServletContextHandler ctxB = server1.addContext(contextB);
|
||||
ctxB.setCrossContextDispatchSupported(true);
|
||||
ctxB.addServlet(TestServletB.class, servletMapping);
|
||||
server1.start();
|
||||
int port1 = server1.getPort();
|
||||
|
@ -348,8 +348,6 @@ public class CreationTest
|
|||
* in it, then invalidate the session in the original context: that should invalidate the
|
||||
* session in both contexts and no session should exist after the response completes.
|
||||
*/
|
||||
//TODO no cross context dispatch in jetty-12
|
||||
@Disabled
|
||||
@Test
|
||||
public void testSessionCreateForwardAndInvalidate() throws Exception
|
||||
{
|
||||
|
@ -367,8 +365,10 @@ public class CreationTest
|
|||
TestServlet servlet = new TestServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
ServletContextHandler contextHandler = server1.addContext(contextPath);
|
||||
contextHandler.setCrossContextDispatchSupported(true);
|
||||
contextHandler.addServlet(holder, servletMapping);
|
||||
ServletContextHandler ctxB = server1.addContext(contextB);
|
||||
ctxB.setCrossContextDispatchSupported(true);
|
||||
ctxB.addServlet(TestServletB.class, servletMapping);
|
||||
server1.start();
|
||||
int port1 = server1.getPort();
|
||||
|
|
Loading…
Reference in New Issue