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

io.undertow.protocols.ajp.AjpResponseParser Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.protocols.ajp;

import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;

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

import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_END_RESPONSE;
import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_REQUEST_BODY_CHUNK;
import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_BODY_CHUNK;
import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_HEADERS;

/**
 * Parser used for the client (i.e. load balancer) side of the AJP connection.
 *
 * @author Stuart Douglas
 */
class AjpResponseParser {

    public static final AjpResponseParser INSTANCE = new AjpResponseParser();

    private static final int AB = ('A' << 8) + 'B';

    //states
    public static final int BEGIN = 0;
    public static final int READING_MAGIC_NUMBER = 1;
    public static final int READING_DATA_SIZE = 2;
    public static final int READING_PREFIX_CODE = 3;
    public static final int READING_STATUS_CODE = 4;
    public static final int READING_REASON_PHRASE = 5;
    public static final int READING_NUM_HEADERS = 6;
    public static final int READING_HEADERS = 7;
    public static final int READING_PERSISTENT_BOOLEAN = 8;
    public static final int READING_BODY_CHUNK_LENGTH = 9;
    public static final int DONE = 10;

    //parser states
    int state;
    byte prefix;
    int numHeaders = 0;
    HttpString currentHeader;

    //final states
    int statusCode;
    String reasonPhrase;
    HeaderMap headers = new HeaderMap();
    int readBodyChunkSize;

    public boolean isComplete() {
        return state == DONE;
    }

    public void parse(final ByteBuffer buf) throws IOException {
        if (!buf.hasRemaining()) {
            return;
        }
        switch (this.state) {
            case BEGIN: {
                IntegerHolder result = parse16BitInteger(buf);
                if (!result.readComplete) {
                    return;
                } else {
                    if (result.value != AB) {
                        throw new IOException("Wrong magic number");
                    }
                }
            }
            case READING_DATA_SIZE: {
                IntegerHolder result = parse16BitInteger(buf);
                if (!result.readComplete) {
                    this.state = READING_DATA_SIZE;
                    return;
                }
            }
            case READING_PREFIX_CODE: {
                if (!buf.hasRemaining()) {
                    this.state = READING_PREFIX_CODE;
                    return;
                } else {
                    final byte prefix = buf.get();
                    this.prefix = prefix;
                    if (prefix == FRAME_TYPE_END_RESPONSE) {
                        this.state = READING_PERSISTENT_BOOLEAN;
                        break;
                    } else if (prefix == FRAME_TYPE_SEND_BODY_CHUNK) {
                        this.state = READING_BODY_CHUNK_LENGTH;
                        break;
                    } else if (prefix != FRAME_TYPE_SEND_HEADERS && prefix != FRAME_TYPE_REQUEST_BODY_CHUNK) {
                        this.state = DONE;
                        return;
                    }
                }
            }
            case READING_STATUS_CODE: {
                //this state is overloaded for the request size
                //when reading state=6 (read_body_chunk requests)

                IntegerHolder result = parse16BitInteger(buf);
                if (result.readComplete) {
                    if (this.prefix == FRAME_TYPE_SEND_HEADERS) {
                        statusCode = result.value;
                    } else {
                        //read body chunk or end result
                        //a bit hacky
                        this.state = DONE;
                        this.readBodyChunkSize = result.value;
                        return;
                    }
                } else {
                    this.state = READING_STATUS_CODE;
                    return;
                }
            }
            case READING_REASON_PHRASE: {
                StringHolder result = parseString(buf, false);
                if (result.readComplete) {
                    reasonPhrase = result.value;
                } else {
                    this.state = READING_REASON_PHRASE;
                    return;
                }
            }
            case READING_NUM_HEADERS: {
                IntegerHolder result = parse16BitInteger(buf);
                if (!result.readComplete) {
                    this.state = READING_NUM_HEADERS;
                    return;
                } else {
                    this.numHeaders = result.value;
                }
            }
            case READING_HEADERS: {
                int readHeaders = this.readHeaders;
                while (readHeaders < this.numHeaders) {
                    if (this.currentHeader == null) {
                        StringHolder result = parseString(buf, true);
                        if (!result.readComplete) {
                            this.state = READING_HEADERS;
                            this.readHeaders = readHeaders;
                            return;
                        }
                        if (result.header != null) {
                            this.currentHeader = result.header;
                        } else {
                            this.currentHeader = HttpString.tryFromString(result.value);
                        }
                    }
                    StringHolder result = parseString(buf, false);
                    if (!result.readComplete) {
                        this.state = READING_HEADERS;
                        this.readHeaders = readHeaders;
                        return;
                    }
                    headers.add(this.currentHeader, result.value);
                    this.currentHeader = null;
                    ++readHeaders;
                }
                break;
            }
        }

        if (state == READING_PERSISTENT_BOOLEAN) {
            if (!buf.hasRemaining()) {
                return;
            }
            currentIntegerPart = buf.get();
            this.state = DONE;
            return;
        } else if (state == READING_BODY_CHUNK_LENGTH) {
            IntegerHolder result = parse16BitInteger(buf);
            if (result.readComplete) {
                this.currentIntegerPart = result.value;
                this.state = DONE;
            }
            return;
        } else {
            this.state = DONE;
        }
    }

