package io.undertow.server.protocol.http;
import io.undertow.UndertowLogger;
import io.undertow.UndertowOptions;
import io.undertow.conduits.ChunkedStreamSinkConduit;
import io.undertow.conduits.ChunkedStreamSourceConduit;
import io.undertow.conduits.ConduitListener;
import io.undertow.conduits.FinishableStreamSinkConduit;
import io.undertow.conduits.FixedLengthStreamSourceConduit;
import io.undertow.conduits.HeadStreamSinkConduit;
import io.undertow.conduits.PreChunkedStreamSinkConduit;
import io.undertow.server.Connectors;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.DateUtils;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.StatusCodes;
import org.jboss.logging.Logger;
import org.xnio.conduits.ConduitStreamSourceChannel;
import org.xnio.conduits.StreamSinkConduit;
import org.xnio.conduits.StreamSourceConduit;
class HttpTransferEncoding {
private static final Logger log = Logger.getLogger("io.undertow.server.handler.transfer-encoding");
private HttpTransferEncoding() {
}
public static void setupRequest(final HttpServerExchange exchange) {
final HeaderMap requestHeaders = exchange.getRequestHeaders();
final String connectionHeader = requestHeaders.getFirst(Headers.CONNECTION);
final String transferEncodingHeader = requestHeaders.getLast(Headers.TRANSFER_ENCODING);
final String contentLengthHeader = requestHeaders.getFirst(Headers.CONTENT_LENGTH);
final HttpServerConnection connection = (HttpServerConnection) exchange.getConnection();
PipeliningBufferingStreamSinkConduit pipeliningBuffer = connection.getPipelineBuffer();
if (pipeliningBuffer != null) {
pipeliningBuffer.setupPipelineBuffer(exchange);
}
ConduitStreamSourceChannel sourceChannel = connection.getChannel().getSourceChannel();
sourceChannel.setConduit(connection.getReadDataStreamSourceConduit());
boolean persistentConnection = persistentConnection(exchange, connectionHeader);
if (transferEncodingHeader == null && contentLengthHeader == null) {
if (persistentConnection
&& connection.getExtraBytes() != null
&& pipeliningBuffer == null
&& connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) {
pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool());
connection.setPipelineBuffer(pipeliningBuffer);
pipeliningBuffer.setupPipelineBuffer(exchange);
}
Connectors.terminateRequest(exchange);
} else {
persistentConnection = handleRequestEncoding(exchange, transferEncodingHeader, contentLengthHeader, connection, pipeliningBuffer, persistentConnection);
}
exchange.setPersistent(persistentConnection);
if (!exchange.isRequestComplete() || connection.getExtraBytes() != null) {
sourceChannel.setReadListener(null);
sourceChannel.suspendReads();
}
}
private static boolean handleRequestEncoding(final HttpServerExchange exchange, String transferEncodingHeader, String contentLengthHeader, HttpServerConnection connection, PipeliningBufferingStreamSinkConduit pipeliningBuffer, boolean persistentConnection) {
HttpString transferEncoding = Headers.IDENTITY;
if (transferEncodingHeader != null) {
transferEncoding = new HttpString(transferEncodingHeader);
}
if (transferEncodingHeader != null && !transferEncoding.equals(Headers.IDENTITY)) {
ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel();
sourceChannel.setConduit(new ChunkedStreamSourceConduit(sourceChannel.getConduit(), exchange, chunkedDrainListener(exchange)));
} else if (contentLengthHeader != null) {
final long contentLength;
contentLength = parsePositiveLong(contentLengthHeader);
if (contentLength == 0L) {
log.trace("No content, starting next request");
Connectors.terminateRequest(exchange);
} else {
ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel();
sourceChannel.setConduit(fixedLengthStreamSourceConduitWrapper(contentLength, sourceChannel.getConduit(), exchange));
}
} else if (transferEncodingHeader != null) {
log.trace("Connection not persistent (no content length and identity transfer encoding)");
persistentConnection = false;
} else if (persistentConnection) {
if (connection.getExtraBytes() != null
&& pipeliningBuffer == null
&& connection.getUndertowOptions().get(UndertowOptions.BUFFER_PIPELINED_DATA, false)) {
pipeliningBuffer = new PipeliningBufferingStreamSinkConduit(connection.getOriginalSinkConduit(), connection.getByteBufferPool());
connection.setPipelineBuffer(pipeliningBuffer);
pipeliningBuffer.setupPipelineBuffer(exchange);
}
Connectors.terminateRequest(exchange);
} else {
Connectors.terminateRequest(exchange);
}
return persistentConnection;
}
private static boolean persistentConnection(HttpServerExchange exchange, String connectionHeader) {
if (exchange.isHttp11()) {
return !(connectionHeader != null && Headers.CLOSE.equalToString(connectionHeader));
} else if (exchange.isHttp10()) {
if (connectionHeader != null) {
if (Headers.KEEP_ALIVE.equals(new HttpString(connectionHeader))) {
return true;
}
}
}
log.trace("Connection not persistent");
return false;
}
private static StreamSourceConduit fixedLengthStreamSourceConduitWrapper(final long contentLength, final StreamSourceConduit conduit, final HttpServerExchange exchange) {
return new FixedLengthStreamSourceConduit(conduit, contentLength, fixedLengthDrainListener(exchange), exchange);
}
private static ConduitListener<FixedLengthStreamSourceConduit> fixedLengthDrainListener(final HttpServerExchange exchange) {
return new ConduitListener<FixedLengthStreamSourceConduit>() {
public void handleEvent(final FixedLengthStreamSourceConduit fixedLengthConduit) {
long remaining = fixedLengthConduit.getRemaining();
if (remaining > 0L) {
UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed();
exchange.setPersistent(false);
}
Connectors.terminateRequest(exchange);
}
};
}
private static ConduitListener<ChunkedStreamSourceConduit> chunkedDrainListener(final HttpServerExchange exchange) {
return new ConduitListener<ChunkedStreamSourceConduit>() {
public void handleEvent(final ChunkedStreamSourceConduit chunkedStreamSourceConduit) {
if (!chunkedStreamSourceConduit.isFinished()) {
UndertowLogger.REQUEST_LOGGER.requestWasNotFullyConsumed();
exchange.setPersistent(false);
}
Connectors.terminateRequest(exchange);
}
};
}
private static ConduitListener<StreamSinkConduit> terminateResponseListener(final HttpServerExchange exchange) {
return new ConduitListener<StreamSinkConduit>() {
public void handleEvent(final StreamSinkConduit channel) {
Connectors.terminateResponse(exchange);
}
};
}
static StreamSinkConduit createSinkConduit(final HttpServerExchange exchange) {
DateUtils.addDateHeaderIfRequired(exchange);
boolean headRequest = exchange.getRequestMethod().equals(Methods.HEAD);
HttpServerConnection serverConnection = (HttpServerConnection) exchange.getConnection();
HttpResponseConduit responseConduit = serverConnection.getResponseConduit();
responseConduit.reset(exchange);
StreamSinkConduit channel = responseConduit;
if (headRequest) {
channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange));
} else if(!Connectors.isEntityBodyAllowed(exchange)) {
exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH);
exchange.getResponseHeaders().remove(Headers.TRANSFER_ENCODING);
channel = new HeadStreamSinkConduit(channel, terminateResponseListener(exchange));
return channel;
}
final HeaderMap responseHeaders = exchange.getResponseHeaders();
String connection = responseHeaders.getFirst(Headers.CONNECTION);
if(exchange.getStatusCode() == StatusCodes.EXPECTATION_FAILED) {
exchange.setPersistent(false);
}
if (!exchange.isPersistent()) {
responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
} else if (exchange.isPersistent() && connection != null) {
if (HttpString.tryFromString(connection).equals(Headers.CLOSE)) {
exchange.setPersistent(false);
}
} else if (exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, true)) {
responseHeaders.put(Headers.CONNECTION, Headers.KEEP_ALIVE.toString());
}
final String transferEncodingHeader = responseHeaders.getLast(Headers.TRANSFER_ENCODING);
if(transferEncodingHeader == null) {
final String contentLengthHeader = responseHeaders.getFirst(Headers.CONTENT_LENGTH);
if (contentLengthHeader != null) {
StreamSinkConduit res = handleFixedLength(exchange, headRequest, channel, responseHeaders, contentLengthHeader, serverConnection);
if (res != null) {
return res;
}
}
} else {
responseHeaders.remove(Headers.CONTENT_LENGTH);
}
return handleResponseConduit(exchange, headRequest, channel, responseHeaders, terminateResponseListener(exchange), transferEncodingHeader);
}
private static StreamSinkConduit handleFixedLength(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, String contentLengthHeader, HttpServerConnection connection) {
try {
final long contentLength = parsePositiveLong(contentLengthHeader);
if (headRequest) {
return channel;
}
ServerFixedLengthStreamSinkConduit fixed = connection.getFixedLengthStreamSinkConduit();
fixed.reset(contentLength, exchange);
return fixed;
} catch (NumberFormatException e) {
responseHeaders.remove(Headers.CONTENT_LENGTH);
}
return null;
}
private static StreamSinkConduit handleResponseConduit(HttpServerExchange exchange, boolean headRequest, StreamSinkConduit channel, HeaderMap responseHeaders, ConduitListener<StreamSinkConduit> finishListener, String transferEncodingHeader) {
if (transferEncodingHeader == null) {
if (exchange.isHttp11()) {
if (exchange.isPersistent()) {
responseHeaders.put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString());
if (headRequest) {
return channel;
}
return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange);
} else {
if (headRequest) {
return channel;
}
return new FinishableStreamSinkConduit(channel, finishListener);
}
} else {
exchange.setPersistent(false);
responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
if (headRequest) {
return channel;
}
return new FinishableStreamSinkConduit(channel, finishListener);
}
} else {
return handleExplicitTransferEncoding(exchange, channel, finishListener, responseHeaders, transferEncodingHeader, headRequest);
}
}
private static StreamSinkConduit handleExplicitTransferEncoding(HttpServerExchange exchange, StreamSinkConduit channel, ConduitListener<StreamSinkConduit> finishListener, HeaderMap responseHeaders, String transferEncodingHeader, boolean headRequest) {
HttpString transferEncoding = new HttpString(transferEncodingHeader);
if (transferEncoding.equals(Headers.CHUNKED)) {
if (headRequest) {
return channel;
}
Boolean preChunked = exchange.getAttachment(HttpAttachments.PRE_CHUNKED_RESPONSE);
if(preChunked != null && preChunked) {
return new PreChunkedStreamSinkConduit(channel, finishListener, exchange);
} else {
return new ChunkedStreamSinkConduit(channel, exchange.getConnection().getByteBufferPool(), true, !exchange.isPersistent(), responseHeaders, finishListener, exchange);
}
} else {
if (headRequest) {
return channel;
}
log.trace("Cancelling persistence because response is identity with no content length");
exchange.setPersistent(false);
responseHeaders.put(Headers.CONNECTION, Headers.CLOSE.toString());
return new FinishableStreamSinkConduit(channel, terminateResponseListener(exchange));
}
}
public static long parsePositiveLong(String str) {
long value = 0;
final int length = str.length();
if (length == 0) {
throw new NumberFormatException(str);
}
long multiplier = 1;
for (int i = length - 1; i >= 0; --i) {
char c = str.charAt(i);
if (c < '0' || c > '9') {
throw new NumberFormatException(str);
}
long digit = c - '0';
value += digit * multiplier;
multiplier *= 10;
}
return value;
}
}