All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.grizzly.http.ajp.AjpMessageUtils Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.grizzly.http.ajp;

import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.util.buf.Ascii;
import com.sun.grizzly.util.buf.ByteChunk;
import com.sun.grizzly.util.buf.CharChunk;
import com.sun.grizzly.util.buf.MessageBytes;
import com.sun.grizzly.util.http.HttpMessages;
import com.sun.grizzly.util.http.MimeHeaders;
import com.sun.grizzly.util.net.SSLSupport;

import java.io.IOException;
import java.nio.ByteBuffer;


/**
 * Utility method for Ajp message parsing and serialization.
 *
 * @author Alexey Stashok
 */
final class AjpMessageUtils {

    private static final int BODY_CHUNK_HEADER_SIZE = 7;
    private static final int MAX_BODY_CHUNK_CONTENT_SIZE = AjpConstants.MAX_READ_SIZE - BODY_CHUNK_HEADER_SIZE;

    private static final byte[] EMPTY_ARRAY = new byte[0];
    
    public static int decodeForwardRequest(byte[] buffer, int pos,
            boolean tomcatAuthentication, AjpHttpRequest request) {
        
        // Translate the HTTP method code to a String.
        byte methodCode = buffer[pos++];
        if (methodCode != AjpConstants.SC_M_JK_STORED) {
            String mName = AjpConstants.methodTransArray[(int) methodCode - 1];
            request.method().setString(mName);
        }

        pos = getBytesToByteChunk(buffer, pos, request.protocol());
        pos = getBytesToByteChunk(buffer, pos, request.requestURI());
        pos = getBytesToByteChunk(buffer, pos, request.remoteAddr());
        pos = getBytesToByteChunk(buffer, pos, request.remoteHost());
        pos = getBytesToByteChunk(buffer, pos, request.localName());

        request.setLocalPort(getShort(buffer, pos));
        pos += 2;

        final boolean isSSL = buffer[pos++] != 0;
        if (isSSL) {
            request.scheme().setString("https");
        }
        
        pos = decodeHeaders(request, buffer, pos);

        pos = decodeAttributes(buffer, pos, request, tomcatAuthentication);
        request.unparsedURI().setString(request.requestURI() +
                (request.queryString().getLength() != 0 ? "?" + request.queryString() : ""));
        
        return pos;
    }

    private static int decodeAttributes(final byte[] requestContent, int pos,
            final AjpHttpRequest req, final boolean tomcatAuthentication) {

        final MessageBytes tmpMessageBytes = req.tmpMessageBytes;
        
        boolean moreAttr = true;

        while (moreAttr) {
            final byte attributeCode = requestContent[pos++];
            if (attributeCode == AjpConstants.SC_A_ARE_DONE) {
                return pos;
            }

            /* Special case ( XXX in future API make it separate type !)
             */
            if (attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE) {
                // Bug 1326: it's an Integer.
                req.setAttribute(SSLSupport.KEY_SIZE_KEY,
                        getShort(requestContent, pos));
                pos += 2;
            }

            if (attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE) {
                // 2 strings ???...
                pos = setStringAttribute(req, requestContent, pos);
            }


            // 1 string attributes
            switch (attributeCode) {
                case AjpConstants.SC_A_CONTEXT:
                    pos = skipBytes(requestContent, pos);
                    // nothing
                    break;

                case AjpConstants.SC_A_SERVLET_PATH:
                    pos = skipBytes(requestContent, pos);
                    // nothing
                    break;

                case AjpConstants.SC_A_REMOTE_USER:
                    if (tomcatAuthentication) {
                        // ignore server
                        pos = skipBytes(requestContent, pos);
                    } else {
                        pos = getBytesToByteChunk(requestContent, pos,
                                req.getRemoteUser());
                    }
                    break;

                case AjpConstants.SC_A_AUTH_TYPE:
                    if (tomcatAuthentication) {
                        // ignore server
                        pos = skipBytes(requestContent, pos);
                    } else {
                        pos = getBytesToByteChunk(requestContent, pos,
                                req.getAuthType());
                    }
                    break;

                case AjpConstants.SC_A_QUERY_STRING:
                    pos = getBytesToByteChunk(requestContent, pos,
                            req.queryString());
                    break;

                case AjpConstants.SC_A_JVM_ROUTE:
                    pos = getBytesToByteChunk(requestContent, pos,
                            req.instanceId());
                    break;

                case AjpConstants.SC_A_SSL_CERT:
                    req.scheme().setString("https");
                    // SSL certificate extraction is costy, initialize on demand
                    pos = getBytesToByteChunk(requestContent, pos, req.sslCert());
                    break;

                case AjpConstants.SC_A_SSL_CIPHER:
                    req.scheme().setString("https");
                    pos = setStringAttributeValue(req,
                            SSLSupport.CIPHER_SUITE_KEY, requestContent, pos);
                    break;

                case AjpConstants.SC_A_SSL_SESSION:
                    req.scheme().setString("https");
                    pos = setStringAttributeValue(req,
                            SSLSupport.SESSION_ID_KEY, requestContent, pos);
                    break;

                case AjpConstants.SC_A_SECRET:
                    pos = getBytesToByteChunk(requestContent, pos, tmpMessageBytes);

                    req.setSecret(tmpMessageBytes.toString());
                    tmpMessageBytes.recycle();

                    break;

                case AjpConstants.SC_A_STORED_METHOD:
                    pos = getBytesToByteChunk(requestContent, pos, req.method());
                    break;

                default:
                    break; // ignore, we don't know about it - backward compat
            }
        }
        
        return pos;
    }

