Skip to content

Commit

Permalink
UNDERTOW-895 Improve MAX_PARAMETERS and MAX_HEADERS handling across a…
Browse files Browse the repository at this point in the history
…ll protocols
  • Loading branch information
stuartwdouglas committed Nov 24, 2016
1 parent 0b55a0b commit 9fbd987
Show file tree
Hide file tree
Showing 23 changed files with 440 additions and 267 deletions.
3 changes: 2 additions & 1 deletion core/src/main/java/io/undertow/UndertowMessages.java
Expand Up @@ -33,6 +33,7 @@
import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.server.handlers.builder.HandlerBuilder; import io.undertow.server.handlers.builder.HandlerBuilder;
import io.undertow.util.HttpString; import io.undertow.util.HttpString;
import io.undertow.util.ParameterLimitException;


/** /**
* @author Stuart Douglas * @author Stuart Douglas
Expand Down Expand Up @@ -172,7 +173,7 @@ public interface UndertowMessages {
IllegalStateException tooManyCookies(int maxCookies); IllegalStateException tooManyCookies(int maxCookies);


@Message(id = 47, value = "The number of parameters exceeded the maximum of %s") @Message(id = 47, value = "The number of parameters exceeded the maximum of %s")
IllegalStateException tooManyParameters(int maxValues); ParameterLimitException tooManyParameters(int maxValues);


@Message(id = 48, value = "No request is currently active") @Message(id = 48, value = "No request is currently active")
IllegalStateException noRequestActive(); IllegalStateException noRequestActive();
Expand Down
Expand Up @@ -79,13 +79,19 @@ class AjpClientExchange extends AbstractAttachable implements ClientExchange {


void terminateRequest() { void terminateRequest() {
state |= REQUEST_TERMINATED; state |= REQUEST_TERMINATED;
if(!clientConnection.isOpen()) {
state |= RESPONSE_TERMINATED;
}
if (anyAreSet(state, RESPONSE_TERMINATED)) { if (anyAreSet(state, RESPONSE_TERMINATED)) {
clientConnection.requestDone(); clientConnection.requestDone();
} }
} }


void terminateResponse() { void terminateResponse() {
state |= RESPONSE_TERMINATED; state |= RESPONSE_TERMINATED;
if(!clientConnection.isOpen()) {
state |= REQUEST_TERMINATED;
}
if (anyAreSet(state, REQUEST_TERMINATED)) { if (anyAreSet(state, REQUEST_TERMINATED)) {
clientConnection.requestDone(); clientConnection.requestDone();
} }
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -74,9 +74,6 @@ abstract class Http2HeaderBlockParser extends Http2PushBackParser implements Hpa


@Override @Override
protected void handleData(ByteBuffer resource, Http2FrameHeaderParser header) throws IOException { protected void handleData(ByteBuffer resource, Http2FrameHeaderParser header) throws IOException {
if(maxHeaders > 0 && headerMap.size() >= maxHeaders) {
throw new IOException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders));
}
boolean continuationFramesComing = Bits.anyAreClear(header.flags, Http2Channel.HEADERS_FLAG_END_HEADERS); boolean continuationFramesComing = Bits.anyAreClear(header.flags, Http2Channel.HEADERS_FLAG_END_HEADERS);
if (frameRemaining == -1) { if (frameRemaining == -1) {
frameRemaining = header.length; frameRemaining = header.length;
Expand Down Expand Up @@ -110,6 +107,10 @@ protected void handleData(ByteBuffer resource, Http2FrameHeaderParser header) th
} catch (HpackException e) { } catch (HpackException e) {
throw new ConnectionErrorException(e.getCloseCode(), e); throw new ConnectionErrorException(e.getCloseCode(), e);
} }

if(maxHeaders > 0 && headerMap.size() > maxHeaders) {
throw new StreamErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR);
}
if(oldLimit != -1) { if(oldLimit != -1) {
if(resource.remaining() == 0) { if(resource.remaining() == 0) {
int paddingInBuffer = oldLimit - resource.limit(); int paddingInBuffer = oldLimit - resource.limit();
Expand All @@ -135,7 +136,11 @@ HeaderMap getHeaderMap() {


@Override @Override
public void emitHeader(HttpString name, String value, boolean neverIndex) throws HpackException { public void emitHeader(HttpString name, String value, boolean neverIndex) throws HpackException {
if(maxHeaders > 0 && headerMap.size() > maxHeaders) {
return;
}
headerMap.add(name, value); headerMap.add(name, value);

if(name.length() == 0) { if(name.length() == 0) {
throw UndertowMessages.MESSAGES.invalidHeader(); throw UndertowMessages.MESSAGES.invalidHeader();
} }
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/java/io/undertow/server/Connectors.java
Expand Up @@ -23,6 +23,7 @@
import io.undertow.server.handlers.Cookie; import io.undertow.server.handlers.Cookie;
import io.undertow.util.DateUtils; import io.undertow.util.DateUtils;
import io.undertow.util.Headers; import io.undertow.util.Headers;
import io.undertow.util.ParameterLimitException;
import io.undertow.util.StatusCodes; import io.undertow.util.StatusCodes;
import io.undertow.util.URLUtils; import io.undertow.util.URLUtils;
import io.undertow.connector.PooledByteBuffer; import io.undertow.connector.PooledByteBuffer;
Expand Down Expand Up @@ -253,7 +254,11 @@ public static void executeRootHandler(final HttpHandler handler, final HttpServe
*/ */
@Deprecated @Deprecated
public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer) { public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer) {
setExchangeRequestPath(exchange, encodedPath, charset, decode, allowEncodedSlash, decodeBuffer, exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS)); try {
setExchangeRequestPath(exchange, encodedPath, charset, decode, allowEncodedSlash, decodeBuffer, exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS));
} catch (ParameterLimitException e) {
throw new RuntimeException(e);
}
} }
/** /**
* Sets the request path and query parameters, decoding to the requested charset. * Sets the request path and query parameters, decoding to the requested charset.
Expand All @@ -262,7 +267,7 @@ public static void setExchangeRequestPath(final HttpServerExchange exchange, fin
* @param encodedPath The encoded path * @param encodedPath The encoded path
* @param charset The charset * @param charset The charset
*/ */
public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer, int maxParameters) { public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException {
boolean requiresDecode = false; boolean requiresDecode = false;
for (int i = 0; i < encodedPath.length(); ++i) { for (int i = 0; i < encodedPath.length(); ++i) {
char c = encodedPath.charAt(i); char c = encodedPath.charAt(i);
Expand Down
Expand Up @@ -75,7 +75,7 @@ public void add(String name, String value, final HeaderMap headers) {
} }
values.add(new FormValueImpl(value, headers)); values.add(new FormValueImpl(value, headers));
if (++valueCount > maxValues) { if (++valueCount > maxValues) {
throw UndertowMessages.MESSAGES.tooManyParameters(maxValues); throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues));
} }
} }


