org.eclipse.jetty.ee8.nested.Request Maven / Gradle / Ivy
Show all versions of jetty-ee8-nested Show documentation
//
// ========================================================================
// 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.ee8.nested;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
import javax.servlet.http.PushBuilder;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.CookieCompliance;
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.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.MultiPartCompliance;
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;
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;
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;
/**
* Jetty Request.
*/
public class Request implements HttpServletRequest {
/**
* The name of the MultiPartConfig request attribute
*/
public static final String MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
/**
* @deprecated use {@link #MULTIPART_CONFIG_ELEMENT}
*/
@Deprecated
public static final String __MULTIPART_CONFIG_ELEMENT = MULTIPART_CONFIG_ELEMENT;
public static final String SSL_CIPHER_SUITE = "javax.servlet.request.cipher_suite";
public static final String SSL_KEY_SIZE = "javax.servlet.request.key_size";
public static final String SSL_SESSION_ID = "javax.servlet.request.ssl_session_id";
public static final String PEER_CERTIFICATES = "javax.servlet.request.X509Certificate";
private static final Logger LOG = LoggerFactory.getLogger(Request.class);
private static final SetCookieParser SET_COOKIE_PARSER = SetCookieParser.newInstance();
private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
private static final int INPUT_NONE = 0;
private static final int INPUT_STREAM = 1;
private static final int INPUT_READER = 2;
private static final Fields NO_PARAMS = Fields.EMPTY;
private static final Fields BAD_PARAMS = new Fields(true);
/**
* Compare inputParameters to NO_PARAMS by Reference
*
* @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(Fields inputParameters) {
@SuppressWarnings("ReferenceEquality")
boolean isNoParams = (inputParameters == NO_PARAMS);
return isNoParams;
}
/**
* Obtain the base {@link Request} instance of a {@link ServletRequest}, by
* coercion, unwrapping or special attribute.
*
* @param request The request
* @return the base {@link Request} instance of a {@link ServletRequest}.
*/
public static Request getBaseRequest(ServletRequest request) {
if (request instanceof Request)
return (Request) request;
Object channel = request.getAttribute(HttpChannel.class.getName());
if (channel instanceof HttpChannel)
return ((HttpChannel) channel).getRequest();
while (request instanceof ServletRequestWrapper) {
request = ((ServletRequestWrapper) request).getRequest();
}
if (request instanceof Request)
return (Request) request;
return null;
}
private final HttpChannel _channel;
private ContextHandler.APIContext _context;
private final List _requestAttributeListeners = new ArrayList<>();
private final HttpInput _input;
private final boolean _crossContextDispatchSupported;
private ContextHandler.CoreContextRequest _coreRequest;
private MetaData.Request _metaData;
private HttpFields _httpFields;
private HttpFields _trailers;
private HttpURI _uri;
private String _method;
private String _pathInContext;
private ServletPathMapping _servletPathMapping;
private Object _asyncNotSupportedSource = null;
private boolean _secure;
private boolean _handled = false;
private Attributes _attributes;
private Authentication _authentication;
private String _contentType;
private String _characterEncoding;
private DispatcherType _dispatcherType;
private int _inputState = INPUT_NONE;
private BufferedReader _reader;
private String _readerEncoding;
private Fields _queryParameters;
private Fields _contentParameters;
private Fields _parameters;
private Charset _queryEncoding;
private UserIdentityScope _scope;
private long _timeStamp;
// parser for multipart/form-data request content
private MultiPart.Parser _multiParts;
private AsyncContextState _async;
private String _lastPathInContext;
private ContextHandler.APIContext _lastContext;
public Request(HttpChannel channel, HttpInput input) {
_channel = channel;
_input = input;
_crossContextDispatchSupported = _channel.getContextHandler().getCoreContextHandler().isCrossContextDispatchSupported();
}
public HttpFields getHttpFields() {
return _httpFields;
}
public void setHttpFields(HttpFields fields) {
_httpFields = fields.asImmutable();
}
@Override
public Map getTrailerFields() {
HttpFields trailersFields = getTrailerHttpFields();
if (trailersFields == null)
return Collections.emptyMap();
Map trailers = new HashMap<>();
for (HttpField field : trailersFields) {
String key = field.getLowerCaseName();
// Servlet spec requires field names to be lower case.
trailers.merge(key, field.getValue(), (existing, value) -> existing + "," + value);
}
return trailers;
}
public void setTrailerHttpFields(HttpFields trailers) {
_trailers = trailers == null ? null : trailers.asImmutable();
}
public HttpFields getTrailerHttpFields() {
return _trailers;
}
public HttpInput getHttpInput() {
return _input;
}
public boolean isPush() {
return Boolean.TRUE.equals(getAttribute("org.eclipse.jetty.pushed"));
}
public boolean isPushSupported() {
return !isPush() && getCoreRequest().getConnectionMetaData().isPushSupported();
}
private static final EnumSet NOT_PUSHED_HEADERS = EnumSet.of(HttpHeader.IF_MATCH, HttpHeader.IF_RANGE, HttpHeader.IF_UNMODIFIED_SINCE, HttpHeader.RANGE, HttpHeader.EXPECT, HttpHeader.REFERER, HttpHeader.COOKIE, HttpHeader.AUTHORIZATION, HttpHeader.IF_NONE_MATCH, HttpHeader.IF_MODIFIED_SINCE);
@Override
public PushBuilder newPushBuilder() {
if (!isPushSupported())
return null;
HttpFields.Mutable fields = HttpFields.build(getHttpFields(), NOT_PUSHED_HEADERS);
HttpField authField = getHttpFields().getField(HttpHeader.AUTHORIZATION);
//TODO check what to do for digest etc etc
if (authField != null && getUserPrincipal() != null && authField.getValue().startsWith("Basic"))
fields.add(authField);
String id;
try {
HttpSession session = getSession(false);
if (session != null) {
// checks if session is valid
session.getLastAccessedTime();
id = session.getId();
} else
id = getRequestedSessionId();
} catch (IllegalStateException e) {
id = getRequestedSessionId();
}
StringBuilder cookieBuilder = new StringBuilder();
Cookie[] cookies = getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (!cookieBuilder.isEmpty())
cookieBuilder.append("; ");
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue());
}
}
// Any Set-Cookie in the response should be present in the push.
for (HttpField field : getResponse().getHttpFields()) {
HttpHeader header = field.getHeader();
if (header == HttpHeader.SET_COOKIE || header == HttpHeader.SET_COOKIE2) {
HttpCookie httpCookie;
if (field instanceof HttpCookieUtils.SetCookieHttpField set)
httpCookie = set.getHttpCookie();
else
httpCookie = SET_COOKIE_PARSER.parse(field.getValue());
if (httpCookie == null || httpCookie.isExpired())
continue;
if (!cookieBuilder.isEmpty())
cookieBuilder.append("; ");
cookieBuilder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
}
}
if (!cookieBuilder.isEmpty())
fields.put(HttpHeader.COOKIE, cookieBuilder.toString());
String query = getQueryString();
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), query, id);
String referrer = getRequestURL().toString();
if (query != null)
referrer += "?" + query;
builder.addHeader("referer", referrer);
return builder;
}
public void addEventListener(final EventListener listener) {
if (listener instanceof ServletRequestAttributeListener)
_requestAttributeListeners.add((ServletRequestAttributeListener) listener);
if (listener instanceof AsyncListener)
throw new IllegalArgumentException(listener.getClass().toString());
}
Fields peekParameters() {
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.
if (_contentParameters == null) {
try {
extractContentParameters();
} catch (IllegalStateException | IllegalArgumentException e) {
LOG.warn(e.toString());
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;
}
return parameters == null ? NO_PARAMS : parameters;
}
private void extractQueryParameters() {
if (_uri == null) {
_queryParameters = NO_PARAMS;
return;
}
String query = _uri.getQuery();
if (StringUtil.isEmpty(query))
_queryParameters = NO_PARAMS;
else {
try {
_queryParameters = new Fields(true);
UrlEncoded.decodeTo(query, _queryParameters::add, _queryEncoding);
} catch (IllegalStateException | IllegalArgumentException e) {
_queryParameters = BAD_PARAMS;
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() {
String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING);
if (contentEncoding == null)
return true;
return HttpHeaderValue.IDENTITY.is(contentEncoding);
}
private void extractContentParameters() {
// TODO get from coreRequest
String contentType = getContentType();
if (contentType == null || contentType.isEmpty())
_contentParameters = NO_PARAMS;
else {
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) {
String baseType = HttpField.getValueParameters(contentType, null);
if (MimeTypes.Type.FORM_ENCODED.is(baseType) && _channel.getHttpConfiguration().isFormEncodedMethod(getMethod())) {
if (_metaData != null && !isContentEncodingSupported()) {
throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding");
}
extractFormParameters(_contentParameters);
} else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) && getAttribute(MULTIPART_CONFIG_ELEMENT) != null && _multiParts == null) {
try {
if (_metaData != null && !isContentEncodingSupported()) {
throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding");
}
getParts(_contentParameters);
} catch (IOException e) {
String msg = "Unable to extract content parameters";
if (LOG.isDebugEnabled())
LOG.debug(msg, e);
throw new BadMessageException(msg, e);
}
}
}
}
}
public void extractFormParameters(Fields params) {
try {
int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE;
int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS;
if (_context != null) {
ContextHandler contextHandler = _context.getContextHandler();
maxFormContentSize = contextHandler.getMaxFormContentSize();
maxFormKeys = contextHandler.getMaxFormKeys();
}
int contentLength = getContentLength();
if (maxFormContentSize >= 0 && contentLength > maxFormContentSize)
throw new IllegalStateException("Form is larger than max length " + maxFormContentSize);
InputStream in = getInputStream();
if (_input.isAsync())
throw new IllegalStateException("Cannot extract parameters with async IO");
UrlEncoded.decodeTo(in, params::add, UrlEncoded.decodeCharset(getCharacterEncoding()), maxFormContentSize, maxFormKeys);
} catch (IOException e) {
String msg = "Unable to extract form parameters";
if (LOG.isDebugEnabled())
LOG.debug(msg, e);
throw new RuntimeIOException(msg, e);
}
}
private int lookupServerAttribute(String key, int dftValue) {
Object attribute = _channel.getServer().getAttribute(key);
if (attribute instanceof Number)
return ((Number) attribute).intValue();
else if (attribute instanceof String)
return Integer.parseInt((String) attribute);
return dftValue;
}
@Override
public AsyncContext getAsyncContext() {
HttpChannelState state = getHttpChannelState();
if (_async == null || !state.isAsyncStarted())
throw new IllegalStateException(state.getStatusString());
return _async;
}
public HttpChannelState getHttpChannelState() {
return _channel.getState();
}
/**
* @deprecated use core level ComplianceViolation.Listener instead. - will be removed in Jetty 12.1.0
*/
@Deprecated(since = "12.0.6", forRemoval = true)
public ComplianceViolation.Listener getComplianceViolationListener() {
return org.eclipse.jetty.server.HttpChannel.from(getCoreRequest()).getComplianceViolationListener();
}
/**
* Get Request Attribute.
*
* Also supports jetty specific attributes to gain access to Jetty APIs:
*
* - org.eclipse.jetty.server.Server
- The Jetty Server instance
* - org.eclipse.jetty.server.HttpChannel
- The HttpChannel for this request
* - org.eclipse.jetty.server.HttpConnection
- The HttpConnection or null if another transport is used
*
* While these attributes may look like security problems, they are exposing nothing that is not already
* available via reflection from a Request instance.
*
* @see ServletRequest#getAttribute(String)
*/
@Override
public Object getAttribute(String name) {
if (name.startsWith("org.eclipse.jetty")) {
if (Server.class.getName().equals(name))
return _channel.getServer();
if (HttpChannel.class.getName().equals(name))
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);
}
@Override
public Enumeration getAttributeNames() {
if (_attributes == null)
return Collections.enumeration(Collections.emptyList());
return Collections.enumeration(_attributes.getAttributeNameSet());
}
public Attributes getAttributes() {
return _attributes;
}
/**
* Get the authentication.
*
* @return the authentication
*/
public Authentication getAuthentication() {
return _authentication;
}
@Override
public String getAuthType() {
if (_authentication instanceof Authentication.Deferred)
setAuthentication(((Authentication.Deferred) _authentication).authenticate(this));
if (_authentication instanceof Authentication.User)
return ((Authentication.User) _authentication).getAuthMethod();
return null;
}
@Override
public String getCharacterEncoding() {
if (_characterEncoding == null) {
if (_context != null)
_characterEncoding = _context.getRequestCharacterEncoding();
if (_characterEncoding == null) {
String contentType = getContentType();
if (contentType != null) {
MimeTypes.Type mime = MimeTypes.CACHE.get(contentType);
String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(contentType) : mime.getCharset().toString();
if (charset != null)
_characterEncoding = charset;
}
}
}
return _characterEncoding;
}
/**
* @return Returns the connection.
*/
public HttpChannel getHttpChannel() {
return _channel;
}
@Override
public int getContentLength() {
long contentLength = getContentLengthLong();
if (contentLength > Integer.MAX_VALUE)
// Per ServletRequest#getContentLength() javadoc this must return -1 for values exceeding Integer.MAX_VALUE
return -1;
return (int) contentLength;
}
@Override
public long getContentLengthLong() {
// Even thought the metadata might know the real content length,
// we always look at the headers because the length may be changed by interceptors.
if (_httpFields == null)
return -1;
return _httpFields.getLongField(HttpHeader.CONTENT_LENGTH);
}
public long getContentRead() {
return _input.getContentReceived();
}
@Override
public String getContentType() {
if (_contentType == null) {
MetaData.Request metadata = _metaData;
_contentType = metadata == null ? null : metadata.getHttpFields().get(HttpHeader.CONTENT_TYPE);
}
return _contentType;
}
/**
* @return The {@link ContextHandler.APIContext context} used for this request.
*/
public ContextHandler.APIContext getContext() {
return _context;
}
public void setContext(ContextHandler.APIContext context, String pathInContext) {
_context = context;
_pathInContext = pathInContext;
if (context != null) {
_lastContext = context;
_lastPathInContext = pathInContext;
}
}
public ContextHandler.APIContext getLastContext() {
return _lastContext;
}
public String getLastPathInContext() {
return _lastPathInContext;
}
@Override
public String getContextPath() {
//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;
if (_dispatcherType == DispatcherType.INCLUDE) {
Dispatcher.IncludeAttributes include = Attributes.unwrap(_attributes, Dispatcher.IncludeAttributes.class);
context = (include == null) ? _context : include.getSourceContext();
} else {
context = _context;
}
if (context == null)
return null;
return context.getContextHandler().getRequestContextPath();
}
/**
* Get the path relative to the context path, analogous to {@link #getServletPath()} + {@link #getPathInfo()}.
* If no context is set, then the path in context is the full path.
* @return The decoded part of the {@link #getRequestURI()} path after any {@link #getContextPath()}
* up to any {@link #getQueryString()}, excluding path parameters.
*/
public String getPathInContext() {
return _pathInContext;
}
@Override
public Cookie[] getCookies() {
return CookieCache.getApiCookies(getCoreRequest(), Cookie.class, this::convertCookie);
}
private Cookie convertCookie(HttpCookie cookie) {
try {
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
if (getCoreRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance().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;
}
@Override
public long getDateHeader(String name) {
HttpFields fields = _httpFields;
if (fields == null)
return -1;
HttpField field = fields.getField(name);
if (field == null)
return -1;
long date = fields.getDateField(name);
if (date == -1)
throw new IllegalArgumentException("Cannot parse date");
return date;
}
@Override
public DispatcherType getDispatcherType() {
return _dispatcherType;
}
@Override
public String getHeader(String name) {
HttpFields fields = _httpFields;
return fields == null ? null : fields.get(name);
}
@Override
public Enumeration getHeaderNames() {
HttpFields fields = _httpFields;
return fields == null ? Collections.emptyEnumeration() : Collections.enumeration(fields.getFieldNamesCollection());
}
@Override
public Enumeration getHeaders(String name) {
HttpFields fields = _httpFields;
if (fields == null)
return Collections.emptyEnumeration();
Enumeration e = fields.getValues(name);
if (e == null)
return Collections.enumeration(Collections.emptyList());
return e;
}
/**
* @return Returns the inputState.
*/
public int getInputState() {
return _inputState;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (_inputState != INPUT_NONE && _inputState != INPUT_STREAM)
throw new IllegalStateException("READER");
_inputState = INPUT_STREAM;
// Try to write a 100 continue, ignoring failure result if it was not necessary.
_channel.getCoreResponse().writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY);
return _input;
}
@Override
public int getIntHeader(String name) {
HttpFields fields = _httpFields;
return fields == null ? -1 : (int) fields.getLongField(name);
}
@Override
public Locale getLocale() {
HttpFields fields = _httpFields;
if (fields == null)
return Locale.getDefault();
List acceptable = fields.getQualityCSV(HttpHeader.ACCEPT_LANGUAGE);
// handle no locale
if (acceptable.isEmpty())
return Locale.getDefault();
String language = acceptable.get(0);
language = HttpField.stripParameters(language);
String country = "";
int dash = language.indexOf('-');
if (dash > -1) {
country = language.substring(dash + 1).trim();
language = language.substring(0, dash).trim();
}
return new Locale(language, country);
}
@Override
public Enumeration getLocales() {
HttpFields fields = _httpFields;
if (fields == null)
return Collections.enumeration(__defaultLocale);
List acceptable = fields.getQualityCSV(HttpHeader.ACCEPT_LANGUAGE);
// handle no locale
if (acceptable.isEmpty())
return Collections.enumeration(__defaultLocale);
List locales = acceptable.stream().map(language -> {
language = HttpField.stripParameters(language);
String country = "";
int dash = language.indexOf('-');
if (dash > -1) {
country = language.substring(dash + 1).trim();
language = language.substring(0, dash).trim();
}
return new Locale(language, country);
}).collect(Collectors.toList());
return Collections.enumeration(locales);
}
@Override
public String getLocalAddr() {
if (_channel != null) {
InetSocketAddress local = _channel.getLocalAddress();
if (local == null)
return "";
InetAddress address = local.getAddress();
String result = address == null ? local.getHostString() : address.getHostAddress();
return HostPort.normalizeHost(result);
}
return "";
}
@Override
public String getLocalName() {
if (_channel != null) {
String localName = _channel.getLocalName();
return HostPort.normalizeHost(localName);
}
// not allowed to be null
return "";
}
@Override
public int getLocalPort() {
return org.eclipse.jetty.server.Request.getLocalPort(_channel.getCoreRequest());
}
@Override
public String getMethod() {
return _method;
}
@Override
public String getParameter(String name) {
return getParameters().getValue(name);
}
@Override
public Map getParameterMap() {
return Collections.unmodifiableMap(getParameters().toStringArrayMap());
}
@Override
public Enumeration getParameterNames() {
return Collections.enumeration(getParameters().getNames());
}
@Override
public String[] getParameterValues(String name) {
List vals = getParameters().getValues(name);
if (vals == null)
return null;
return vals.toArray(new String[0]);
}
@Deprecated
public MultiMap getQueryParameters() {
return _queryParameters == null ? null : _queryParameters.toMultiMap();
}
public Fields getQueryFields() {
return _queryParameters;
}
public void setQueryFields(Fields queryParameters) {
_queryParameters = queryParameters;
}
@Deprecated
public void setQueryParameters(MultiMap queryParameters) {
_queryParameters = queryParameters == null ? null : new Fields(queryParameters);
}
public void setContentFields(Fields contentParameters) {
_contentParameters = contentParameters;
}
@Deprecated
public void setContentParameters(MultiMap contentParameters) {
_contentParameters = contentParameters == null ? NO_PARAMS : new Fields(contentParameters);
}
public void resetParameters() {
_parameters = null;
}
@Override
public String getPathInfo() {
// The pathInfo returned is normally for the current servlet. Except during an
// INCLUDE dispatch, in which case this method returns the pathInfo of the source servlet,
// which we recover from the IncludeAttributes wrapper.
ServletPathMapping mapping = findServletPathMapping();
return mapping == null ? _pathInContext : mapping.getPathInfo();
}
@Override
public String getPathTranslated() {
String pathInfo = getPathInfo();
if (pathInfo == null || _context == null)
return null;
return _context.getRealPath(pathInfo);
}
@Override
public String getProtocol() {
MetaData.Request metadata = _metaData;
if (metadata == null)
return null;
HttpVersion version = metadata.getHttpVersion();
if (version == null)
return null;
return version.toString();
}
/*
* @see jakarta.servlet.ServletRequest#getProtocol()
*/
public HttpVersion getHttpVersion() {
MetaData.Request metadata = _metaData;
return metadata == null ? null : metadata.getHttpVersion();
}
public String getQueryEncoding() {
return _queryEncoding == null ? null : _queryEncoding.name();
}
Charset getQueryCharset() {
return _queryEncoding;
}
@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();
}
@Override
public BufferedReader getReader() throws IOException {
if (_inputState != INPUT_NONE && _inputState != INPUT_READER)
throw new IllegalStateException("STREAMED");
if (_inputState == INPUT_READER)
return _reader;
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = MimeTypes.ISO_8859_1;
if (_reader != null && encoding.equalsIgnoreCase(_readerEncoding)) {
// Try to write a 100 continue, ignoring failure result if it was not necessary.
_channel.getCoreResponse().writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY);
} else {
ServletInputStream in = getInputStream();
_readerEncoding = encoding;
_reader = new BufferedReader(new InputStreamReader(in, encoding)) {
@Override
public void close() throws IOException {
// Do not call super to avoid marking this reader as closed,
// but do close the ServletInputStream that can be reopened.
in.close();
}
};
}
_inputState = INPUT_READER;
return _reader;
}
@Override
@Deprecated(since = "Servlet API 2.1")
public String getRealPath(String path) {
if (_context == null)
return null;
return _context.getRealPath(path);
}
/**
* Access the underlying Remote {@link InetSocketAddress} for this request.
*
* @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
* conditions that result in no remote address)
*/
public InetSocketAddress getRemoteInetSocketAddress() {
return _channel.getCoreRequest().getConnectionMetaData().getRemoteSocketAddress() instanceof InetSocketAddress inetSocketAddr ? inetSocketAddr : null;
}
@Override
public String getRemoteAddr() {
return org.eclipse.jetty.server.Request.getRemoteAddr(_channel.getCoreRequest());
}
@Override
public String getRemoteHost() {
SocketAddress remote = _channel.getCoreRequest().getConnectionMetaData().getRemoteSocketAddress();
if (remote instanceof InetSocketAddress inetSocketAddress)
return inetSocketAddress.getHostString();
return remote.toString();
}
@Override
public int getRemotePort() {
return org.eclipse.jetty.server.Request.getRemotePort(_channel.getCoreRequest());
}
@Override
public String getRemoteUser() {
Principal p = getUserPrincipal();
if (p == null)
return null;
return p.getName();
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
if (path == null || _context == null)
return null;
// handle relative path
if (!path.startsWith("/")) {
String relTo = _pathInContext;
int slash = relTo.lastIndexOf("/");
if (slash > 1)
relTo = relTo.substring(0, slash + 1);
else
relTo = "/";
path = URIUtil.addPaths(relTo, path);
}
return _context.getRequestDispatcher(path);
}
@Override
public String getRequestedSessionId() {
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
return requestedSession == null ? null : requestedSession.sessionId();
}
@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();
}
@Override
public StringBuffer getRequestURL() {
final StringBuffer url = new StringBuffer(128);
URIUtil.appendSchemeHostPort(url, getScheme(), getServerName(), getServerPort());
String path = getRequestURI();
if (path != null)
url.append(path);
return url;
}
public Response getResponse() {
return _channel.getResponse();
}
/**
* Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
* path.
*
* Because this method returns a StringBuffer
, not a string, you can modify the URL easily, for example, to append path and query parameters.
*
* This method is useful for creating redirect messages and for reporting errors.
*
* @return "scheme://host:port"
*/
public StringBuilder getRootURL() {
return new StringBuilder(HttpURI.from(getScheme(), getServerName(), getServerPort(), null).asString());
}
@Override
public String getScheme() {
return _uri == null ? "http" : _uri.getScheme();
}
@Override
public String getServerName() {
if ((_uri != null) && StringUtil.isNotBlank(_uri.getAuthority()))
return HostPort.normalizeHost(_uri.getHost());
else
return findServerName();
}
private String findServerName() {
if (_channel != null) {
HostPort serverAuthority = _channel.getServerAuthority();
if (serverAuthority != null)
return HostPort.normalizeHost(serverAuthority.getHost());
}
// Return host from connection
String name = getLocalName();
if (name != null)
return HostPort.normalizeHost(name);
// not allowed to be null
return "";
}
@Override
public int getServerPort() {
return org.eclipse.jetty.server.Request.getServerPort(getCoreRequest());
}
@Override
public ServletContext getServletContext() {
//TODO cross context include must return the context of the originating request
return _context;
}
public String getServletName() {
if (_scope != null)
return _scope.getName();
return null;
}
@Override
public String getServletPath() {
// The servletPath returned is normally for the current servlet. Except during an
// INCLUDE dispatch, in which case this method returns the servletPath of the source servlet,
// which we recover from the IncludeAttributes wrapper.
ServletPathMapping mapping = findServletPathMapping();
return mapping == null ? "" : mapping.getServletPath();
}
public ServletResponse getServletResponse() {
return _channel.getResponse();
}
@Override
public String changeSessionId() {
String newId = _coreRequest.changeSessionId();
if (newId != null && getRemoteUser() != null)
_coreRequest.getManagedSession().setAttribute(ManagedSession.SESSION_CREATED_SECURE, Boolean.TRUE);
return newId;
}
/**
* Called when the request is fully finished being handled.
* For every session in any context that the session has
* accessed, ensure that the session is completed.
*/
public void onCompleted() {
// Clean up unread content.
_input.consumeAll();
// Clean up any tmp files created by MultiPartInputStream.
if (_multiParts != null) {
try {
if (!_crossContextDispatchSupported || !_coreRequest.getContext().isCrossContextDispatch(_coreRequest))
_multiParts.deleteParts();
} catch (Throwable e) {
LOG.warn("Errors deleting multipart tmp files", e);
}
}
}
/**
* Find a session that this request has already entered for the
* given SessionHandler
*
* @param sessionManager the SessionHandler (ie context) to check
* @return the session for the passed session handler or null
*/
public HttpSession getSession(SessionManager sessionManager) {
ManagedSession managedSession = _coreRequest.getManagedSession();
if (managedSession != null && managedSession.getSessionManager() == sessionManager)
return managedSession.getApi();
return null;
}
@Override
public HttpSession getSession() {
return getSession(true);
}
@Override
public HttpSession getSession(boolean create) {
Session session = _coreRequest.getSession(create);
if (session != null && session.isNew() && getAuthentication() instanceof Authentication.User)
session.setAttribute(ManagedSession.SESSION_CREATED_SECURE, Boolean.TRUE);
return session == null ? null : session.getApi();
}
/**
* Get Request TimeStamp
*
* @return The time that the request was received.
*/
public long getTimeStamp() {
return _timeStamp;
}
public HttpURI getHttpURI() {
return _uri;
}
public void setHttpURI(HttpURI uri) {
if (_uri != null && !Objects.equals(_uri.getQuery(), uri.getQuery()) && _queryParameters != BAD_PARAMS)
_parameters = _queryParameters = null;
_uri = uri.asImmutable();
}
/**
* @return Returns the original uri passed in metadata before customization/rewrite
*/
public String getOriginalURI() {
MetaData.Request metadata = _metaData;
if (metadata == null)
return null;
HttpURI uri = metadata.getHttpURI();
if (uri == null)
return null;
return uri.isAbsolute() && metadata.getHttpVersion() == HttpVersion.HTTP_2 ? uri.getPathQuery() : uri.toString();
}
public UserIdentity getUserIdentity() {
if (_authentication instanceof Authentication.Deferred)
setAuthentication(((Authentication.Deferred) _authentication).authenticate(this));
if (_authentication instanceof Authentication.User)
return ((Authentication.User) _authentication).getUserIdentity();
return null;
}
/**
* @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
* {@link Authentication.Deferred}).
*/
public UserIdentity getResolvedUserIdentity() {
if (_authentication instanceof Authentication.User)
return ((Authentication.User) _authentication).getUserIdentity();
return null;
}
public UserIdentityScope getUserIdentityScope() {
return _scope;
}
@Override
public Principal getUserPrincipal() {
if (_authentication instanceof Authentication.Deferred)
setAuthentication(((Authentication.Deferred) _authentication).authenticate(this));
if (_authentication instanceof Authentication.User) {
UserIdentity user = ((Authentication.User) _authentication).getUserIdentity();
return user.getUserPrincipal();
}
return null;
}
public boolean isHandled() {
return _handled;
}
@Override
public boolean isAsyncStarted() {
return getHttpChannelState().isAsyncStarted();
}
@Override
public boolean isAsyncSupported() {
return _asyncNotSupportedSource == null;
}
@Override
public boolean isRequestedSessionIdFromCookie() {
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.sessionIdFromCookie();
}
@Override
@Deprecated(since = "Servlet API 2.1")
public boolean isRequestedSessionIdFromUrl() {
return isRequestedSessionIdFromURL();
}
@Override
public boolean isRequestedSessionIdFromURL() {
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
}
@Override
public boolean isRequestedSessionIdValid() {
AbstractSessionManager.RequestedSession requestedSession = _coreRequest.getRequestedSession();
SessionManager sessionManager = _coreRequest.getSessionManager();
ManagedSession managedSession = _coreRequest.getManagedSession();
return requestedSession != null && sessionManager != null && managedSession != null && requestedSession.sessionId() != null && requestedSession.session() != null && requestedSession.session().isValid() && sessionManager.getSessionIdManager().getId(requestedSession.sessionId()).equals(managedSession.getId());
}
@Override
public boolean isSecure() {
return _secure;
}
public void setSecure(boolean secure) {
_secure = secure;
}
/**
*
Get the nanoTime at which the request arrived to a connector, obtained via {@link System#nanoTime()}.
* This method can be used when measuring latencies.
* @return The nanoTime at which the request was received/created in nanoseconds
*/
public long getBeginNanoTime() {
return _metaData.getBeginNanoTime();
}
@Override
public boolean isUserInRole(String role) {
if (_authentication instanceof Authentication.Deferred)
setAuthentication(((Authentication.Deferred) _authentication).authenticate(this));
if (_authentication instanceof Authentication.User)
return ((Authentication.User) _authentication).isUserInRole(_scope, role);
return false;
}
void onRequest(ContextHandler.CoreContextRequest coreRequest) {
_input.reopen();
_channel.getResponse().getHttpOutput().reopen();
_coreRequest = coreRequest;
setTimeStamp(org.eclipse.jetty.server.Request.getTimeStamp(coreRequest));
_metaData = new MetaData.Request(coreRequest.getBeginNanoTime(), coreRequest.getMethod(), coreRequest.getHttpURI(), coreRequest.getConnectionMetaData().getHttpVersion(), coreRequest.getHeaders());
_attributes = new ServletAttributes(coreRequest);
_method = coreRequest.getMethod();
_uri = coreRequest.getHttpURI();
_httpFields = coreRequest.getHeaders();
// This is further modified inside ContextHandler.doScope().
_pathInContext = URIUtil.decodePath(coreRequest.getHttpURI().getCanonicalPath());
setSecure(coreRequest.isSecure());
}
public ContextHandler.CoreContextRequest getCoreRequest() {
return _coreRequest;
}
public MetaData.Request getMetaData() {
return _metaData;
}
public boolean hasMetaData() {
return _metaData != null;
}
protected void recycle() {
if (_reader != null && _inputState == INPUT_READER) {
try {
int r = _reader.read();
while (r != -1) {
r = _reader.read();
}
} catch (Exception e) {
LOG.trace("IGNORED", e);
_reader = null;
_readerEncoding = null;
}
}
getHttpChannelState().recycle();
_requestAttributeListeners.clear();
_input.recycle();
_coreRequest = null;
_metaData = null;
_httpFields = null;
_trailers = null;
_uri = null;
_method = null;
_pathInContext = null;
_servletPathMapping = null;
_asyncNotSupportedSource = null;
_secure = false;
_handled = false;
_attributes = null;
_authentication = Authentication.NOT_CHECKED;
_contentType = null;
_characterEncoding = null;
_dispatcherType = null;
_inputState = INPUT_NONE;
// _reader can be reused
// _readerEncoding can be reused
_queryParameters = null;
_contentParameters = null;
_parameters = null;
_queryEncoding = null;
_scope = null;
_timeStamp = 0;
_multiParts = null;
if (_async != null)
_async.reset();
_async = null;
}
@Override
public void removeAttribute(String name) {
Object oldValue = _attributes == null ? null : _attributes.getAttribute(name);
if (_attributes != null)
_attributes.removeAttribute(name);
if (oldValue != null && !_requestAttributeListeners.isEmpty()) {
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context, this, name, oldValue);
for (ServletRequestAttributeListener listener : _requestAttributeListeners) {
listener.attributeRemoved(event);
}
}
}
public void removeEventListener(final EventListener listener) {
_requestAttributeListeners.remove(listener);
}
public void setAsyncSupported(boolean supported, Object source) {
_asyncNotSupportedSource = supported ? null : (source == null ? "unknown" : source);
}
/**
* Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
* {@link #setQueryEncoding}.
*
* @see ServletRequest#setAttribute(String, Object)
*/
@Override
public void setAttribute(String name, Object value) {
if (_attributes == null)
return;
// TODO are these still needed?
switch(name) {
case "org.eclipse.jetty.server.Request.queryEncoding" ->
setQueryEncoding(value == null ? null : value.toString());
case "org.eclipse.jetty.server.sendContent" ->
LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
}
Object oldValue = _attributes.setAttribute(name, value);
if (!_requestAttributeListeners.isEmpty()) {
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context, this, name, oldValue == null ? value : oldValue);
for (ServletRequestAttributeListener l : _requestAttributeListeners) {
if (oldValue == null)
l.attributeAdded(event);
else if (value == null)
l.attributeRemoved(event);
else
l.attributeReplaced(event);
}
}
}
/**
* Set the attributes for the request.
*
* @param attributes The attributes, which must be a {@link Attributes.Wrapper}
* for which {@link Attributes#unwrap(Attributes)} will return the
* original {@link ServletAttributes}.
*/
public void setAttributes(Attributes attributes) {
_attributes = attributes;
}
public void setAsyncAttributes() {
// Return if we have been async dispatched before.
if (getAttribute(AsyncContext.ASYNC_REQUEST_URI) != null)
return;
// Unwrap the _attributes to get the base attributes instance.
Attributes baseAttributes = Attributes.unwrap(_attributes);
// We cannot use a apply AsyncAttribute via #setAttributes as that
// will wrap over any dispatch specific attribute wrappers (eg.
// Dispatcher#ForwardAttributes). Async attributes must persist
// after the current dispatch, so they must be set under any other
// wrappers.
String fwdRequestURI = (String) getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
if (fwdRequestURI == null) {
if (baseAttributes instanceof ServletAttributes) {
// The baseAttributes map is our ServletAttributes, so we can set the async
// attributes there, under any other wrappers.
((ServletAttributes) baseAttributes).setAsyncAttributes(getRequestURI(), getContextPath(), getPathInContext(), getServletPathMapping(), getQueryString());
} else {
// We cannot find our ServletAttributes instance, so just set directly and hope
// whatever non jetty wrappers that have been applied will do the right thing.
_attributes.setAttribute(AsyncContext.ASYNC_REQUEST_URI, getRequestURI());
_attributes.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, getContextPath());
_attributes.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, getServletPath());
_attributes.setAttribute(AsyncContext.ASYNC_PATH_INFO, getPathInfo());
_attributes.setAttribute(AsyncContext.ASYNC_QUERY_STRING, getQueryString());
_attributes.setAttribute(AsyncContext.ASYNC_MAPPING, getHttpServletMapping());
}
} else {
if (baseAttributes instanceof ServletAttributes) {
// The baseAttributes map is our ServletAttributes, so we can set the async
// attributes there, under any other wrappers.
((ServletAttributes) baseAttributes).setAsyncAttributes(fwdRequestURI, (String) getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH), (String) getAttribute(RequestDispatcher.FORWARD_PATH_INFO), (ServletPathMapping) getAttribute(RequestDispatcher.FORWARD_MAPPING), (String) getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
} else {
// We cannot find our ServletAttributes instance, so just set directly and hope
// whatever non jetty wrappers that have been applied will do the right thing.
_attributes.setAttribute(AsyncContext.ASYNC_REQUEST_URI, fwdRequestURI);
_attributes.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH, getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
_attributes.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
_attributes.setAttribute(AsyncContext.ASYNC_PATH_INFO, getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
_attributes.setAttribute(AsyncContext.ASYNC_QUERY_STRING, getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
_attributes.setAttribute(AsyncContext.ASYNC_MAPPING, getAttribute(RequestDispatcher.FORWARD_MAPPING));
}
}
}
/**
* Set the authentication.
*
* @param authentication the authentication to set
*/
public void setAuthentication(Authentication authentication) {
_authentication = authentication;
if (_coreRequest != null)
org.eclipse.jetty.server.Request.setAuthenticationState(_coreRequest, authentication);
}
@Override
public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException {
if (_inputState != INPUT_NONE)
return;
MimeTypes.getKnownCharset(encoding);
_characterEncoding = encoding;
}
/*
* @see jakarta.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
*/
public void setCharacterEncodingUnchecked(String encoding) {
_characterEncoding = encoding;
}
/*
* @see jakarta.servlet.ServletRequest#getContentType()
*/
public void setContentType(String contentType) {
_contentType = contentType;
}
public void setDispatcherType(DispatcherType type) {
_dispatcherType = type;
}
public void setHandled(boolean h) {
_handled = h;
}
/**
* @param method The method to set.
*/
public void setMethod(String method) {
_method = method;
}
public boolean isHead() {
return HttpMethod.HEAD.is(getMethod());
}
/**
* Set the character encoding used for the query string. This call will effect the return of getQueryString and getParameters. It must be called before any
* getParameter methods.
*
* The request attribute "org.eclipse.jetty.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
*
* @param queryEncoding the URI query character encoding
*/
public void setQueryEncoding(String queryEncoding) {
_queryEncoding = Charset.forName(queryEncoding);
}
public void setTimeStamp(long ts) {
_timeStamp = ts;
}
public void setUserIdentityScope(UserIdentityScope scope) {
_scope = scope;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
if (_asyncNotSupportedSource != null)
throw new IllegalStateException("!asyncSupported: " + _asyncNotSupportedSource);
return forceStartAsync();
}
private AsyncContextState forceStartAsync() {
HttpChannelState state = getHttpChannelState();
if (_async == null)
_async = new AsyncContextState(state);
AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, this, getResponse());
state.startAsync(event);
return _async;
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
if (_asyncNotSupportedSource != null)
throw new IllegalStateException("!asyncSupported: " + _asyncNotSupportedSource);
HttpChannelState state = getHttpChannelState();
if (_async == null)
_async = new AsyncContextState(state);
AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse, getHttpURI());
event.setDispatchContext(getServletContext());
state.startAsync(event);
return _async;
}
public static HttpServletRequest unwrap(ServletRequest servletRequest) {
if (servletRequest instanceof HttpServletRequestWrapper) {
return (HttpServletRequestWrapper) servletRequest;
}
if (servletRequest instanceof ServletRequestWrapper) {
return unwrap(((ServletRequestWrapper) servletRequest).getRequest());
}
return ((HttpServletRequest) servletRequest);
}
@Override
public String toString() {
return String.format("%s%s%s %s%s@%x", getClass().getSimpleName(), _handled ? "[" : "(", getMethod(), getHttpURI(), _handled ? "]" : ")", hashCode());
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
//if already authenticated, return true
if (getUserPrincipal() != null && getRemoteUser() != null && getAuthType() != null)
return true;
//do the authentication
if (_authentication instanceof Authentication.Deferred) {
setAuthentication(((Authentication.Deferred) _authentication).authenticate(this, response));
}
//if the authentication did not succeed
if (_authentication instanceof Authentication.Deferred)
response.sendError(HttpStatus.UNAUTHORIZED_401);
//if the authentication is incomplete, return false
if (!(_authentication instanceof Authentication.ResponseSent))
return false;
//something has gone wrong
throw new ServletException("Authentication failed");
}
@Override
public Part getPart(String name) throws IOException, ServletException {
getParts();
return _multiParts.getPart(name);
}
@Override
public Collection getParts() throws IOException, ServletException {
String contentType = getContentType();
if (contentType == null || !MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpField.getValueParameters(contentType, null)))
throw new ServletException("Unsupported Content-Type [" + contentType + "], expected [multipart/form-data]");
return getParts(null);
}
private Collection 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");
int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE;
int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS;
if (_context != null) {
ContextHandler contextHandler = _context.getContextHandler();
maxFormContentSize = contextHandler.getMaxFormContentSize();
maxFormKeys = contextHandler.getMaxFormKeys();
} else {
maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize);
maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys);
}
MultiPartCompliance multiPartCompliance = getHttpChannel().getHttpConfiguration().getMultiPartCompliance();
_multiParts = newMultiParts(multiPartCompliance, config, maxFormKeys);
Collection parts;
try {
parts = _multiParts.getParts();
} catch (BadMessageException e) {
throw e;
}// Catch RuntimeException to handle IllegalStateException, IllegalArgumentException, CharacterEncodingException, etc .. (long list)
catch (RuntimeException | IOException e) {
throw new BadMessageException("Unable to parse form content", e);
}
reportComplianceViolations();
String formCharset = null;
Part charsetPart = _multiParts.getPart("_charset_");
if (charsetPart != null) {
try (InputStream is = charsetPart.getInputStream()) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
IO.copy(is, os);
formCharset = os.toString(StandardCharsets.UTF_8);
}
}
/*
Select Charset to use for this part. (NOTE: charset behavior is for the part value only and not the part header/field names)
1. Use the part specific charset as provided in that part's Content-Type header; else
2. Use the overall default charset. Determined by:
a. if part name _charset_ exists, use that part's value.
b. if the request.getCharacterEncoding() returns a value, use that.
(note, this can be either from the charset field on the request Content-Type
header, or from a manual call to request.setCharacterEncoding())
c. use utf-8.
*/
Charset defaultCharset;
if (formCharset != null)
defaultCharset = Charset.forName(formCharset);
else if (getCharacterEncoding() != null)
defaultCharset = Charset.forName(getCharacterEncoding());
else
defaultCharset = StandardCharsets.UTF_8;
long formContentSize = 0;
ByteArrayOutputStream os = null;
for (Part p : parts) {
if (p.getSubmittedFileName() == null) {
formContentSize = Math.addExact(formContentSize, p.getSize());
if (maxFormContentSize >= 0 && formContentSize > maxFormContentSize)
throw new IllegalStateException("Form is larger than max length " + maxFormContentSize);
// Servlet Spec 3.0 pg 23, parts without filename must be put into params.
String charset = null;
if (p.getContentType() != null)
charset = MimeTypes.getCharsetFromContentType(p.getContentType());
try (InputStream is = p.getInputStream()) {
if (os == null)
os = new ByteArrayOutputStream();
IO.copy(is, os);
String content = os.toString(charset == null ? defaultCharset : Charset.forName(charset));
if (_contentParameters == null)
_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();
}
private void reportComplianceViolations() {
ComplianceViolation.Listener complianceViolationListener = org.eclipse.jetty.server.HttpChannel.from(getCoreRequest()).getComplianceViolationListener();
List nonComplianceWarnings = _multiParts.getNonComplianceWarnings();
for (ComplianceViolation.Event nc : nonComplianceWarnings) complianceViolationListener.onComplianceViolation(new ComplianceViolation.Event(nc.mode(), nc.violation(), nc.details()));
}
private MultiPart.Parser newMultiParts(MultiPartCompliance multiPartCompliance, MultipartConfigElement config, int maxParts) throws IOException {
File contextTmpDir = (_context != null ? (File) _context.getAttribute(ServletContext.TEMPDIR) : null);
return MultiPart.newFormDataParser(multiPartCompliance, getInputStream(), getContentType(), config, contextTmpDir, maxParts);
}
@Override
public void login(String username, String password) throws ServletException {
if (_authentication instanceof Authentication.LoginAuthentication) {
Authentication auth = ((Authentication.LoginAuthentication) _authentication).login(username, password, this);
if (auth == null)
throw new Authentication.Failed("Authentication failed for username '" + username + "'");
else
_authentication = auth;
} else {
throw new Authentication.Failed("Authenticated failed for username '" + username + "'. Already authenticated as " + _authentication);
}
}
@Override
public void logout() throws ServletException {
if (_authentication instanceof Authentication.LogoutAuthentication)
_authentication = ((Authentication.LogoutAuthentication) _authentication).logout(this);
}
public void mergeQueryParameters(String oldQuery, String newQuery) {
Fields newQueryParams = null;
// Have to assume ENCODING because we can't know otherwise.
if (newQuery != null) {
newQueryParams = new Fields(true);
UrlEncoded.decodeTo(newQuery, newQueryParams::add, UrlEncoded.ENCODING);
}
Fields oldQueryParams = _queryParameters;
if (oldQueryParams == null && oldQuery != null) {
oldQueryParams = new Fields(true);
try {
UrlEncoded.decodeTo(oldQuery, oldQueryParams::add, getQueryCharset());
} catch (Throwable th) {
_queryParameters = BAD_PARAMS;
throw new BadMessageException("Bad query encoding", th);
}
}
Fields mergedQueryParams;
if (newQueryParams == null || newQueryParams.getSize() == 0)
mergedQueryParams = oldQueryParams == null ? NO_PARAMS : oldQueryParams;
else if (oldQueryParams == null || oldQueryParams.getSize() == 0)
mergedQueryParams = newQueryParams;
else {
// Parameters values are accumulated.
mergedQueryParams = new Fields(newQueryParams);
mergedQueryParams.addAll(oldQueryParams);
}
setQueryFields(mergedQueryParams);
resetParameters();
}
@Override
public T upgrade(Class handlerClass) throws IOException, ServletException {
// Not implemented. Throw ServletException as per spec
throw new ServletException("Not implemented");
}
/**
* Set the servletPathMapping, the servletPath and the pathInfo.
* @param servletPathMapping The mapping used to return from {@link #getHttpServletMapping()}
*/
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;
}
/**
* @return The mapping for the current target servlet, regardless of dispatch type.
*/
public ServletPathMapping getServletPathMapping() {
return _servletPathMapping;
}
/**
* @return The mapping for the target servlet reported by the {@link #getServletPath()} and
* {@link #getPathInfo()} methods. For {@link DispatcherType#INCLUDE} dispatches, this
* method returns the mapping of the source servlet, otherwise it returns the mapping of
* the target servlet.
*/
ServletPathMapping findServletPathMapping() {
ServletPathMapping mapping;
if (_dispatcherType == DispatcherType.INCLUDE) {
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 {
mapping = _servletPathMapping;
}
return mapping;
}
@Override
public HttpServletMapping getHttpServletMapping() {
// TODO This is to pass the current TCK. This has been challenged in https://github.com/eclipse-ee4j/jakartaee-tck/issues/585
if (_dispatcherType == DispatcherType.ASYNC) {
ServletPathMapping async = (ServletPathMapping) getAttribute(AsyncContext.ASYNC_MAPPING);
if (async != null && "/DispatchServlet".equals(async.getServletPath()))
return async;
}
// The mapping returned is normally for the current servlet. Except during an
// INCLUDE dispatch, in which case this method returns the mapping of the source servlet,
// which we recover from the IncludeAttributes wrapper.
return findServletPathMapping();
}
}