io.undertow.protocols.ajp.AjpResponseParser Maven / Gradle / Ivy
/*
* 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