Expand All @@ -86,10 +86,10 @@ public void add(String name, Path value, String fileName, final HeaderMap header
} }
values.add(new FormValueImpl(value, fileName, headers)); values.add(new FormValueImpl(value, fileName, headers));
if (values.size() > maxValues) { if (values.size() > maxValues) {
throw UndertowMessages.MESSAGES.tooManyParameters(maxValues); throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues));
} }
if (++valueCount > maxValues) { if (++valueCount > maxValues) {
throw UndertowMessages.MESSAGES.tooManyParameters(maxValues); throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues));
} }
} }


Expand All @@ -102,7 +102,7 @@ public void put(String name, String value, final HeaderMap headers) {
values.add(new FormValueImpl(value, headers)); values.add(new FormValueImpl(value, headers));


if (++valueCount > maxValues) { if (++valueCount > maxValues) {
throw UndertowMessages.MESSAGES.tooManyParameters(maxValues); throw new RuntimeException(UndertowMessages.MESSAGES.tooManyParameters(maxValues));
} }
} }


Expand Down
Expand Up @@ -365,6 +365,19 @@ protected Host findStickyHost(HttpServerExchange exchange) {
return null; return null;
} }


/**
* Should only be used for tests
*
* DO NOT CALL THIS METHOD WHEN REQUESTS ARE IN PROGRESS
*
* It is not thread safe so internal state can get messed up.
*/
public void closeCurrentConnections() {
for(Host host : hosts) {
host.closeCurrentConnections();
}
}

public final class Host extends ConnectionPoolErrorHandler.SimpleConnectionPoolErrorHandler implements ConnectionPoolManager { public final class Host extends ConnectionPoolErrorHandler.SimpleConnectionPoolErrorHandler implements ConnectionPoolManager {
final ProxyConnectionPool connectionPool; final ProxyConnectionPool connectionPool;
final String jvmRoute; final String jvmRoute;
Expand Down Expand Up @@ -411,6 +424,10 @@ public int getMaxQueueSize() {
public URI getUri() { public URI getUri() {
return uri; return uri;
} }

void closeCurrentConnections() {
connectionPool.closeCurrentConnections();
}
} }


private static class ExclusiveConnectionHolder { private static class ExclusiveConnectionHolder {
Expand Down
Expand Up @@ -60,7 +60,7 @@ public interface ProxyClient {
/** /**
* An opaque interface that may contain information about the proxy target * An opaque interface that may contain information about the proxy target
*/ */
public interface ProxyTarget { interface ProxyTarget {


} }
} }
Expand Up @@ -42,6 +42,7 @@
import java.net.URI; import java.net.URI;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -536,6 +537,21 @@ public void connect(ProxyClient.ProxyTarget proxyTarget, HttpServerExchange exch
} }
} }


