package org.glassfish.grizzly.http.ajp;
import org.glassfish.grizzly.memory.Buffers;
import java.io.IOException;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.util.Ascii;
import org.glassfish.grizzly.http.util.BufferChunk;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.HexUtils;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.memory.CompositeBuffer;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.ssl.SSLSupport;
import static org.glassfish.grizzly.http.util.HttpCodecUtils.*;
import org.glassfish.grizzly.http.util.HttpUtils;
final class AjpMessageUtils {
private static final int[] DEC = HexUtils.getDecBytes();
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
static void decodeRequest(final Buffer requestContent,
final AjpHttpRequest req, final boolean tomcatAuthentication)
throws IOException {
int offset = requestContent.position();
byte methodCode = requestContent.get(offset++);
if (methodCode != AjpConstants.SC_M_JK_STORED) {
String mName = AjpConstants.methodTransArray[(int) methodCode - 1];
req.getMethodDC().setString(mName);
}
offset = getBytesToDataChunk(requestContent, offset, req.getProtocolDC());
final int requestURILen = readShort(requestContent, offset);
if (!isNullLength(requestURILen)) {
req.getRequestURIRef().init(requestContent, offset + 2, offset + 2 + requestURILen);
}
offset += 2 + requestURILen + 1;
offset = getBytesToDataChunk(requestContent, offset, req.remoteAddr());
offset = getBytesToDataChunk(requestContent, offset, req.remoteHostRaw());
offset = getBytesToDataChunk(requestContent, offset, req.localName());
req.setLocalPort(readShort(requestContent, offset));
offset += 2;
final boolean isSSL = requestContent.get(offset++) != 0;
req.setSecure(isSSL);
req.getResponse().setSecure(isSSL);
offset = decodeHeaders(requestContent, offset, req);
decodeAttributes(requestContent, offset, req,
tomcatAuthentication);
req.setUnparsedHostHeader(req.getHeaders().getValue("host"));
}
private static int decodeAttributes(final Buffer requestContent, int offset,
final AjpHttpRequest req, final boolean tomcatAuthentication) {
final DataChunk tmpDataChunk = req.tmpDataChunk;
boolean moreAttr = true;
while (moreAttr) {
final byte attributeCode = requestContent.get(offset++);
if (attributeCode == AjpConstants.SC_A_ARE_DONE) {
return offset;
}
if (attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE) {
req.setAttribute(SSLSupport.KEY_SIZE_KEY,
readShort(requestContent, offset));
offset += 2;
}
if (attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE) {
offset = setStringAttribute(req, requestContent, offset);
}
switch (attributeCode) {
case AjpConstants.SC_A_CONTEXT:
offset = skipBytes(requestContent, offset);
break;
case AjpConstants.SC_A_REMOTE_USER:
if (tomcatAuthentication) {
offset = skipBytes(requestContent, offset);
} else {
offset = getBytesToDataChunk(requestContent, offset,
req.remoteUser());
}
break;
case AjpConstants.SC_A_AUTH_TYPE:
if (tomcatAuthentication) {
offset = skipBytes(requestContent, offset);
} else {
offset = getBytesToDataChunk(requestContent, offset,
req.authType());
}
break;
case AjpConstants.SC_A_QUERY_STRING:
offset = getBytesToDataChunk(requestContent, offset,
req.getQueryStringDC());
break;
case AjpConstants.SC_A_JVM_ROUTE:
offset = getBytesToDataChunk(requestContent, offset,
req.instanceId());
break;
case AjpConstants.SC_A_SSL_CERT:
req.setSecure(true);
offset = getBytesToDataChunk(requestContent, offset, req.sslCert());
break;
case AjpConstants.SC_A_SSL_CIPHER:
req.setSecure(true);
offset = setStringAttributeValue(req,
SSLSupport.CIPHER_SUITE_KEY, requestContent, offset);
break;
case AjpConstants.SC_A_SSL_SESSION:
req.setSecure(true);
offset = setStringAttributeValue(req,
SSLSupport.SESSION_ID_KEY, requestContent, offset);
break;
case AjpConstants.SC_A_SECRET:
offset = getBytesToDataChunk(requestContent, offset, tmpDataChunk);
req.setSecret(tmpDataChunk.toString());
tmpDataChunk.recycle();
break;
case AjpConstants.SC_A_STORED_METHOD:
offset = getBytesToDataChunk(requestContent, offset, req.getMethodDC());
break;
default:
break;
}
}
return offset;
}
static int (final Buffer requestContent, int offset,
final AjpHttpRequest req) {
final MimeHeaders headers = req.getHeaders();
final int hCount = readShort(requestContent, offset);
offset += 2;
for (int i = 0; i < hCount; i++) {
String hName;
int isc = readShort(requestContent, offset);
int hId = isc & 0xFF;
DataChunk valueDC;
isc &= 0xFF00;
if (0xA000 == isc) {
offset += 2;
hName = AjpConstants.headerTransArray[hId - 1];
valueDC = headers.addValue(hName);
} else {
hId = -1;
final int headerNameLen = readShort(requestContent, offset);
offset += 2;
valueDC = headers.addValue(requestContent,
offset, headerNameLen);
offset += headerNameLen + 1;
}
offset = getBytesToDataChunk(requestContent, offset, valueDC);
final DataChunk headerNameDC = headers.getName(headers.size() - 1);
if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH
|| (hId == -1 && headerNameDC.equalsIgnoreCase("Content-Length"))) {
final long cl = Ascii.parseLong(valueDC);
if (cl < Integer.MAX_VALUE) {
req.setContentLength((int) cl);
}
} else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE
|| (hId == -1 && headerNameDC.equalsIgnoreCase("Content-Type"))) {
req.setContentType(valueDC.toString());
}
}
return offset;
}
static void parseHost(final DataChunk hostDC,
final DataChunk serverNameDC,
final HttpRequestPacket request) {
if (hostDC == null) {
request.setServerPort(request.getLocalPort());
serverNameDC.setString(request.getLocalName());
return;
}
final BufferChunk valueBC = hostDC.getBufferChunk();
final int valueS = valueBC.getStart();
final int valueL = valueBC.getEnd() - valueS;
int colonPos = -1;
final Buffer valueB = valueBC.getBuffer();
final boolean ipv6 = (valueB.get(valueS) == '[');
boolean bracketClosed = false;
for (int i = 0; i < valueL; i++) {
final byte b = valueB.get(i + valueS);
if (b == ']') {
bracketClosed = true;
} else if (b == ':') {
if (!ipv6 || bracketClosed) {
colonPos = i;
break;
}
}
}
if (colonPos < 0) {
if (!request.isSecure()) {
request.setServerPort(80);
} else {
request.setServerPort(443);
}
serverNameDC.setBuffer(valueB, valueS, valueS + valueL);
} else {
serverNameDC.setBuffer(valueB, valueS, valueS + colonPos);
int port = 0;
int mult = 1;
for (int i = valueL - 1; i > colonPos; i--) {
int charValue = DEC[(int) valueB.get(i + valueS)];
if (charValue == -1) {
throw new IllegalStateException(
String.format("Host header %s contained a non-decimal value in the port definition.",
hostDC.toString()));
}
port = port + (charValue * mult);
mult = 10 * mult;
}
request.setServerPort(port);
}
}
private static boolean isNullLength(final int length) {
return length == 0xFFFF || length == -1;
}
static int readShort(final Buffer buffer, final int offset) {
return buffer.getShort(offset) & 0xFFFF;
}
static int getBytesToDataChunk(final Buffer buffer, final int offset,
final DataChunk dataChunk) {
final int bytesStart = offset + 2;
final int length = readShort(buffer, offset);
if (isNullLength(length)) {
return bytesStart;
}
dataChunk.setBuffer(buffer, bytesStart, bytesStart + length);
return bytesStart + length + 1;
}
private static int skipBytes(final Buffer buffer, final int offset) {
final int bytesStart = offset + 2;
final int length = readShort(buffer, offset);
if (isNullLength(length)) {
return bytesStart;
}
return bytesStart + length + 1;
}
private static int setStringAttribute(final AjpHttpRequest req,
final Buffer buffer, int offset) {
final DataChunk tmpDataChunk = req.tmpDataChunk;
offset = getBytesToDataChunk(buffer, offset, tmpDataChunk);
final String key = tmpDataChunk.toString();
tmpDataChunk.recycle();
offset = getBytesToDataChunk(buffer, offset, tmpDataChunk);
final String value = tmpDataChunk.toString();
tmpDataChunk.recycle();
req.setAttribute(key, value);
return offset;
}
private static int setStringAttributeValue(final AjpHttpRequest req,
final String key, final Buffer buffer, int offset) {
final DataChunk tmpDataChunk = req.tmpDataChunk;
offset = getBytesToDataChunk(buffer, offset, tmpDataChunk);
final String value = tmpDataChunk.toString();
tmpDataChunk.recycle();
req.setAttribute(key, value);
return offset;
}
public static Buffer (final MemoryManager mm,
final HttpResponsePacket httpResponsePacket) {
Buffer encodedBuffer = mm.allocate(4096);
int startPos = encodedBuffer.position();
encodedBuffer.position(startPos + 4);
encodedBuffer.put(AjpConstants.JK_AJP13_SEND_HEADERS);
encodedBuffer.putShort((short) httpResponsePacket.getStatus());
final byte[] tempBuffer = httpResponsePacket.getTempHeaderEncodingBuffer();
if (httpResponsePacket.isCustomReasonPhraseSet()) {
encodedBuffer = putBytes(mm,
encodedBuffer,
HttpUtils.filter(
httpResponsePacket.getReasonPhraseDC()),
tempBuffer);
} else {
encodedBuffer = putBytes(mm, encodedBuffer,
httpResponsePacket.getHttpStatus().getReasonPhraseBytes());
}
if (httpResponsePacket.isAcknowledgement()) {
encodedBuffer = putShort(mm, encodedBuffer, 0);
} else {
final MimeHeaders headers = httpResponsePacket.getHeaders();
final String contentType = httpResponsePacket.getContentType();
if (contentType != null) {
headers.setValue("Content-Type").setString(contentType);
}
final String contentLanguage = httpResponsePacket.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("Content-Language").setString(contentLanguage);
}
final long contentLength = httpResponsePacket.getContentLength();
if (contentLength >= 0) {
final Buffer contentLengthBuffer = getLongAsBuffer(mm, contentLength);
headers.setValue("Content-Length").setBuffer(contentLengthBuffer,
contentLengthBuffer.position(), contentLengthBuffer.limit());
}
final int numHeaders = headers.size();
encodedBuffer = putShort(mm, encodedBuffer, numHeaders);
for (int i = 0; i < numHeaders; i++) {
final DataChunk headerName = headers.getName(i);
encodedBuffer = putBytes(mm, encodedBuffer, headerName, tempBuffer);
final DataChunk headerValue = headers.getValue(i);
encodedBuffer = putBytes(mm, encodedBuffer, headerValue, tempBuffer);
}
}
encodedBuffer.put(startPos, (byte) 'A');
encodedBuffer.put(startPos + 1, (byte) 'B');
encodedBuffer.putShort(startPos + 2,
(short) (encodedBuffer.position() - startPos - 4));
return encodedBuffer;
}
private static final int BODY_CHUNK_HEADER_SIZE = 7;
private static final int MAX_BODY_CHUNK_CONTENT_SIZE =
AjpConstants.SUGGESTED_MAX_PAYLOAD_SIZE - BODY_CHUNK_HEADER_SIZE - 1;
public static Buffer appendContentAndTrim(final MemoryManager memoryManager,
Buffer dstBuffer, Buffer httpContentBuffer) {
Buffer resultBuffer = null;
do {
Buffer contentRemainder = null;
if (httpContentBuffer.remaining() > MAX_BODY_CHUNK_CONTENT_SIZE) {
contentRemainder = httpContentBuffer.split(
httpContentBuffer.position() + MAX_BODY_CHUNK_CONTENT_SIZE);
}
final Buffer encodedContentChunk = appendContentChunkAndTrim(
memoryManager,dstBuffer, httpContentBuffer);
resultBuffer = Buffers.appendBuffers(memoryManager, resultBuffer,
encodedContentChunk);
dstBuffer = null;
httpContentBuffer = contentRemainder;
} while (httpContentBuffer != null && httpContentBuffer.hasRemaining());
return resultBuffer;
}
private static Buffer appendContentChunkAndTrim(final MemoryManager memoryManager,
final Buffer dstBuffer, final Buffer httpContentBuffer) {
final boolean useDstBufferForHeaders = dstBuffer != null &&
dstBuffer.remaining() >= BODY_CHUNK_HEADER_SIZE;
final Buffer headerBuffer;
if (useDstBufferForHeaders) {
headerBuffer = dstBuffer;
} else {
if (dstBuffer != null) {
dstBuffer.trim();
}
headerBuffer = memoryManager.allocate(BODY_CHUNK_HEADER_SIZE);
headerBuffer.allowBufferDispose(true);
}
headerBuffer.put((byte) 'A');
headerBuffer.put((byte) 'B');
headerBuffer.putShort((short) (4 + httpContentBuffer.remaining()));
headerBuffer.put(AjpConstants.JK_AJP13_SEND_BODY_CHUNK);
headerBuffer.putShort((short) httpContentBuffer.remaining());
headerBuffer.trim();
Buffer resultBuffer = Buffers.appendBuffers(memoryManager,
headerBuffer, httpContentBuffer);
final Buffer terminatingBuffer = memoryManager.allocate(1);
terminatingBuffer.allowBufferDispose(true);
resultBuffer = Buffers.appendBuffers(memoryManager,
resultBuffer, terminatingBuffer);
if (!useDstBufferForHeaders && dstBuffer != null) {
resultBuffer = Buffers.appendBuffers(memoryManager,
dstBuffer, resultBuffer);
}
if (resultBuffer.isComposite()) {
((CompositeBuffer) resultBuffer).allowInternalBuffersDispose(true);
}
return resultBuffer;
}
private static Buffer putBytes(final MemoryManager memoryManager,
Buffer dstBuffer,
final DataChunk dataChunk,
final byte[] tempBuffer) {
if (dataChunk == null || dataChunk.isNull()) {
return putBytes(memoryManager, dstBuffer, EMPTY_BYTE_ARRAY);
}
final int size = dataChunk.getLength();
if (dstBuffer.remaining() < size + 2 + 1) {
dstBuffer = resizeBuffer(memoryManager, dstBuffer, size + 2 + 1);
}
dstBuffer.putShort((short) size);
dstBuffer = put(memoryManager, dstBuffer, tempBuffer, dataChunk);
dstBuffer.put((byte) 0);
return dstBuffer;
}
private static Buffer putBytes(final MemoryManager memoryManager,
Buffer dstBuffer, final byte[] bytes) {
final int size = bytes.length;
if (dstBuffer.remaining() < size + 2 + 1) {
dstBuffer = resizeBuffer(memoryManager, dstBuffer, size + 2 + 1);
}
dstBuffer.putShort((short) size);
dstBuffer = put(memoryManager, dstBuffer, bytes);
dstBuffer.put((byte) 0);
return dstBuffer;
}
private static Buffer putShort(final MemoryManager memoryManager,
Buffer dstBuffer, final int value) {
if (dstBuffer.remaining() < 2) {
dstBuffer = resizeBuffer(memoryManager, dstBuffer, 2);
}
dstBuffer.putShort((short) value);
return dstBuffer;
}
}