package org.glassfish.grizzly.http;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.isSpaceOrTab;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.put;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.skipSpaces;
import static org.glassfish.grizzly.utils.Charsets.ASCII_CHARSET;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.http.HttpCodecFilter.ContentParsingState;
import org.glassfish.grizzly.http.HttpCodecFilter.HeaderParsingState;
import org.glassfish.grizzly.http.util.Ascii;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.http.util.HexUtils;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.memory.CompositeBuffer;
import org.glassfish.grizzly.memory.CompositeBuffer.DisposeOrder;
import org.glassfish.grizzly.memory.MemoryManager;
public final class ChunkedTransferEncoding implements TransferEncoding {
private static final int MAX_HTTP_CHUNK_SIZE_LENGTH = 16;
private static final long CHUNK_SIZE_OVERFLOW = Long.MAX_VALUE >> 4;
private static final int CHUNK_LENGTH_PARSED_STATE = 3;
private static final byte[] LAST_CHUNK_CRLF_BYTES = "0\r\n".getBytes(ASCII_CHARSET);
private static final int[] DEC = HexUtils.getDecBytes();
private final int ;
public ChunkedTransferEncoding(final int maxHeadersSize) {
this.maxHeadersSize = maxHeadersSize;
}
@Override
public boolean (final HttpHeader httpPacket) {
return httpPacket.isChunked();
}
@Override
public boolean (final HttpHeader httpPacket) {
return httpPacket.isChunked();
}
@Override
public void (final FilterChainContext ctx, final HttpHeader httpHeader, final HttpContent content) {
httpHeader.makeTransferEncodingHeader(Constants.CHUNKED_ENCODING);
}
@Override
@SuppressWarnings({ "UnusedDeclaration" })
public ParsingResult (final FilterChainContext ctx, final HttpHeader httpPacket, Buffer buffer) {
final HttpPacketParsing httpPacketParsing = (HttpPacketParsing) httpPacket;
final ContentParsingState contentParsingState = httpPacketParsing.getContentParsingState();
boolean isLastChunk = contentParsingState.isLastChunk;
if (!isLastChunk && contentParsingState.chunkRemainder <= 0) {
buffer = parseTrailerCRLF(httpPacketParsing, buffer);
if (buffer == null) {
return ParsingResult.create(null, null);
}
if (!parseHttpChunkLength(httpPacketParsing, buffer)) {
if (isHeadRequest(httpPacket)) {
return ParsingResult.create(httpPacket.httpTrailerBuilder().headers(contentParsingState.trailerHeaders).build(), null);
}
return ParsingResult.create(null, buffer, false);
}
} else {
contentParsingState.chunkContentStart = 0;
}
int chunkContentStart = contentParsingState.chunkContentStart;
if (contentParsingState.chunkLength == 0) {
if (!isLastChunk) {
contentParsingState.isLastChunk = true;
isLastChunk = true;
initTrailerParsing(httpPacketParsing);
}
if (!parseLastChunkTrailer(ctx, httpPacket, httpPacketParsing, buffer)) {
return ParsingResult.create(null, buffer);
}
chunkContentStart = httpPacketParsing.getHeaderParsingState().offset;
}
final long thisPacketRemaining = contentParsingState.chunkRemainder;
final int contentAvailable = buffer.limit() - chunkContentStart;
Buffer remainder = null;
if (contentAvailable > thisPacketRemaining) {
remainder = buffer.split((int) (chunkContentStart + thisPacketRemaining));
buffer.position(chunkContentStart);
} else if (chunkContentStart > 0) {
buffer.position(chunkContentStart);
}
if (isLastChunk) {
return ParsingResult.create(httpPacket.httpTrailerBuilder().headers(contentParsingState.trailerHeaders).build(), remainder);
}
buffer.shrink();
if (buffer.hasRemaining()) {
contentParsingState.chunkRemainder -= buffer.remaining();
} else {
buffer.tryDispose();
buffer = Buffers.EMPTY_BUFFER;
}
return ParsingResult.create(httpPacket.httpContentBuilder().content(buffer).build(), remainder);
}
@Override
public Buffer serializePacket(final FilterChainContext ctx, final HttpContent httpContent) {
return encodeHttpChunk(ctx.getMemoryManager(), httpContent, httpContent.isLast());
}
private void initTrailerParsing(HttpPacketParsing httpPacket) {
final HeaderParsingState headerParsingState = httpPacket.getHeaderParsingState();
final ContentParsingState contentParsingState = httpPacket.getContentParsingState();
headerParsingState.subState = 0;
final int start = contentParsingState.chunkContentStart;
headerParsingState.start = start;
headerParsingState.offset = start;
headerParsingState.packetLimit = start + maxHeadersSize;
}
private boolean (final FilterChainContext ctx, final HttpHeader httpHeader, final HttpPacketParsing httpPacket, final Buffer input) {
final HeaderParsingState headerParsingState = httpPacket.getHeaderParsingState();
final ContentParsingState contentParsingState = httpPacket.getContentParsingState();
final HttpCodecFilter filter = headerParsingState.codecFilter;
final boolean result = filter.parseHeadersFromBuffer(httpHeader, contentParsingState.trailerHeaders, headerParsingState, input);
if (result) {
if (contentParsingState.trailerHeaders.size() > 0) {
filter.onHttpHeadersParsed(httpHeader, contentParsingState.trailerHeaders, ctx);
}
} else {
headerParsingState.checkOverflow(input.limit(), "The chunked encoding trailer header is too large");
}
return result;
}
private static boolean parseHttpChunkLength(final HttpPacketParsing httpPacket, final Buffer input) {
final HeaderParsingState parsingState = httpPacket.getHeaderParsingState();
while (true) {
switch (parsingState.state) {
case 0: {
final int pos = input.position();
parsingState.start = pos;
parsingState.offset = pos;
parsingState.packetLimit = pos + MAX_HTTP_CHUNK_SIZE_LENGTH;
}
case 1: {
final int nonSpaceIdx = skipSpaces(input, parsingState.offset, parsingState.packetLimit);
if (nonSpaceIdx == -1) {
parsingState.offset = input.limit();
parsingState.state = 1;
parsingState.checkOverflow(input.limit(), "The chunked encoding length prefix is too large");
return false;
}
parsingState.offset = nonSpaceIdx;
parsingState.state = 2;
}
case 2: {
int offset = parsingState.offset;
int limit = Math.min(parsingState.packetLimit, input.limit());
long value = parsingState.parsingNumericValue;
while (offset < limit) {
final byte b = input.get(offset);
if (isSpaceOrTab(b) ||
b == Constants.CR || b == Constants.SEMI_COLON) {
parsingState.checkpoint = offset;
} else if (b == Constants.LF) {
final ContentParsingState contentParsingState = httpPacket.getContentParsingState();
contentParsingState.chunkContentStart = offset + 1;
contentParsingState.chunkLength = value;
contentParsingState.chunkRemainder = value;
parsingState.state = CHUNK_LENGTH_PARSED_STATE;
return true;
} else if (parsingState.checkpoint == -1) {
if (DEC[b & 0xFF] != -1 && checkOverflow(value)) {
value = (value << 4) + DEC[b & 0xFF];
} else {
throw new HttpBrokenContentException("Invalid byte representing a hex value within a chunk length encountered : " + b);
}
} else {
throw new HttpBrokenContentException("Unexpected HTTP chunk header");
}
offset++;
}
parsingState.parsingNumericValue = value;
parsingState.offset = limit;
parsingState.checkOverflow(limit, "The chunked encoding length prefix is too large");
return false;
}
}
}
}
private static boolean checkOverflow(final long value) {
return value <= CHUNK_SIZE_OVERFLOW;
}
private static boolean (final HttpHeader header) {
final HttpRequestPacket request = header.isRequest() ? (HttpRequestPacket) header : ((HttpResponsePacket) header).getRequest();
return request.isHeadRequest();
}
private static Buffer parseTrailerCRLF(final HttpPacketParsing httpPacket, final Buffer input) {
final HeaderParsingState parsingState = httpPacket.getHeaderParsingState();
if (parsingState.state == CHUNK_LENGTH_PARSED_STATE) {
while (input.hasRemaining()) {
if (input.get() == Constants.LF) {
parsingState.recycle();
if (input.hasRemaining()) {
return input.slice();
}
return null;
}
}
return null;
}
return input;
}
private static Buffer encodeHttpChunk(final MemoryManager memoryManager, final HttpContent httpContent, final boolean isLastChunk) {
final Buffer content = httpContent.getContent();
Buffer httpChunkBuffer = memoryManager.allocate(16);
final int chunkSize = content.remaining();
Ascii.intToHexString(httpChunkBuffer, chunkSize);
httpChunkBuffer = put(memoryManager, httpChunkBuffer, HttpCodecFilter.CRLF_BYTES);
httpChunkBuffer.trim();
httpChunkBuffer.allowBufferDispose(true);
final boolean hasContent = chunkSize > 0;
if (hasContent) {
httpChunkBuffer = Buffers.appendBuffers(memoryManager, httpChunkBuffer, content);
if (httpChunkBuffer.isComposite()) {
httpChunkBuffer.allowBufferDispose(true);
((CompositeBuffer) httpChunkBuffer).allowInternalBuffersDispose(true);
((CompositeBuffer) httpChunkBuffer).disposeOrder(DisposeOrder.FIRST_TO_LAST);
}
}
Buffer httpChunkTrailer;
if (!isLastChunk) {
httpChunkTrailer = memoryManager.allocate(2);
} else {
final boolean isTrailer = HttpTrailer.isTrailer(httpContent) && ((HttpTrailer) httpContent).getHeaders().size() > 0;
if (!isTrailer) {
httpChunkTrailer = memoryManager.allocate(8);
} else {
httpChunkTrailer = memoryManager.allocate(256);
}
if (hasContent) {
httpChunkTrailer = put(memoryManager, httpChunkTrailer, HttpCodecFilter.CRLF_BYTES);
httpChunkTrailer = put(memoryManager, httpChunkTrailer, LAST_CHUNK_CRLF_BYTES);
}
if (isTrailer) {
final HttpTrailer httpTrailer = (HttpTrailer) httpContent;
final MimeHeaders mimeHeaders = httpTrailer.getHeaders();
httpChunkTrailer = HttpCodecFilter.encodeMimeHeaders(memoryManager, httpChunkTrailer, mimeHeaders,
httpContent.getHttpHeader().getTempHeaderEncodingBuffer());
}
}
httpChunkTrailer = put(memoryManager, httpChunkTrailer, HttpCodecFilter.CRLF_BYTES);
httpChunkTrailer.trim();
httpChunkTrailer.allowBufferDispose(true);
return Buffers.appendBuffers(memoryManager, httpChunkBuffer, httpChunkTrailer);
}
}