    protected HttpString headers(int offset) {
        return AjpConstants.HTTP_HEADERS_ARRAY[offset];
    }

    public HeaderMap getHeaders() {
        return headers;
    }

    public int getStatusCode() {
        return statusCode;
    }

    public String getReasonPhrase() {
        return reasonPhrase;
    }

    public int getReadBodyChunkSize() {
        return readBodyChunkSize;
    }

    public static final int STRING_LENGTH_MASK = 1 << 31;

    /**
     * The length of the string being read
     */
    public int stringLength = -1;

    /**
     * The current string being read
     */
    public StringBuilder currentString;

    /**
     * when reading the first byte of an integer this stores the first value. It is set to -1 to signify that
     * the first byte has not been read yet.
     */
    public int currentIntegerPart = -1;
    boolean containsUrlCharacters = false;
    public int readHeaders = 0;

    public void reset() {

        state = 0;
        prefix = 0;
        numHeaders = 0;
        currentHeader = null;

        statusCode = 0;
        reasonPhrase = null;
        headers = new HeaderMap();
        stringLength = -1;
        currentString = null;
        currentIntegerPart = -1;
        readHeaders = 0;
    }

    protected IntegerHolder parse16BitInteger(ByteBuffer buf) {
        if (!buf.hasRemaining()) {
            return new IntegerHolder(-1, false);
        }
        int number = this.currentIntegerPart;
        if (number == -1) {
            number = (buf.get() & 0xFF);
        }
        if (buf.hasRemaining()) {
            final byte b = buf.get();
            int result = ((0xFF & number) << 8) + (b & 0xFF);
            this.currentIntegerPart = -1;
            return new IntegerHolder(result, true);
        } else {
            this.currentIntegerPart = number;
            return new IntegerHolder(-1, false);
        }
    }

    protected StringHolder parseString(ByteBuffer buf, boolean header) {
        boolean containsUrlCharacters = this.containsUrlCharacters;
        if (!buf.hasRemaining()) {
            return new StringHolder(null, false, false);
        }
        int stringLength = this.stringLength;
        if (stringLength == -1) {
            int number = buf.get() & 0xFF;
            if (buf.hasRemaining()) {
                final byte b = buf.get();
                stringLength = ((0xFF & number) << 8) + (b & 0xFF);
            } else {
                this.stringLength = number | STRING_LENGTH_MASK;
                return new StringHolder(null, false, false);
            }
        } else if ((stringLength & STRING_LENGTH_MASK) != 0) {
            int number = stringLength & ~STRING_LENGTH_MASK;
            stringLength = ((0xFF & number) << 8) + (buf.get() & 0xFF);
        }
        if (header && (stringLength & 0xFF00) != 0) {
            this.stringLength = -1;
            return new StringHolder(headers(stringLength & 0xFF));
        }
        if (stringLength == 0xFFFF) {
            //OxFFFF means null
            this.stringLength = -1;
            return new StringHolder(null, true, false);
        }
        StringBuilder builder = this.currentString;

        if (builder == null) {
            builder = new StringBuilder();
            this.currentString = builder;
        }
        int length = builder.length();
        while (length < stringLength) {
            if (!buf.hasRemaining()) {
                this.stringLength = stringLength;
                this.containsUrlCharacters = containsUrlCharacters;
                return new StringHolder(null, false, false);
            }
            char c = (char) buf.get();
            if(c == '+' || c == '%') {
                containsUrlCharacters = true;
            }
            builder.append(c);
            ++length;
        }

        if (buf.hasRemaining()) {
            buf.get(); //null terminator
            this.currentString = null;
            this.stringLength = -1;
            this.containsUrlCharacters = false;
            return new StringHolder(builder.toString(), true, containsUrlCharacters);
        } else {
            this.stringLength = stringLength;
            this.containsUrlCharacters = containsUrlCharacters;
            return new StringHolder(null, false, false);
        }
    }

    protected static class IntegerHolder {
        public final int value;
        public final boolean readComplete;

        private IntegerHolder(int value, boolean readComplete) {
            this.value = value;
            this.readComplete = readComplete;
        }
    }

    protected static class StringHolder {
        public final String value;
        public final HttpString header;
        public final boolean readComplete;
        public final boolean containsUrlCharacters;

        private StringHolder(String value, boolean readComplete, boolean containsUrlCharacters) {
            this.value = value;
            this.readComplete = readComplete;
            this.containsUrlCharacters = containsUrlCharacters;
            this.header = null;
        }

        private StringHolder(HttpString value) {
            this.value = null;
            this.readComplete = true;
            this.header = value;
            this.containsUrlCharacters = false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy