/*
* This file is part of the OWASP Proxy, a free intercepting proxy library.
* Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.owasp.proxy.ajp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import org.owasp.proxy.util.AsciiString;
public class AJPMessage {
static final byte[] AJP_CLIENT = { (byte) 0x12, (byte) 0x34 };
static final byte[] AJP_SERVER = { (byte) 0x41, (byte) 0x42 };
/**
* Fixed size buffer.
*/
protected byte buf[];
/**
* The current read or write position in the buffer.
*/
protected int pos;
/**
* This actually means different things depending on whether the packet is
* read or write. For read, it's the length of the payload (excluding the
* header). For write, it's the length of the packet as a whole (counting
* the header). Oh, well.
*/
protected int len;
public AJPMessage(int packetSize) {
buf = new byte[packetSize];
}
/**
* Prepare this packet for accumulating a message to be written. Set the
* write position to just after the header (but leave the length unwritten,
* because it is as yet unknown).
*/
public void reset() {
len = 4;
pos = 4;
Arrays.fill(buf, (byte) 0);
}
public void endClientMessage() {
end(AJP_CLIENT);
}
public void endServerMessage() {
end(AJP_SERVER);
}
/**
* For a packet to be sent, finish the process of accumulating data and
* write the packet signature and the length of the data payload into the
* header.
*/
private void end(byte[] signature) {
len = pos;
int dLen = len - 4;
buf[0] = signature[0];
buf[1] = signature[1];
buf[2] = (byte) ((dLen >>> 8) & 0xFF);
buf[3] = (byte) (dLen & 0xFF);
}
public byte[] toByteArray() {
byte[] arr = new byte[getLen()];
System.arraycopy(buf, 0, arr, 0, getLen());
return arr;
}
/**
* Return the current message length. For read, it's the length of the
* payload (excluding the header). For write, it's the length of the packet
* as a whole (counting the header).
*/
public int getLen() {
return len;
}
/**
* Add a short integer (2 bytes) to the message.
*/
public void appendInt(int val) {
buf[pos++] = (byte) ((val >>> 8) & 0xFF);
buf[pos++] = (byte) (val & 0xFF);
}
/**
* Append a byte (1 byte) to the message.
*/
public void appendByte(int val) {
buf[pos++] = (byte) val;
}
/**
* Append a boolean value to the message.
*/
public void appendBoolean(boolean val) {
buf[pos++] = (byte) (val ? 1 : 0);
}
/**
* Append an int (4 bytes) to the message.
*/
public void appendLongInt(int val) {
buf[pos++] = (byte) ((val >>> 24) & 0xFF);
buf[pos++] = (byte) ((val >>> 16) & 0xFF);
buf[pos++] = (byte) ((val >>> 8) & 0xFF);
buf[pos++] = (byte) (val & 0xFF);
}
/**
* Write a String out at the current write position. Strings are encoded
* with the length in two bytes first, then the string, and then a
* terminating \0 (which is <B>not</B> included in the encoded length). The
* terminator is for the convenience of the C code, where it saves a round
* of copying. A null string is encoded as a string with length 0.
*/
public void appendString(String str) {
int len = str.length();
appendInt(len);
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
// Note: This is clearly incorrect for many strings,
// but is the only consistent approach within the current
// servlet framework. It must suffice until servlet output
// streams properly encode their output.
if ((c <= 31) && (c != 9) && (c != 10) && (c != 13)) {
c = ' ';
} else if (c == 127) {
c = ' ';
}
appendByte(c);
}
appendByte(0);
}
/**
* Copy a chunk of bytes into the packet, starting at the current write
* position. The chunk of bytes is encoded with the length in two bytes
* first, then the data itself, and finally a terminating \0 (which is
* <B>not</B> included in the encoded length).
*
* @param b
* The array from which to copy bytes.
* @param off
* The offset into the array at which to start copying
* @param len
* The number of bytes to copy.
*/
public void appendBytes(byte[] b, int off, int len) {
if (pos + len + 3 > buf.length) {
System.err.println("ajpmessage.overflow " + len + " " + pos);
return;
}
appendInt(len);
System.arraycopy(b, off, buf, pos, len);
pos += len;
appendByte(0);
}
/**
* Utility method to read as many bytes as will fit into the buffer from an
* InputStream, using the AJPMessage buffer as the read destination. This
* eliminates an extra buffer, as well as a copy.
*
* @param in
* @return the number of bytes read from the InputStream
* @throws IOException
*/
public int appendBytes(InputStream in, int max) throws IOException {
max = Math.min(max, buf.length - (pos + 2 + 1));
int read = 0, got;
while (read < max
&& (got = in.read(buf, pos + 2 + read, max - read)) > -1) {
read += got;
}
if (read > 0) {
appendInt(read);
pos += read;
appendByte(0);
}
return read;
}
/**
* Read an integer from packet, and advance the read position past it.
* Integers are encoded as two unsigned bytes with the high-order byte
* first, and, as far as I can tell, in little-endian order within each
* byte.
*/
public int getInt() {
int b1 = buf[pos++] & 0xFF;
int b2 = buf[pos++] & 0xFF;
return (b1 << 8) + b2;
}
public int peekInt() {
int b1 = buf[pos] & 0xFF;
int b2 = buf[pos + 1] & 0xFF;
return (b1 << 8) + b2;
}
public byte getByte() {
byte res = buf[pos++];
return res;
}
public byte peekByte() {
byte res = buf[pos];
return res;
}
public boolean getBoolean() {
byte res = buf[pos++];
return res == 0 ? false : true;
}
/**
* Copy a chunk of bytes from the packet into an array and advance the read
* position past the chunk. See appendBytes() for details on the encoding.
*
* @return The number of bytes copied.
*/
public int getBytes(byte[] dest) {
int length = getInt();
if (pos + length > buf.length) {
System.err.println("ajpmessage.read" + length);
return 0;
}
if ((length == 0xFFFF) || (length == -1)) {
return 0;
}
System.arraycopy(buf, pos, dest, 0, length);
pos += length;
pos++; // Skip terminating \0
return length;
}
/**
* Read a 32 bits integer from packet, and advance the read position past
* it. Integers are encoded as four unsigned bytes with the high-order byte
* first, and, as far as I can tell, in little-endian order within each
* byte.
*/
public int getLongInt() {
int b1 = buf[pos++] & 0xFF; // No swap, Java order
b1 <<= 8;
b1 |= (buf[pos++] & 0xFF);
b1 <<= 8;
b1 |= (buf[pos++] & 0xFF);
b1 <<= 8;
b1 |= (buf[pos++] & 0xFF);
return b1;
}
/**
* Write a String out at the current write position. Strings are encoded
* with the length in two bytes first, then the string, and then a
* terminating \0 (which is <B>not</B> included in the encoded length). The
* terminator is for the convenience of the C code, where it saves a round
* of copying. A null string is encoded as a string with length 0.
*/
public String getString() {
int len = getInt();
if (len == 0xFFFF || len == -1)
return null;
StringBuilder buff = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = (char) getByte();
buff.append(c);
}
byte b;
if ((b = getByte()) != 0)
throw new IllegalStateException(
"Unexpected read! Expected null, got " + b);
return buff.toString();
}
public int getHeaderLength() {
return 4;
}
public int getPacketSize() {
return buf.length;
}
public int processHeader() {
pos = 0;
int mark = getInt();
len = getInt();
// Verify message signature
if ((mark != 0x1234) && (mark != 0x4142)) {
System.err.println("ajpmessage.invalid " + mark);
if (true) {
dump("In: ");
}
return -1;
}
if (false) {
System.err.println("Received " + len + " " + buf[0]);
}
return len;
}
/**
* Dump the contents of the message, prefixed with the given String.
*/
public void dump(String msg) {
if (true) {
System.err.println(msg + ": " + AsciiString.create(buf) + " " + pos
+ "/" + (len + 4));
}
int max = pos;
if (len + 4 > pos)
max = len + 4;
if (max > 1000)
max = 1000;
if (true) {
for (int j = 0; j < max; j += 16) {
System.err.println(hexLine(buf, j, len));
}
}
}
protected static String hexLine(byte buf[], int start, int len) {
StringBuffer sb = new StringBuffer();
for (int i = start; i < start + 16; i++) {
if (i < len + 4) {
sb.append(hex(buf[i]) + " ");
} else {
sb.append(" ");
}
}
sb.append(" | ");
for (int i = start; i < start + 16 && i < len + 4; i++) {
if (!Character.isISOControl((char) buf[i])) {
sb.append(Character.valueOf((char) buf[i]));
} else {
sb.append(".");
}
}
return sb.toString();
}
protected static String hex(int x) {
String h = Integer.toHexString(x);
if (h.length() == 1) {
h = "0" + h;
}
return h.substring(h.length() - 2);
}
/**
* Read an AJP message.
*
* @throws IOException
* any failure, including incomplete reads
*/
public void readMessage(InputStream in) throws IOException {
reset();
read(in, buf, 0, getHeaderLength());
processHeader();
read(in, buf, getHeaderLength(), getLen());
}
/**
* Read at least the specified amount of bytes, and place them in the input
* buffer.
*/
private static void read(InputStream in, byte[] buf, int pos, int n)
throws IOException {
int read = 0;
int res = 0;
while (read < n) {
res = in.read(buf, read + pos, n - read);
if (res > 0) {
read += res;
} else {
throw new IOException("Read failed, got " + read + " of " + n);
}
}
}
public void write(OutputStream out) throws IOException {
out.write(buf, 0, getLen());
out.flush();
}
}