    public static String getString(ByteBuffer buffer) {
        final int length = getShort(buffer);
        final String s = new String(buffer.array(), buffer.position(), length);
        // Don't forget to skip the terminating \0 (that's why "+ 1")
        advance(buffer, length + 1);

        return s;
    }

    public static int decodeHeaders(final Request req, final byte[] buf, int pos) {
        // Decode headers
        final MimeHeaders headers = req.getMimeHeaders();

        final int hCount = getShort(buf, pos);
        pos += 2;

        for (int i = 0; i < hCount; i++) {
            String hName;

            // Header names are encoded as either an integer code starting
            // with 0xA0, or as a normal string (in which case the first
            // two bytes are the length).
            int isc = getShort(buf, pos);
            int hId = isc & 0xFF;

            pos += 2;
            
            MessageBytes valueDC;
            isc &= 0xFF00;
            if (0xA000 == isc) {
                hName = AjpConstants.headerTransArray[hId - 1];
                valueDC = headers.addValue(hName);
            } else {
                // reset hId -- if the header currently being read
                // happens to be 7 or 8 bytes long, the code below
                // will think it's the content-type header or the
                // content-length header - SC_REQ_CONTENT_TYPE=7,
                // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
                // behaviour.  see bug 5861 for more information.
                hId = -1;

                pos -= 2;
                
                final int length = getShort(buf, pos);
                pos += 2;

                valueDC = headers.addValue(buf, pos, length);
                // Don't forget to skip the terminating \0 (that's why "+ 1")
                pos += length + 1;
            }

            pos = getBytesToByteChunk(buf, pos, valueDC);

            // Get the last added header name (the one we need)
            final MessageBytes headerNameDC = headers.getName(headers.size() - 1);

            if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH
                    || hId == -1 && headerNameDC.equalsIgnoreCase("Content-Length")) {
                // just read the content-length header, so set it
                final ByteChunk bc = valueDC.getByteChunk();
                final long cl = Ascii.parseLong(bc.getBytes(), bc.getStart(), bc.getEnd() - bc.getStart());
                if (cl < Integer.MAX_VALUE) {
                    req.setContentLength((int) cl);
                }
            } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE
                    || hId == -1 && headerNameDC.equalsIgnoreCase("Content-Type")) {
                // just read the content-type header, so set it
                req.setContentType(valueDC.toString());
            }
        }
        
        return pos;
    }

    static int getBytesToByteChunk(final byte[] buffer, int pos,
            final MessageBytes bytes) {
        final int length = getShort(buffer, pos);
        pos += 2;
        
        if (length != 0xFFFF) {
            bytes.setBytes(buffer, pos, length);
            // Don't forget to skip the terminating \0 (that's why "+ 1")
            pos += length + 1;
        }
        
        return pos;
    }

    private static void advance(ByteBuffer buffer, int length) {
        buffer.position(buffer.position() + length);
    }

    private static int setStringAttribute(final AjpHttpRequest req,
            final byte[] buffer, int offset) {
        final MessageBytes tmpMessageBytes = req.tmpMessageBytes;

        offset = getBytesToByteChunk(buffer, offset, tmpMessageBytes);
        final String key = tmpMessageBytes.toString();

        tmpMessageBytes.recycle();

        offset = getBytesToByteChunk(buffer, offset, tmpMessageBytes);        
        final String value = tmpMessageBytes.toString();
        
        tmpMessageBytes.recycle();

        req.setAttribute(key, value);
        
        return offset;
    }

    private static int setStringAttributeValue(final AjpHttpRequest req,
            final String key, final byte[] buffer, int pos) {

        final MessageBytes tmpMessageBytes = req.tmpMessageBytes;
        
        pos = getBytesToByteChunk(buffer, pos, tmpMessageBytes);
        final String value = tmpMessageBytes.toString();
        
        tmpMessageBytes.recycle();

        req.setAttribute(key, value);
        return pos;
    }

    public static ByteChunk encodeHeaders(AjpHttpResponse response) {
        try {
            final ByteChunk headerBuffer = response.tmpHeaderByteChunk;
            final int start = headerBuffer.getStart();

            headerBuffer.setEnd(start + 4); // reserve 4 bytes for AJP header
            
            headerBuffer.append(AjpConstants.JK_AJP13_SEND_HEADERS);
            putShort(headerBuffer, (short) response.getStatus());
            String message = null;
            if (response.isAllowCustomReasonPhrase()) {
                message = response.getMessage();
            }

            if (message == null) {
                message = HttpMessages.getMessage(response.getStatus());
            }

            putBytes(headerBuffer, message);

            if (false/*response.isAcknowledgement()*/) {
                // If it's acknowledgment packet - don't encode the headers
                // Serialize 0 num_headers
                putShort(headerBuffer, (short) 0);
            } else {
                final MimeHeaders headers = response.getMimeHeaders();
                final String contentType = response.getContentType();
                if (contentType != null) {
                    headers.setValue("Content-Type").setString(contentType);
                }
                final String contentLanguage = response.getContentLanguage();
                if (contentLanguage != null) {
                    headers.setValue("Content-Language").setString(contentLanguage);
                }
                final long contentLength = response.getContentLength();
                if (contentLength >= 0) {
                    headers.setValue("Content-Length").setLong(contentLength);
                }

                final int numHeaders = headers.size();

                putShort(headerBuffer, (short) numHeaders);

                for (int i = 0; i < numHeaders; i++) {
                    putBytes(headerBuffer, headers.getName(i));
                    putBytes(headerBuffer, headers.getValue(i));
                }
            }

            // Add Ajp message header
            final byte[] headerBytes = headerBuffer.getBuffer();
            headerBytes[start] = ((byte) 'A');
            headerBytes[start + 1] = ((byte) 'B');

            
            putShort(headerBytes, start + 2, (short) (headerBuffer.getLength() - 4));

            return headerBuffer;
        } catch (IOException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }
    private static int skipBytes(final byte[] buffer, int pos) {

        final int length = getShort(buffer, pos);
        pos += 2;
        
        if (!isNullLength(length)) {
            pos += length;
        }

        // Don't forget to skip the terminating \0 (that's why "+ 1")
        pos++;
        
        return pos;
    }

    static int getShort(final ByteBuffer buffer) {
        return buffer.getShort() & 0xFFFF;
    }

    static int getShort(byte[] b, int off) {
	return ((short) (((b[off + 1] & 0xFF)) + 
			((b[off + 0]) << 8))) & 0xFFFF;
    }
    
    static int getInt(byte[] b, int off) {
	return ((b[off + 3] & 0xFF)) +
	       ((b[off + 2] & 0xFF) << 8) +
	       ((b[off + 1] & 0xFF) << 16) +
	       ((b[off + 0]) << 24);
    }
    
    public static void putShort(final ByteChunk chunk, final short value)
            throws IOException {
        chunk.append((byte) (value >> 8));
        chunk.append((byte) (value & 0xFF));
    }

    public static void putShort(final byte[] b, final int off, final short value) {
        b[off] = ((byte) (value >> 8));
        b[off + 1] = ((byte) (value & 0xFF));
    }
    
    private static void putBytes(ByteChunk dstBuffer, final MessageBytes mb)
            throws IOException {
        
        if (mb == null || mb.isNull()) {
            putBytes(dstBuffer, EMPTY_ARRAY);
            return;
        }
        
        if (mb.getType() == MessageBytes.T_BYTES) {
            final ByteChunk bc = mb.getByteChunk();
            putBytes(dstBuffer, bc.getBytes(), bc.getStart(), bc.getLength());
        } else if (mb.getType() == MessageBytes.T_CHARS) {
            final CharChunk cc = mb.getCharChunk();
            putBytes(dstBuffer, cc);
        } else {
            putBytes(dstBuffer, mb.toString());
        }
    }

    private static void putBytes(final ByteChunk dstBuffer, final byte[] bytes)
            throws IOException {
        putBytes(dstBuffer, bytes, 0, bytes.length);
    }

    private static void putBytes(final ByteChunk dstBuffer,
            final byte[] bytes, final int start, final int length)
            throws IOException {
        
        dstBuffer.makeSpace(length + 3);
        
        final byte[] dstArray = dstBuffer.getBuffer();
        int pos = dstBuffer.getEnd();
        putShort(dstArray, pos, (short) length);
        pos += 2;

        System.arraycopy(bytes, start, dstArray, pos, length);
        pos += length;
        
        dstArray[pos++] = 0;

        dstBuffer.setEnd(pos);
    }

    private static void putBytes(final ByteChunk dstBuffer, final CharChunk cc)
            throws IOException {
        
        if (cc == null || cc.isNull()) {
            putBytes(dstBuffer, EMPTY_ARRAY);
            return;
        }
        
        final int length = cc.getLength();
        
        dstBuffer.makeSpace(length + 3);
        
        final byte[] dstArray = dstBuffer.getBuffer();
        int pos = dstBuffer.getEnd();
        
        putShort(dstArray, pos, (short) length);
        pos += 2;
        
        final int start = cc.getStart();
        final int end = cc.getEnd();
        final char[] cbuf = cc.getBuffer();
        for (int i = start; i < end; i++) {
            char c = cbuf[i];
            if ((c <= 31 && c != 9) || c == 127 || c > 255) {
                c = ' ';
            }
            
            dstArray[pos++] = (byte) c;
        }
        
        dstArray[pos++] = 0;
        
        dstBuffer.setEnd(pos);
    }

    private static void putBytes(final ByteChunk dstBuffer, final String s)
            throws IOException {
        
        if (s == null) {
            putBytes(dstBuffer, EMPTY_ARRAY);
            return;
        }
        
        final int length = s.length();
        
        dstBuffer.makeSpace(length + 3);
        
        final byte[] dstArray = dstBuffer.getBuffer();
        int pos = dstBuffer.getEnd();
        
        putShort(dstArray, pos, (short) length);
        pos += 2;
        
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if ((c <= 31 && c != 9) || c == 127 || c > 255) {
                c = ' ';
            }
            
            dstArray[pos++] = (byte) c;
        }
        
        dstArray[pos++] = 0;
        
        dstBuffer.setEnd(pos);
    }

    private static boolean isNullLength(final int length) {
        return length == 0xFFFF || length == -1;
    }
    
    public static byte[] createAjpPacket(final byte type, byte[] payload) {
        final int length = payload.length;
        final byte[] ajpMessage = new byte[5 + length];

        ajpMessage[0] = 'A';
        ajpMessage[1] = 'B';
        putShort(ajpMessage, 2, (short) (length + 1));
        ajpMessage[4] = type;

        System.arraycopy(payload, 0, ajpMessage, 5, length);

        return ajpMessage;
    }

    public static byte[] toBytes(short size) {
        return new byte[]{(byte) (size >> 8), (byte) (size & 0xFF)};
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy