org.eclipse.jetty.server.Request Maven / Gradle / Ivy
Show all versions of jetty-server 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.server;
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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
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 jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.ServletRequestWrapper;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpUpgradeHandler;
import jakarta.servlet.http.Part;
import jakarta.servlet.http.PushBuilder;
import jakarta.servlet.http.WebConnection;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
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.HttpScheme;
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.UriCompliance;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
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;
import static org.eclipse.jetty.http.HttpCompliance.Violation.MISMATCHED_AUTHORITY;
/**
* Jetty Request.
*
* Implements {@link jakarta.servlet.http.HttpServletRequest} from the jakarta.servlet.http
package.
*
*
* The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
* request object to be as lightweight as possible and not actually implement any significant behavior. For example
*
*
* - the {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
* {@link Request#getPathInfo()} with a context path and calls {@link Request#setContext(Context,String)} as a result. For
* some dispatch types (ie include and named dispatch) the context path may not reflect the {@link ServletContext} set
* by {@link Request#setContext(Context, String)}.
*
* - the HTTP session methods will all return null sessions until such time as a request has been passed to a
* {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.
*
* - The {@link Request#getServletPath()} method will return "" until the request has been passed to a
org.eclipse.jetty.servlet.ServletHandler
* and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPathMapping(ServletPathMapping)} called as a result.
*
*
*
* A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
* An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
*
*
* Request instances are recycled, which combined with badly written asynchronous applications can result in calls on requests that have been reset.
* The code is written in a style to avoid NPE and ISE when such calls are made, as this has often proved generate exceptions that distraction
* from debugging such bad asynchronous applications. Instead, request methods attempt to not fail when called in an illegal state, so that hopefully
* the bad application will proceed to a major state event (eg calling AsyncContext.onComplete) which has better asynchronous guards, true atomic state
* and better failure behaviour that will assist in debugging.
*
*
* The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
* {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
* attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
* "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
*
* If IOExceptions or timeouts occur while reading form parameters, these are thrown as unchecked Exceptions: ether {@link RuntimeIOException},
* {@link BadMessageException} or {@link RuntimeException} as appropriate.
*/
public class Request implements HttpServletRequest
{
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
private static final Logger LOG = LoggerFactory.getLogger(Request.class);
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 MultiMap NO_PARAMS = new MultiMap<>();
private static final MultiMap BAD_PARAMS = new MultiMap<>();
/**
* 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(MultiMap 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 final List _requestAttributeListeners = new ArrayList<>();
private final HttpInput _input;
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 _newContext;
private boolean _cookiesExtracted = false;
private boolean _handled = false;
private boolean _contentParamsExtracted;
private boolean _requestedSessionIdFromCookie = false;
private Attributes _attributes;
private Authentication _authentication;
private String _contentType;
private String _characterEncoding;
private ContextHandler.Context _context;
private ContextHandler.Context _errorContext;
private Cookies _cookies;
private DispatcherType _dispatcherType;
private int _inputState = INPUT_NONE;
private BufferedReader _reader;
private String _readerEncoding;
private MultiMap _queryParameters;
private MultiMap _contentParameters;
private MultiMap _parameters;
private Charset _queryEncoding;
private InetSocketAddress _remote;
private String _requestedSessionId;
private UserIdentity.Scope _scope;
private HttpSession _session;
private SessionHandler _sessionHandler;
private long _timeStamp;
private MultiParts _multiParts; //if the request is a multi-part mime
private AsyncContextState _async;
private List _sessions; //list of sessions used during lifetime of request
public Request(HttpChannel channel, HttpInput input)
{
_channel = channel;
_input = input;
}
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.getName().toLowerCase();
String value = trailers.get(key);
trailers.put(key, value == null ? field.getValue() : value + "," + field.getValue());
}
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() && getHttpChannel().getHttpTransport().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();
if (session != null)
{
session.getLastAccessedTime(); // checks if session is valid
id = session.getId();
}
else
id = getRequestedSessionId();
}
catch (IllegalStateException e)
{
id = getRequestedSessionId();
}
Map cookies = new HashMap<>();
Cookie[] existingCookies = getCookies();
if (existingCookies != null)
{
for (Cookie c: getCookies())
{
cookies.put(c.getName(), c.getValue());
}
}
//Any Set-Cookies that were set on the response must be set as Cookies on the
//PushBuilder, unless the max-age of the cookie is <= 0
HttpFields responseFields = getResponse().getHttpFields();
for (HttpField field : responseFields)
{
HttpHeader header = field.getHeader();
if (header == HttpHeader.SET_COOKIE)
{
HttpCookie cookie = (field instanceof SetCookieHttpField)
? ((SetCookieHttpField)field).getHttpCookie()
: new HttpCookie(field.getValue());
if (cookie.getMaxAge() > 0)
cookies.put(cookie.getName(), cookie.getValue());
else
cookies.remove(cookie.getName());
}
}
if (!cookies.isEmpty())
{
StringBuilder buff = new StringBuilder();
for (Map.Entry entry : cookies.entrySet())
{
if (buff.length() > 0)
buff.append("; ");
buff.append(entry.getKey()).append('=').append(entry.getValue());
}
fields.add(new HttpField(HttpHeader.COOKIE, buff.toString()));
}
PushBuilder builder = new PushBuilderImpl(this, fields, getMethod(), getQueryString(), id);
builder.addHeader("referer", getRequestURL().toString());
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());
}
/**
* Remember a session that this request has just entered.
*
* @param s the session
*/
public void enterSession(HttpSession s)
{
if (!(s instanceof Session))
return;
if (_sessions == null)
_sessions = new ArrayList<>();
if (LOG.isDebugEnabled())
LOG.debug("Request {} entering session={}", this, s);
_sessions.add((Session)s);
}
/**
* Complete this request's access to a session.
*
* @param session the session
*/
private void leaveSession(Session session)
{
if (LOG.isDebugEnabled())
LOG.debug("Request {} leaving session {}", this, session);
//try and scope to a request and context before leaving the session
ServletContext ctx = session.getServletContext();
ContextHandler handler = ContextHandler.getContextHandler(ctx);
if (handler == null)
session.getSessionHandler().complete(session);
else
handler.handle(this, () -> session.getSessionHandler().complete(session));
}
/**
* A response is being committed for a session,
* potentially write the session out before the
* client receives the response.
*
* @param session the session
*/
private void commitSession(Session session)
{
if (LOG.isDebugEnabled())
LOG.debug("Response {} committing for session {}", this, session);
//try and scope to a request and context before committing the session
ServletContext ctx = session.getServletContext();
ContextHandler handler = ContextHandler.getContextHandler(ctx);
if (handler == null)
session.getSessionHandler().commit(session);
else
handler.handle(this, () -> session.getSessionHandler().commit(session));
}
private MultiMap getParameters()
{
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;
// 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.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 parameters = _parameters;
return parameters == null ? NO_PARAMS : parameters;
}
private void extractQueryParameters()
{
if (_uri == null || StringUtil.isEmpty(_uri.getQuery()))
_queryParameters = NO_PARAMS;
else
{
try
{
_queryParameters = new MultiMap<>();
UrlEncoded.decodeTo(_uri.getQuery(), _queryParameters, _queryEncoding);
}
catch (IllegalStateException | IllegalArgumentException e)
{
_queryParameters = BAD_PARAMS;
throw new BadMessageException("Unable to parse URI query", e);
}
}
}
private boolean isContentEncodingSupported()
{
String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING);
if (contentEncoding == null)
return true;
return HttpHeaderValue.IDENTITY.is(contentEncoding);
}
private void extractContentParameters()
{
String contentType = getContentType();
if (contentType == null || contentType.isEmpty())
_contentParameters = NO_PARAMS;
else
{
_contentParameters = new MultiMap<>();
int contentLength = getContentLength();
if (contentLength != 0 && _inputState == INPUT_NONE)
{
String baseType = HttpField.valueParameters(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 RuntimeIOException(msg, e);
}
}
}
}
}
public void extractFormParameters(MultiMap 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();
}
else
{
maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize);
maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys);
}
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, 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();
}
public ComplianceViolation.Listener getComplianceViolationListener()
{
if (_channel instanceof ComplianceViolation.Listener)
{
return (ComplianceViolation.Listener)_channel;
}
ComplianceViolation.Listener listener = _channel.getConnector().getBean(ComplianceViolation.Listener.class);
if (listener == null)
{
listener = _channel.getServer().getBean(ComplianceViolation.Listener.class);
}
return listener;
}
/**
* 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 jakarta.servlet.ServletRequest#getAttribute(java.lang.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 (HttpConnection.class.getName().equals(name) &&
_channel.getHttpTransport() instanceof HttpConnection)
return _channel.getHttpTransport();
}
return (_attributes == null) ? null : _attributes.getAttribute(name);
}
@Override
public Enumeration getAttributeNames()
{
if (_attributes == null)
return Collections.enumeration(Collections.emptyList());
return AttributesMap.getAttributeNamesCopy(_attributes);
}
public Attributes getAttributes()
{
if (_attributes == null)
_attributes = new ServletAttributes();
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.getFields().get(HttpHeader.CONTENT_TYPE);
}
return _contentType;
}
/**
* @return The current {@link Context context} used for this request, or null
if {@link #setContext} has not yet been called.
*/
public Context getContext()
{
return _context;
}
/**
* @return The current {@link Context context} used for this error handling for this request. If the request is asynchronous,
* then it is the context that called async. Otherwise it is the last non-null context passed to #setContext
*/
public Context getErrorContext()
{
if (isAsyncStarted())
{
ContextHandler handler = _channel.getState().getContextHandler();
if (handler != null)
return handler.getServletContext();
}
return _errorContext;
}
@Override
public String getContextPath()
{
// The context path returned is normally for the current context. Except during a cross context
// INCLUDE dispatch, in which case this method returns the context path of the source context,
// which we recover from the IncludeAttributes wrapper.
Context 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 in the context.
*
* 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.
* @see #setContext(Context, String)
*/
public String getPathInContext()
{
return _pathInContext;
}
@Override
public Cookie[] getCookies()
{
MetaData.Request metadata = _metaData;
if (metadata == null || _cookiesExtracted)
{
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
return _cookies.getCookies();
}
_cookiesExtracted = true;
for (HttpField field : metadata.getFields())
{
if (field.getHeader() == HttpHeader.COOKIE)
{
if (_cookies == null)
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance(), getComplianceViolationListener());
_cookies.addCookieField(field.getValue());
}
}
//Javadoc for Request.getCookies() stipulates null for no cookies
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
return _cookies.getCookies();
}
@Override
public long getDateHeader(String name)
{
HttpFields fields = _httpFields;
return fields == null ? -1 : fields.getDateField(name);
}
@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() : fields.getFieldNames();
}
@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;
if (_channel.isExpecting100Continue())
_channel.continue100(_input.available());
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 formatAddrOrHost(result);
}
return "";
}
@Override
public String getLocalName()
{
if (_channel != null)
{
String localName = _channel.getLocalName();
return formatAddrOrHost(localName);
}
return ""; // not allowed to be null
}
@Override
public int getLocalPort()
{
if (_channel != null)
{
int localPort = _channel.getLocalPort();
if (localPort > 0)
return localPort;
}
return 0;
}
@Override
public String getMethod()
{
return _method;
}
@Override
public String getParameter(String name)
{
return getParameters().getValue(name, 0);
}
@Override
public Map getParameterMap()
{
return Collections.unmodifiableMap(getParameters().toStringArrayMap());
}
@Override
public Enumeration getParameterNames()
{
return Collections.enumeration(getParameters().keySet());
}
@Override
public String[] getParameterValues(String name)
{
List vals = getParameters().getValues(name);
if (vals == null)
return null;
return vals.toArray(new String[0]);
}
public MultiMap getQueryParameters()
{
return _queryParameters;
}
public void setQueryParameters(MultiMap queryParameters)
{
_queryParameters = queryParameters;
}
public void setContentParameters(MultiMap contentParameters)
{
_contentParameters = 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()
{
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 = StringUtil.__ISO_8859_1;
if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
{
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();
}
};
}
else if (_channel.isExpecting100Continue())
{
_channel.continue100(_input.available());
}
_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()
{
InetSocketAddress remote = _remote;
if (remote == null)
remote = _channel.getRemoteAddress();
return remote;
}
@Override
public String getRemoteAddr()
{
InetSocketAddress remote = _remote;
if (remote == null)
remote = _channel.getRemoteAddress();
if (remote == null)
return "";
InetAddress address = remote.getAddress();
String result = address == null
? remote.getHostString()
: address.getHostAddress();
return formatAddrOrHost(result);
}
@Override
public String getRemoteHost()
{
InetSocketAddress remote = _remote;
if (remote == null)
remote = _channel.getRemoteAddress();
if (remote == null)
return "";
// We want the URI host, so add IPv6 brackets if necessary.
return formatAddrOrHost(remote.getHostString());
}
@Override
public int getRemotePort()
{
InetSocketAddress remote = _remote;
if (remote == null)
remote = _channel.getRemoteAddress();
return remote == null ? 0 : remote.getPort();
}
@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()
{
return _requestedSessionId;
}
@Override
public String getRequestURI()
{
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()
{
StringBuilder url = new StringBuilder(128);
URIUtil.appendSchemeHostPort(url, getScheme(), getServerName(), getServerPort());
return url;
}
@Override
public String getScheme()
{
return _uri == null ? "http" : _uri.getScheme();
}
@Override
public String getServerName()
{
if ((_uri != null) && StringUtil.isNotBlank(_uri.getAuthority()))
return formatAddrOrHost(_uri.getHost());
else
return findServerName();
}
private String findServerName()
{
if (_channel != null)
{
HostPort serverAuthority = _channel.getServerAuthority();
if (serverAuthority != null)
return formatAddrOrHost(serverAuthority.getHost());
}
// Return host from connection
String name = getLocalName();
if (name != null)
return formatAddrOrHost(name);
return ""; // not allowed to be null
}
@Override
public int getServerPort()
{
int port = -1;
if ((_uri != null) && StringUtil.isNotBlank(_uri.getAuthority()))
port = _uri.getPort();
else
port = findServerPort();
// If no port specified, return the default port for the scheme
if (port <= 0)
return HttpScheme.getDefaultPort(getScheme());
// return a specific port
return port;
}
private int findServerPort()
{
if (_channel != null)
{
HostPort serverAuthority = _channel.getServerAuthority();
if (serverAuthority != null)
return serverAuthority.getPort();
}
// Return host from connection
return getLocalPort();
}
@Override
public ServletContext getServletContext()
{
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()
{
HttpSession session = getSession(false);
if (session == null)
throw new IllegalStateException("No session");
if (session instanceof Session)
{
Session s = ((Session)session);
s.renewId(this);
if (getRemoteUser() != null)
s.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (s.isIdChanged() && _sessionHandler.isUsingCookies())
_channel.getResponse().replaceCookie(_sessionHandler.getSessionCookie(s, getContextPath(), isSecure()));
}
return session.getId();
}
/**
* 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()
{
HttpChannel httpChannel = getHttpChannel();
// httpChannel can be null in some scenarios
// it's not possible to use requestlog in those scenarios anyway.
if (httpChannel != null)
{
RequestLog requestLog = httpChannel.getRequestLog();
if (requestLog != null)
{
// Don't allow pulling more parameters from request body content
_contentParamsExtracted = true;
if (_contentParameters == null)
_contentParameters = NO_PARAMS;
// Reset the status code to what was committed
MetaData.Response committedResponse = getResponse().getCommittedMetaData();
if (committedResponse != null)
{
getResponse().setStatus(committedResponse.getStatus());
// TODO: Reset the response headers to what they were when committed
}
requestLog.log(this, getResponse());
}
}
if (_sessions != null)
{
for (Session s:_sessions)
leaveSession(s);
}
//Clean up any tmp files created by MultiPartInputStream
if (_multiParts != null)
{
try
{
_multiParts.close();
}
catch (Throwable e)
{
LOG.warn("Errors deleting multipart tmp files", e);
}
}
}
/**
* Called when a response is about to be committed, ie sent
* back to the client
*/
public void onResponseCommit()
{
if (_sessions != null)
{
for (Session s:_sessions)
{
commitSession(s);
}
}
}
/**
* Find a session that this request has already entered for the
* given SessionHandler
*
* @param sessionHandler the SessionHandler (ie context) to check
* @return the session for the passed session handler or null
*/
public HttpSession getSession(SessionHandler sessionHandler)
{
if (_sessions == null || _sessions.size() == 0 || sessionHandler == null)
return null;
HttpSession session = null;
for (HttpSession s:_sessions)
{
Session ss = (Session)s;
if (sessionHandler == ss.getSessionHandler())
{
session = s;
if (ss.isValid())
return session;
}
}
return session;
}
@Override
public HttpSession getSession()
{
return getSession(true);
}
@Override
public HttpSession getSession(boolean create)
{
if (_session != null)
{
if (_sessionHandler != null && !_sessionHandler.isValid(_session))
_session = null;
else
return _session;
}
if (!create)
return null;
if (getResponse().isCommitted())
throw new IllegalStateException("Response is committed");
if (_sessionHandler == null)
throw new IllegalStateException("No SessionManager");
_session = _sessionHandler.newHttpSession(this);
if (_session == null)
throw new IllegalStateException("Create session failed");
HttpCookie cookie = _sessionHandler.getSessionCookie(_session, getContextPath(), isSecure());
if (cookie != null)
_channel.getResponse().replaceCookie(cookie);
return _session;
}
/**
* @return Returns the sessionManager.
*/
public SessionHandler getSessionHandler()
{
return _sessionHandler;
}
/**
* 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.getURI();
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 UserIdentity.Scope 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()
{
return _requestedSessionId != null && _requestedSessionIdFromCookie;
}
@Override
@Deprecated(since = "Servlet API 2.1")
public boolean isRequestedSessionIdFromUrl()
{
return _requestedSessionId != null && !_requestedSessionIdFromCookie;
}
@Override
public boolean isRequestedSessionIdFromURL()
{
return _requestedSessionId != null && !_requestedSessionIdFromCookie;
}
@Override
public boolean isRequestedSessionIdValid()
{
if (_requestedSessionId == null)
return false;
HttpSession session = getSession(false);
return (session != null && _sessionHandler.getSessionIdManager().getId(_requestedSessionId).equals(_sessionHandler.getId(session)));
}
@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;
}
/**
* @param request the Request metadata
*/
public void setMetaData(MetaData.Request request)
{
if (_metaData == null && _input != null && _channel != null)
{
_input.reopen();
_channel.getResponse().getHttpOutput().reopen();
}
_metaData = request;
_method = request.getMethod();
_httpFields = request.getFields();
final HttpURI uri = request.getURI();
UriCompliance compliance = null;
if (uri.hasViolations())
{
compliance = _channel == null || _channel.getHttpConfiguration() == null ? null : _channel.getHttpConfiguration().getUriCompliance();
String badMessage = UriCompliance.checkUriCompliance(compliance, uri);
if (badMessage != null)
throw new BadMessageException(badMessage);
}
HttpField host = getHttpFields().getField(HttpHeader.HOST);
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
{
_uri = uri;
if (host instanceof HostPortHttpField && !((HostPortHttpField)host).getHostPort().toString().equals(uri.getAuthority()))
{
HttpChannel httpChannel = getHttpChannel();
HttpConfiguration httpConfiguration = httpChannel.getHttpConfiguration();
if (httpConfiguration != null)
{
HttpCompliance httpCompliance = httpConfiguration.getHttpCompliance();
if (httpCompliance.allows(MISMATCHED_AUTHORITY))
{
if (httpChannel instanceof ComplianceViolation.Listener)
((ComplianceViolation.Listener)httpChannel).onComplianceViolation(httpCompliance, MISMATCHED_AUTHORITY, _uri.toString());
}
else
{
throw new BadMessageException(400, "Mismatched Authority");
}
}
}
}
else
{
HttpURI.Mutable builder = HttpURI.build(uri);
if (!uri.isAbsolute())
builder.scheme(HttpScheme.HTTP.asString());
if (uri.getPath() == null)
builder.path("/");
if (!uri.hasAuthority())
{
if (host instanceof HostPortHttpField)
{
HostPortHttpField authority = (HostPortHttpField)host;
builder.host(authority.getHost()).port(authority.getPort());
}
else
{
builder.host(findServerName()).port(findServerPort());
}
}
_uri = builder.asImmutable();
}
setSecure(HttpScheme.HTTPS.is(_uri.getScheme()));
String encoded = _uri.getPath();
String path;
if (encoded == null)
// TODO this is not really right for CONNECT
path = _uri.isAbsolute() ? "/" : null;
else if (encoded.startsWith("/"))
{
path = (encoded.length() == 1) ? "/" : _uri.getDecodedPath();
}
else if ("*".equals(encoded) || HttpMethod.CONNECT.is(getMethod()))
{
path = encoded;
}
else
{
path = null;
}
if (path == null || path.isEmpty())
{
_pathInContext = encoded == null ? "" : encoded;
throw new BadMessageException(400, "Bad URI");
}
_pathInContext = path;
}
public org.eclipse.jetty.http.MetaData.Request getMetaData()
{
return _metaData;
}
public boolean hasMetaData()
{
return _metaData != null;
}
protected void recycle()
{
if (_context != null)
throw new IllegalStateException("Request in context!");
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();
_metaData = null;
_httpFields = null;
_trailers = null;
_uri = null;
_method = null;
_pathInContext = null;
_servletPathMapping = null;
_asyncNotSupportedSource = null;
_secure = false;
_newContext = false;
_cookiesExtracted = false;
_handled = false;
_contentParamsExtracted = false;
_requestedSessionIdFromCookie = false;
_attributes = Attributes.unwrap(_attributes);
if (_attributes != null)
{
if (ServletAttributes.class.equals(_attributes.getClass()))
_attributes.clearAttributes();
else
_attributes = null;
}
setAuthentication(Authentication.NOT_CHECKED);
_contentType = null;
_characterEncoding = null;
_context = null;
_errorContext = null;
if (_cookies != null)
_cookies.reset();
_dispatcherType = null;
_inputState = INPUT_NONE;
// _reader can be reused
// _readerEncoding can be reused
_queryParameters = null;
_contentParameters = null;
_parameters = null;
_queryEncoding = null;
_remote = null;
_requestedSessionId = null;
_scope = null;
_session = null;
_sessionHandler = null;
_timeStamp = 0;
_multiParts = null;
if (_async != null)
_async.reset();
_async = null;
_sessions = 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 jakarta.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
*/
@Override
public void setAttribute(String name, Object value)
{
Object oldValue = _attributes == null ? null : _attributes.getAttribute(name);
if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
setQueryEncoding(value == null ? null : value.toString());
else if ("org.eclipse.jetty.server.sendContent".equals(name))
LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
if (_attributes == null)
_attributes = new ServletAttributes();
_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 org.eclipse.jetty.util.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;
if (_attributes == null)
baseAttributes = _attributes = new ServletAttributes();
else
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;
}
@Override
public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
{
if (_inputState != INPUT_NONE)
return;
_characterEncoding = encoding;
// check encoding is supported
if (!StringUtil.isUTF8(encoding))
{
try
{
Charset.forName(encoding);
}
catch (UnsupportedCharsetException e)
{
throw new UnsupportedEncodingException(e.getMessage());
}
}
}
/*
* @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;
}
/**
* Set request context and path in the context.
*
* @param context context object
* @param pathInContext the part of the URI path that is withing the context.
* For servlets, this is equal to servletPath + pathInfo
*/
public void setContext(Context context, String pathInContext)
{
_newContext = _context != context;
_context = context;
_pathInContext = pathInContext;
if (context != null)
_errorContext = context;
}
/**
* @return True if this is the first call of takeNewContext()
since the last
* {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context, String)} call.
*/
public boolean takeNewContext()
{
boolean nc = _newContext;
_newContext = false;
return nc;
}
/**
* @param cookies The cookies to set.
*/
public void setCookies(Cookie[] cookies)
{
if (_cookies == null)
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance(), getComplianceViolationListener());
_cookies.setCookies(cookies);
}
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 getParamaters. 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);
}
/**
* @param addr The address to set.
*/
public void setRemoteAddr(InetSocketAddress addr)
{
_remote = addr;
}
/**
* @param requestedSessionId The requestedSessionId to set.
*/
public void setRequestedSessionId(String requestedSessionId)
{
_requestedSessionId = requestedSessionId;
}
/**
* @param requestedSessionIdCookie The requestedSessionIdCookie to set.
*/
public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
{
_requestedSessionIdFromCookie = requestedSessionIdCookie;
}
/**
* @param session The session to set.
*/
public void setSession(HttpSession session)
{
_session = session;
}
/**
* @param sessionHandler The SessionHandler to set.
*/
public void setSessionHandler(SessionHandler sessionHandler)
{
_sessionHandler = sessionHandler;
}
public void setTimeStamp(long ts)
{
_timeStamp = ts;
}
public void setUserIdentityScope(UserIdentity.Scope 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.valueParameters(contentType, null)))
throw new ServletException("Unsupported Content-Type [" + contentType + "], expected [multipart/form-data]");
return getParts(null);
}
private Collection getParts(MultiMap params) throws IOException
{
if (_multiParts == null)
{
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);
}
_multiParts = newMultiParts(config, maxFormKeys);
Collection parts = _multiParts.getParts();
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 MultiMap<>() : params;
_contentParameters.add(p.getName(), content);
}
os.reset();
}
}
}
return _multiParts.getParts();
}
private MultiParts newMultiParts(MultipartConfigElement config, int maxParts) throws IOException
{
MultiPartFormDataCompliance compliance = getHttpChannel().getHttpConfiguration().getMultipartFormDataCompliance();
if (LOG.isDebugEnabled())
LOG.debug("newMultiParts {} {}", compliance, this);
switch (compliance)
{
case RFC7578:
return new MultiParts.MultiPartsHttpParser(getInputStream(), getContentType(), config,
(_context != null ? (File)_context.getAttribute(ServletContext.TEMPDIR) : null), this, maxParts);
case LEGACY:
default:
return new MultiParts.MultiPartsUtilParser(getInputStream(), getContentType(), config,
(_context != null ? (File)_context.getAttribute(ServletContext.TEMPDIR) : null), this, 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)
{
MultiMap newQueryParams = null;
// Have to assume ENCODING because we can't know otherwise.
if (newQuery != null)
{
newQueryParams = new MultiMap<>();
UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING);
}
MultiMap oldQueryParams = _queryParameters;
if (oldQueryParams == null && oldQuery != null)
{
oldQueryParams = new MultiMap<>();
try
{
UrlEncoded.decodeTo(oldQuery, oldQueryParams, getQueryCharset());
}
catch (Throwable th)
{
_queryParameters = BAD_PARAMS;
throw new BadMessageException(400, "Bad query encoding", th);
}
}
MultiMap mergedQueryParams;
if (newQueryParams == null || newQueryParams.size() == 0)
mergedQueryParams = oldQueryParams == null ? NO_PARAMS : oldQueryParams;
else if (oldQueryParams == null || oldQueryParams.size() == 0)
mergedQueryParams = newQueryParams;
else
{
// Parameters values are accumulated.
mergedQueryParams = new MultiMap<>(newQueryParams);
mergedQueryParams.addAllValues(oldQueryParams);
}
setQueryParameters(mergedQueryParams);
resetParameters();
}
@Override
public T upgrade(Class handlerClass) throws IOException, ServletException
{
Response response = _channel.getResponse();
if (response.getStatus() != HttpStatus.SWITCHING_PROTOCOLS_101)
throw new IllegalStateException("Response status should be 101");
if (response.getHeader("Upgrade") == null)
throw new IllegalStateException("Missing Upgrade header");
if (!"Upgrade".equalsIgnoreCase(response.getHeader("Connection")))
throw new IllegalStateException("Invalid Connection header");
if (response.isCommitted())
throw new IllegalStateException("Cannot upgrade committed response");
if (_metaData == null || _metaData.getHttpVersion() != HttpVersion.HTTP_1_1)
throw new IllegalStateException("Only requests over HTTP/1.1 can be upgraded");
ServletOutputStream outputStream = response.getOutputStream();
ServletInputStream inputStream = getInputStream();
HttpChannelOverHttp httpChannel11 = (HttpChannelOverHttp)_channel;
HttpConnection httpConnection = (HttpConnection)_channel.getConnection();
T upgradeHandler;
try
{
upgradeHandler = handlerClass.getDeclaredConstructor().newInstance();
}
catch (Exception e)
{
throw new ServletException("Unable to instantiate handler class", e);
}
httpChannel11.servletUpgrade(); // tell the HTTP 1.1 channel that it is now handling an upgraded servlet
AsyncContext asyncContext = forceStartAsync(); // force the servlet in async mode
outputStream.flush(); // commit the 101 response
httpConnection.getGenerator().servletUpgrade(); // tell the generator it can send data as-is
httpConnection.addEventListener(new Connection.Listener()
{
@Override
public void onClosed(Connection connection)
{
try
{
asyncContext.complete();
}
catch (Exception e)
{
LOG.warn("error during upgrade AsyncContext complete", e);
}
try
{
upgradeHandler.destroy();
}
catch (Exception e)
{
LOG.warn("error during upgrade HttpUpgradeHandler destroy", e);
}
}
@Override
public void onOpened(Connection connection)
{
}
});
upgradeHandler.init(new WebConnection()
{
@Override
public void close() throws Exception
{
try
{
inputStream.close();
}
finally
{
outputStream.close();
}
}
@Override
public ServletInputStream getInputStream()
{
return inputStream;
}
@Override
public ServletOutputStream getOutputStream()
{
return outputStream;
}
});
return upgradeHandler;
}
/**
* Set the servletPathMapping, the servletPath and the pathInfo.
* @param servletPathMapping The mapping used to return from {@link #getHttpServletMapping()}
*/
public void setServletPathMapping(ServletPathMapping servletPathMapping)
{
_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)
{
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();
}
private String formatAddrOrHost(String name)
{
return _channel == null ? HostPort.normalizeHost(name) : _channel.formatAddrOrHost(name);
}
}