org.apache.coyote.http11.AbstractHttp11Protocol Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.coyote.http11;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpUpgradeHandler;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.CompressionConfig;
import org.apache.coyote.ContinueResponseTiming;
import org.apache.coyote.Processor;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.UpgradeToken;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.coyote.http11.upgrade.UpgradeGroupInfo;
import org.apache.coyote.http11.upgrade.UpgradeProcessorExternal;
import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal;
import org.apache.tomcat.util.buf.StringUtils;
import org.apache.tomcat.util.http.parser.HttpParser;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.modeler.Util;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.net.openssl.OpenSSLImplementation;
import org.apache.tomcat.util.res.StringManager;
public abstract class AbstractHttp11Protocol extends AbstractProtocol {
protected static final StringManager sm = StringManager.getManager(AbstractHttp11Protocol.class);
private final CompressionConfig compressionConfig = new CompressionConfig();
private HttpParser httpParser = null;
public AbstractHttp11Protocol(AbstractEndpoint endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
}
@Override
public void init() throws Exception {
// Upgrade protocols have to be configured first since the endpoint
// init (triggered via super.init() below) uses this list to configure
// the list of ALPN protocols to advertise
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
configureUpgradeProtocol(upgradeProtocol);
}
super.init();
// Set the Http11Protocol (i.e. this) for any upgrade protocols once
// this has completed initialisation as the upgrade protocols may expect this
// to be initialised when the call is made
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
upgradeProtocol.setHttp11Protocol(this);
}
httpParser = new HttpParser(relaxedPathChars, relaxedQueryChars);
}
@Override
public void destroy() throws Exception {
// There may be upgrade protocols with their own MBeans. These need to
// be de-registered.
ObjectName rgOname = getGlobalRequestProcessorMBeanName();
if (rgOname != null) {
Registry registry = Registry.getRegistry(null, null);
ObjectName query = new ObjectName(rgOname.getCanonicalName() + ",Upgrade=*");
Set upgrades = registry.getMBeanServer().queryMBeans(query, null);
for (ObjectInstance upgrade : upgrades) {
registry.unregisterComponent(upgrade.getObjectName());
}
}
super.destroy();
}
@Override
protected String getProtocolName() {
return "Http";
}
/**
* {@inheritDoc}
*
* Over-ridden here to make the method visible to nested classes.
*/
@Override
protected AbstractEndpoint getEndpoint() {
return super.getEndpoint();
}
public HttpParser getHttpParser() {
return httpParser;
}
// ------------------------------------------------ HTTP specific properties
// ------------------------------------------ managed in the ProtocolHandler
private ContinueResponseTiming continueResponseTiming = ContinueResponseTiming.IMMEDIATELY;
public String getContinueResponseTiming() {
return continueResponseTiming.toString();
}
public void setContinueResponseTiming(String continueResponseTiming) {
this.continueResponseTiming = ContinueResponseTiming.fromString(continueResponseTiming);
}
public ContinueResponseTiming getContinueResponseTimingInternal() {
return continueResponseTiming;
}
private boolean useKeepAliveResponseHeader = true;
public boolean getUseKeepAliveResponseHeader() {
return useKeepAliveResponseHeader;
}
public void setUseKeepAliveResponseHeader(boolean useKeepAliveResponseHeader) {
this.useKeepAliveResponseHeader = useKeepAliveResponseHeader;
}
private String relaxedPathChars = null;
public String getRelaxedPathChars() {
return relaxedPathChars;
}
public void setRelaxedPathChars(String relaxedPathChars) {
this.relaxedPathChars = relaxedPathChars;
}
private String relaxedQueryChars = null;
public String getRelaxedQueryChars() {
return relaxedQueryChars;
}
public void setRelaxedQueryChars(String relaxedQueryChars) {
this.relaxedQueryChars = relaxedQueryChars;
}
private int maxSavePostSize = 4 * 1024;
/**
* Return the maximum size of the post which will be saved during FORM or CLIENT-CERT authentication.
*
* @return The size in bytes
*/
public int getMaxSavePostSize() {
return maxSavePostSize;
}
/**
* Set the maximum size of a POST which will be buffered during FORM or CLIENT-CERT authentication. When a POST is
* received where the security constraints require a client certificate, the POST body needs to be buffered while an
* SSL handshake takes place to obtain the certificate. A similar buffering is required during FORM auth.
*
* @param maxSavePostSize The maximum size POST body to buffer in bytes
*/
public void setMaxSavePostSize(int maxSavePostSize) {
this.maxSavePostSize = maxSavePostSize;
}
/**
* Maximum size of the HTTP message header.
*/
private int maxHttpHeaderSize = 8 * 1024;
public int getMaxHttpHeaderSize() {
return maxHttpHeaderSize;
}
public void setMaxHttpHeaderSize(int valueI) {
maxHttpHeaderSize = valueI;
}
/**
* Maximum size of the HTTP request message header.
*/
private int maxHttpRequestHeaderSize = -1;
public int getMaxHttpRequestHeaderSize() {
return maxHttpRequestHeaderSize == -1 ? getMaxHttpHeaderSize() : maxHttpRequestHeaderSize;
}
public void setMaxHttpRequestHeaderSize(int valueI) {
maxHttpRequestHeaderSize = valueI;
}
/**
* Maximum size of the HTTP response message header.
*/
private int maxHttpResponseHeaderSize = -1;
public int getMaxHttpResponseHeaderSize() {
return maxHttpResponseHeaderSize == -1 ? getMaxHttpHeaderSize() : maxHttpResponseHeaderSize;
}
public void setMaxHttpResponseHeaderSize(int valueI) {
maxHttpResponseHeaderSize = valueI;
}
private int connectionUploadTimeout = 300000;
/**
* Specifies a different (usually longer) connection timeout during data upload. Default is 5 minutes as in Apache
* HTTPD server.
*
* @return The timeout in milliseconds
*/
public int getConnectionUploadTimeout() {
return connectionUploadTimeout;
}
/**
* Set the upload timeout.
*
* @param timeout Upload timeout in milliseconds
*/
public void setConnectionUploadTimeout(int timeout) {
connectionUploadTimeout = timeout;
}
private boolean disableUploadTimeout = true;
/**
* Get the flag that controls upload time-outs. If true, the connectionUploadTimeout will be ignored and the regular
* socket timeout will be used for the full duration of the connection.
*
* @return {@code true} if the separate upload timeout is disabled
*/
public boolean getDisableUploadTimeout() {
return disableUploadTimeout;
}
/**
* Set the flag to control whether a separate connection timeout is used during upload of a request body.
*
* @param isDisabled {@code true} if the separate upload timeout should be disabled
*/
public void setDisableUploadTimeout(boolean isDisabled) {
disableUploadTimeout = isDisabled;
}
public void setCompression(String compression) {
compressionConfig.setCompression(compression);
}
public String getCompression() {
return compressionConfig.getCompression();
}
protected int getCompressionLevel() {
return compressionConfig.getCompressionLevel();
}
public String getNoCompressionUserAgents() {
return compressionConfig.getNoCompressionUserAgents();
}
protected Pattern getNoCompressionUserAgentsPattern() {
return compressionConfig.getNoCompressionUserAgentsPattern();
}
public void setNoCompressionUserAgents(String noCompressionUserAgents) {
compressionConfig.setNoCompressionUserAgents(noCompressionUserAgents);
}
public String getCompressibleMimeType() {
return compressionConfig.getCompressibleMimeType();
}
public void setCompressibleMimeType(String valueS) {
compressionConfig.setCompressibleMimeType(valueS);
}
public String[] getCompressibleMimeTypes() {
return compressionConfig.getCompressibleMimeTypes();
}
public int getCompressionMinSize() {
return compressionConfig.getCompressionMinSize();
}
public void setCompressionMinSize(int compressionMinSize) {
compressionConfig.setCompressionMinSize(compressionMinSize);
}
public boolean useCompression(Request request, Response response) {
return compressionConfig.useCompression(request, response);
}
private Pattern restrictedUserAgents = null;
/**
* Get the string form of the regular expression that defines the User agents which should be restricted to HTTP/1.0
* support.
*
* @return The regular expression as a String
*/
public String getRestrictedUserAgents() {
if (restrictedUserAgents == null) {
return null;
} else {
return restrictedUserAgents.toString();
}
}
protected Pattern getRestrictedUserAgentsPattern() {
return restrictedUserAgents;
}
/**
* Set restricted user agent list (which will downgrade the connector to HTTP/1.0 mode). Regular expression as
* supported by {@link Pattern}.
*
* @param restrictedUserAgents The regular expression as supported by {@link Pattern} for the user agents e.g.
* "gorilla|desesplorer|tigrus"
*/
public void setRestrictedUserAgents(String restrictedUserAgents) {
if (restrictedUserAgents == null || restrictedUserAgents.length() == 0) {
this.restrictedUserAgents = null;
} else {
this.restrictedUserAgents = Pattern.compile(restrictedUserAgents);
}
}
private String server;
public String getServer() {
return server;
}
/**
* Set the server header name.
*
* @param server The new value to use for the server header
*/
public void setServer(String server) {
this.server = server;
}
private boolean serverRemoveAppProvidedValues = false;
/**
* Should application provider values for the HTTP Server header be removed. Note that if {@link #server} is set,
* any application provided value will be over-ridden.
*
* @return {@code true} if application provided values should be removed, otherwise {@code false}
*/
public boolean getServerRemoveAppProvidedValues() {
return serverRemoveAppProvidedValues;
}
public void setServerRemoveAppProvidedValues(boolean serverRemoveAppProvidedValues) {
this.serverRemoveAppProvidedValues = serverRemoveAppProvidedValues;
}
/**
* Maximum size of trailing headers in bytes
*/
private int maxTrailerSize = 8192;
public int getMaxTrailerSize() {
return maxTrailerSize;
}
public void setMaxTrailerSize(int maxTrailerSize) {
this.maxTrailerSize = maxTrailerSize;
}
/**
* Maximum size of extension information in chunked encoding
*/
private int maxExtensionSize = 8192;
public int getMaxExtensionSize() {
return maxExtensionSize;
}
public void setMaxExtensionSize(int maxExtensionSize) {
this.maxExtensionSize = maxExtensionSize;
}
/**
* Maximum amount of request body to swallow.
*/
private int maxSwallowSize = 2 * 1024 * 1024;
public int getMaxSwallowSize() {
return maxSwallowSize;
}
public void setMaxSwallowSize(int maxSwallowSize) {
this.maxSwallowSize = maxSwallowSize;
}
/**
* This field indicates if the protocol is treated as if it is secure. This normally means https is being used but
* can be used to fake https e.g behind a reverse proxy.
*/
private boolean secure;
public boolean getSecure() {
return secure;
}
public void setSecure(boolean b) {
secure = b;
}
/**
* The names of headers that are allowed to be sent via a trailer when using chunked encoding. They are stored in
* lower case.
*/
private Set allowedTrailerHeaders = ConcurrentHashMap.newKeySet();
public void setAllowedTrailerHeaders(String commaSeparatedHeaders) {
// Jump through some hoops so we don't end up with an empty set while
// doing updates.
Set toRemove = new HashSet<>(allowedTrailerHeaders);
if (commaSeparatedHeaders != null) {
String[] headers = commaSeparatedHeaders.split(",");
for (String header : headers) {
String trimmedHeader = header.trim().toLowerCase(Locale.ENGLISH);
if (toRemove.contains(trimmedHeader)) {
toRemove.remove(trimmedHeader);
} else {
allowedTrailerHeaders.add(trimmedHeader);
}
}
allowedTrailerHeaders.removeAll(toRemove);
}
}
protected Set getAllowedTrailerHeadersInternal() {
return allowedTrailerHeaders;
}
public boolean isTrailerHeaderAllowed(String headerName) {
return allowedTrailerHeaders.contains(headerName);
}
public String getAllowedTrailerHeaders() {
// Chances of a change during execution of this line are small enough
// that a sync is unnecessary.
List copy = new ArrayList<>(allowedTrailerHeaders);
return StringUtils.join(copy);
}
public void addAllowedTrailerHeader(String header) {
if (header != null) {
allowedTrailerHeaders.add(header.trim().toLowerCase(Locale.ENGLISH));
}
}
public void removeAllowedTrailerHeader(String header) {
if (header != null) {
allowedTrailerHeaders.remove(header.trim().toLowerCase(Locale.ENGLISH));
}
}
/**
* The upgrade protocol instances configured.
*/
private final List upgradeProtocols = new ArrayList<>();
@Override
public void addUpgradeProtocol(UpgradeProtocol upgradeProtocol) {
upgradeProtocols.add(upgradeProtocol);
}
@Override
public UpgradeProtocol[] findUpgradeProtocols() {
return upgradeProtocols.toArray(new UpgradeProtocol[0]);
}
/**
* The protocols that are available via internal Tomcat support for access via HTTP upgrade.
*/
private final Map httpUpgradeProtocols = new HashMap<>();
/**
* The protocols that are available via internal Tomcat support for access via ALPN negotiation.
*/
private final Map negotiatedProtocols = new HashMap<>();
private void configureUpgradeProtocol(UpgradeProtocol upgradeProtocol) {
// HTTP Upgrade
String httpUpgradeName = upgradeProtocol.getHttpUpgradeName(getEndpoint().isSSLEnabled());
boolean httpUpgradeConfigured = false;
if (httpUpgradeName != null && httpUpgradeName.length() > 0) {
httpUpgradeProtocols.put(httpUpgradeName, upgradeProtocol);
httpUpgradeConfigured = true;
getLog().info(sm.getString("abstractHttp11Protocol.httpUpgradeConfigured", getName(), httpUpgradeName));
}
// ALPN
String alpnName = upgradeProtocol.getAlpnName();
if (alpnName != null && alpnName.length() > 0) {
// ALPN is only available with TLS
if (getEndpoint().isSSLEnabled()) {
negotiatedProtocols.put(alpnName, upgradeProtocol);
getEndpoint().addNegotiatedProtocol(alpnName);
getLog().info(sm.getString("abstractHttp11Protocol.alpnConfigured", getName(), alpnName));
} else {
if (!httpUpgradeConfigured) {
// ALPN is not supported by this connector and the upgrade
// protocol implementation does not support standard HTTP
// upgrade so there is no way available to enable support
// for this protocol.
getLog().error(sm.getString("abstractHttp11Protocol.alpnWithNoAlpn",
upgradeProtocol.getClass().getName(), alpnName, getName()));
}
}
}
}
@Override
public UpgradeProtocol getNegotiatedProtocol(String negotiatedName) {
return negotiatedProtocols.get(negotiatedName);
}
@Override
public UpgradeProtocol getUpgradeProtocol(String upgradedName) {
return httpUpgradeProtocols.get(upgradedName);
}
/**
* Map of upgrade protocol name to {@link UpgradeGroupInfo} instance.
*
* HTTP upgrades via {@link HttpServletRequest#upgrade(Class)} do not have to depend on an {@code UpgradeProtocol}.
* To enable basic statistics to be made available for these protocols, a map of protocol name to
* {@link UpgradeGroupInfo} instances is maintained here.
*/
private final Map upgradeProtocolGroupInfos = new ConcurrentHashMap<>();
public UpgradeGroupInfo getUpgradeGroupInfo(String upgradeProtocol) {
if (upgradeProtocol == null) {
return null;
}
UpgradeGroupInfo result = upgradeProtocolGroupInfos.get(upgradeProtocol);
if (result == null) {
// Protecting against multiple JMX registration, not modification
// of the Map.
synchronized (upgradeProtocolGroupInfos) {
result = upgradeProtocolGroupInfos.get(upgradeProtocol);
if (result == null) {
result = new UpgradeGroupInfo();
upgradeProtocolGroupInfos.put(upgradeProtocol, result);
ObjectName oname = getONameForUpgrade(upgradeProtocol);
if (oname != null) {
try {
Registry.getRegistry(null, null).registerComponent(result, oname, null);
} catch (Exception e) {
getLog().warn(sm.getString("abstractHttp11Protocol.upgradeJmxRegistrationFail"), e);
result = null;
}
}
}
}
}
return result;
}
public ObjectName getONameForUpgrade(String upgradeProtocol) {
ObjectName oname = null;
ObjectName parentRgOname = getGlobalRequestProcessorMBeanName();
if (parentRgOname != null) {
StringBuilder name = new StringBuilder(parentRgOname.getCanonicalName());
name.append(",Upgrade=");
if (Util.objectNameValueNeedsQuote(upgradeProtocol)) {
name.append(ObjectName.quote(upgradeProtocol));
} else {
name.append(upgradeProtocol);
}
try {
oname = new ObjectName(name.toString());
} catch (Exception e) {
getLog().warn(sm.getString("abstractHttp11Protocol.upgradeJmxNameFail"), e);
}
}
return oname;
}
// ------------------------------------------------ HTTP specific properties
// ------------------------------------------ passed through to the EndPoint
public boolean isSSLEnabled() {
return getEndpoint().isSSLEnabled();
}
public void setSSLEnabled(boolean SSLEnabled) {
getEndpoint().setSSLEnabled(SSLEnabled);
}
public boolean getUseSendfile() {
return getEndpoint().getUseSendfile();
}
public void setUseSendfile(boolean useSendfile) {
getEndpoint().setUseSendfile(useSendfile);
}
/**
* @return The maximum number of requests which can be performed over a keep-alive connection. The default is the
* same as for Apache HTTP Server (100).
*/
public int getMaxKeepAliveRequests() {
return getEndpoint().getMaxKeepAliveRequests();
}
/**
* Set the maximum number of Keep-Alive requests to allow. This is to safeguard from DoS attacks. Setting to a
* negative value disables the limit.
*
* @param mkar The new maximum number of Keep-Alive requests allowed
*/
public void setMaxKeepAliveRequests(int mkar) {
getEndpoint().setMaxKeepAliveRequests(mkar);
}
// ----------------------------------------------- HTTPS specific properties
// ------------------------------------------ passed through to the EndPoint
public String getDefaultSSLHostConfigName() {
return getEndpoint().getDefaultSSLHostConfigName();
}
public void setDefaultSSLHostConfigName(String defaultSSLHostConfigName) {
getEndpoint().setDefaultSSLHostConfigName(defaultSSLHostConfigName);
}
@Override
public void addSslHostConfig(SSLHostConfig sslHostConfig) {
getEndpoint().addSslHostConfig(sslHostConfig);
}
@Override
public void addSslHostConfig(SSLHostConfig sslHostConfig, boolean replace) {
getEndpoint().addSslHostConfig(sslHostConfig, replace);
}
@Override
public SSLHostConfig[] findSslHostConfigs() {
return getEndpoint().findSslHostConfigs();
}
public void reloadSslHostConfigs() {
getEndpoint().reloadSslHostConfigs();
}
public void reloadSslHostConfig(String hostName) {
getEndpoint().reloadSslHostConfig(hostName);
}
protected String getSslImplementationShortName() {
if (OpenSSLImplementation.class.getName().equals(getSslImplementationName())) {
return "openssl";
}
if (getSslImplementationName() != null &&
getSslImplementationName().endsWith(".panama.OpenSSLImplementation")) {
return "opensslffm";
}
return "jsse";
}
public String getSslImplementationName() {
return getEndpoint().getSslImplementationName();
}
public void setSslImplementationName(String s) {
getEndpoint().setSslImplementationName(s);
}
public int getSniParseLimit() {
return getEndpoint().getSniParseLimit();
}
public void setSniParseLimit(int sniParseLimit) {
getEndpoint().setSniParseLimit(sniParseLimit);
}
// ------------------------------------------------------------- Common code
@Override
protected Processor createProcessor() {
Http11Processor processor = new Http11Processor(this, adapter);
return processor;
}
@Override
protected Processor createUpgradeProcessor(SocketWrapperBase> socket, UpgradeToken upgradeToken) {
HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler();
if (httpUpgradeHandler instanceof InternalHttpUpgradeHandler) {
return new UpgradeProcessorInternal(socket, upgradeToken, getUpgradeGroupInfo(upgradeToken.getProtocol()));
} else {
return new UpgradeProcessorExternal(socket, upgradeToken, getUpgradeGroupInfo(upgradeToken.getProtocol()));
}
}
}