/**
* Should only be used for tests.
*
*/
void closeCurrentConnections() {
for(Map.Entry<XnioIoThread, HostThreadData> data : hostThreadData.entrySet()) {
ConnectionHolder d = data.getValue().availableConnections.poll();
while (d != null) {
IoUtils.safeClose(d.clientConnection);
d = data.getValue().availableConnections.poll();
}
data.getValue().connections = 0;
}
}

private final class HostThreadData { private final class HostThreadData {


int connections = 0; int connections = 0;
Expand Down
Expand Up @@ -585,7 +585,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener<StreamSinkChannel>() { result.getRequestChannel().getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener<StreamSinkChannel>() {
@Override @Override
public void handleEvent(StreamSinkChannel channel) { public void handleEvent(StreamSinkChannel channel) {
Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(exchange, result), handler, handler, exchange.getConnection().getByteBufferPool()); Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(exchange, result, exchange, proxyClientHandler, idempotentPredicate), handler, handler, exchange.getConnection().getByteBufferPool());


} }
}, handler)); }, handler));
Expand All @@ -596,7 +596,7 @@ public void handleEvent(StreamSinkChannel channel) {
handler.handleException(result.getRequestChannel(), e); handler.handleException(result.getRequestChannel(), e);
} }
} }
HTTPTrailerChannelListener trailerListener = new HTTPTrailerChannelListener(exchange, result); HTTPTrailerChannelListener trailerListener = new HTTPTrailerChannelListener(exchange, result, exchange, proxyClientHandler, idempotentPredicate);
if(!exchange.isRequestComplete()) { if(!exchange.isRequestComplete()) {
Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), trailerListener, handler, handler, exchange.getConnection().getByteBufferPool()); Transfer.initiateTransfer(exchange.getRequestChannel(), result.getRequestChannel(), ChannelListeners.closingChannelListener(), trailerListener, handler, handler, exchange.getConnection().getByteBufferPool());
} else { } else {
Expand All @@ -607,24 +607,26 @@ public void handleEvent(StreamSinkChannel channel) {


@Override @Override
public void failed(IOException e) { public void failed(IOException e) {
UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed(exchange.getRequestURI(), e); handleFailure(exchange, proxyClientHandler, idempotentPredicate, e);
if(idempotentPredicate.resolve(exchange) && proxyClientHandler != null) {
proxyClientHandler.failed(exchange); //this will attempt a retry if configured to do so
} else {
if (!exchange.isResponseStarted()) {
exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
exchange.endExchange();
} else {
IoUtils.safeClose(exchange.getConnection());
}
}
} }
}); });




} }
} }


static void handleFailure(HttpServerExchange exchange, ProxyClientHandler proxyClientHandler, Predicate idempotentRequestPredicate, IOException e) {
UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed(exchange.getRequestURI(), e);
if(exchange.isResponseStarted()) {
IoUtils.safeClose(exchange.getConnection());
} else if(idempotentRequestPredicate.resolve(exchange) && proxyClientHandler != null) {
proxyClientHandler.failed(exchange); //this will attempt a retry if configured to do so
} else {
exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
exchange.endExchange();
}
}

private static final class ResponseCallback implements ClientCallback<ClientExchange> { private static final class ResponseCallback implements ClientCallback<ClientExchange> {


private final HttpServerExchange exchange; private final HttpServerExchange exchange;
Expand Down Expand Up @@ -674,33 +676,29 @@ public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange
}); });
} }
final IoExceptionHandler handler = new IoExceptionHandler(exchange, result.getConnection()); final IoExceptionHandler handler = new IoExceptionHandler(exchange, result.getConnection());
Transfer.initiateTransfer(result.getResponseChannel(), exchange.getResponseChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(result, exchange), handler, handler, exchange.getConnection().getByteBufferPool()); Transfer.initiateTransfer(result.getResponseChannel(), exchange.getResponseChannel(), ChannelListeners.closingChannelListener(), new HTTPTrailerChannelListener(result, exchange, exchange, proxyClientHandler, idempotentPredicate), handler, handler, exchange.getConnection().getByteBufferPool());
} }


@Override @Override
public void failed(IOException e) { public void failed(IOException e) {
UndertowLogger.PROXY_REQUEST_LOGGER.proxyRequestFailed(exchange.getRequestURI(), e); handleFailure(exchange, proxyClientHandler, idempotentPredicate, e);
if (!exchange.isResponseStarted()) {
if(idempotentPredicate.resolve(exchange) && proxyClientHandler != null) {
proxyClientHandler.failed(exchange);
} else {
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
exchange.endExchange();
}
} else {
IoUtils.safeClose(exchange.getConnection());
}
} }
} }


private static final class HTTPTrailerChannelListener implements ChannelListener<StreamSinkChannel> { private static final class HTTPTrailerChannelListener implements ChannelListener<StreamSinkChannel> {


private final Attachable source; private final Attachable source;
private final Attachable target; private final Attachable target;
private final HttpServerExchange exchange;
private final ProxyClientHandler proxyClientHandler;
private final Predicate idempotentPredicate;


private HTTPTrailerChannelListener(final Attachable source, final Attachable target) { private HTTPTrailerChannelListener(final Attachable source, final Attachable target, HttpServerExchange exchange, ProxyClientHandler proxyClientHandler, Predicate idempotentPredicate) {
this.source = source; this.source = source;
this.target = target; this.target = target;
this.exchange = exchange;
this.proxyClientHandler = proxyClientHandler;
this.idempotentPredicate = idempotentPredicate;
} }


@Override @Override
Expand All @@ -725,11 +723,9 @@ public void handleEvent(StreamSinkChannel channel) {
channel.shutdownWrites(); channel.shutdownWrites();
} }
} catch (IOException e) { } catch (IOException e) {
UndertowLogger.REQUEST_IO_LOGGER.ioException(e); handleFailure(exchange, proxyClientHandler, idempotentPredicate, e);
IoUtils.safeClose(channel);
} catch (Exception e) { } catch (Exception e) {
UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); handleFailure(exchange, proxyClientHandler, idempotentPredicate, new IOException(e));
IoUtils.safeClose(channel);
} }


} }
Expand Down
Expand Up @@ -34,6 +34,7 @@
import io.undertow.util.Methods; import io.undertow.util.Methods;
import org.xnio.ChannelListener; import org.xnio.ChannelListener;
import io.undertow.connector.PooledByteBuffer; import io.undertow.connector.PooledByteBuffer;
import io.undertow.util.StatusCodes;
import org.xnio.StreamConnection; import org.xnio.StreamConnection;
import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel; import org.xnio.channels.StreamSourceChannel;
Expand Down Expand Up @@ -105,6 +106,7 @@ public void handleEvent(final StreamSourceChannel channel) {
channel.suspendReads(); channel.suspendReads();
return; return;
} }

PooledByteBuffer existing = connection.getExtraBytes(); PooledByteBuffer existing = connection.getExtraBytes();


final PooledByteBuffer pooled = existing == null ? connection.getByteBufferPool().allocate() : existing; final PooledByteBuffer pooled = existing == null ? connection.getByteBufferPool().allocate() : existing;
Expand Down Expand Up @@ -223,6 +225,7 @@ public void handleEvent(AjpServerResponseConduit channel) {
if(state.attributes != null) { if(state.attributes != null) {
httpServerExchange.putAttachment(HttpServerExchange.REQUEST_ATTRIBUTES, state.attributes); httpServerExchange.putAttachment(HttpServerExchange.REQUEST_ATTRIBUTES, state.attributes);
} }
AjpRequestParseState oldState = state;
state = null; state = null;
this.httpServerExchange = null; this.httpServerExchange = null;
httpServerExchange.setPersistent(true); httpServerExchange.setPersistent(true);
Expand All @@ -234,7 +237,13 @@ public void handleEvent(AjpServerResponseConduit channel) {
if(connectorStatistics != null) { if(connectorStatistics != null) {
connectorStatistics.setup(httpServerExchange); connectorStatistics.setup(httpServerExchange);
} }
Connectors.executeRootHandler(connection.getRootHandler(), httpServerExchange);
if(oldState.badRequest) {
httpServerExchange.setStatusCode(StatusCodes.BAD_REQUEST);
httpServerExchange.endExchange();
} else {
Connectors.executeRootHandler(connection.getRootHandler(), httpServerExchange);
}


} catch (Throwable t) { } catch (Throwable t) {
//TODO: we should attempt to return a 500 status code in this situation //TODO: we should attempt to return a 500 status code in this situation
Expand Down
Expand Up @@ -94,12 +94,14 @@ class AjpRequestParseState {
public String sslCipher; public String sslCipher;
public String sslCert; public String sslCert;
public String sslKeySize; public String sslKeySize;
boolean badRequest;


public void reset() { public void reset() {
stringLength = -1; stringLength = -1;
currentStringLength = 0; currentStringLength = 0;
currentIntegerPart = -1; currentIntegerPart = -1;
readHeaders = 0; readHeaders = 0;
badRequest = false;
} }
public boolean isComplete() { public boolean isComplete() {
return state == 15; return state == 15;
Expand Down

0 comments on commit 9fbd987

Please sign in to comment.