package com.grendelscan.commons.flex; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; import org.w3c.dom.Document; import com.grendelscan.commons.html.HtmlNodeWriter; import com.grendelscan.commons.http.dataHandling.references.DataReference; import com.grendelscan.commons.flex.arrays.AmfAssociativeArrayData; import com.grendelscan.commons.flex.arrays.AmfBooleanArray; import com.grendelscan.commons.flex.arrays.AmfByteArray; import com.grendelscan.commons.flex.arrays.AmfDoubleArray; import com.grendelscan.commons.flex.arrays.AmfIntArray; import com.grendelscan.commons.flex.arrays.AmfMessageBodies; import com.grendelscan.commons.flex.arrays.AmfMessageHeaders; import com.grendelscan.commons.flex.arrays.AmfObjectArray; import com.grendelscan.commons.flex.arrays.AmfPrimitiveArray; import com.grendelscan.commons.flex.complexTypes.AmfASObject; import com.grendelscan.commons.flex.complexTypes.AmfActionMessageRoot; import com.grendelscan.commons.flex.complexTypes.AmfBody; import com.grendelscan.commons.flex.complexTypes.AmfServerSideObject; import com.grendelscan.commons.flex.dataTypeDefinitions.AmfConstants; import com.grendelscan.commons.flex.dataTypeDefinitions.AmfDataType; import com.grendelscan.commons.flex.interfaces.AmfGenericObject; import com.grendelscan.commons.flex.messages.AmfAcknowledgeMessage; import com.grendelscan.commons.flex.messages.AmfActionMessageHeader; import com.grendelscan.commons.flex.messages.AmfAsyncMessage; import com.grendelscan.commons.flex.messages.AmfCommandMessage; import com.grendelscan.commons.flex.messages.AmfErrorMessage; import com.grendelscan.commons.flex.messages.AmfOperation; import com.grendelscan.commons.flex.messages.AmfRemotingMessage; import flex.messaging.MessageException; import flex.messaging.io.amf.ASObject; import flex.messaging.messages.AcknowledgeMessage; import flex.messaging.messages.AsyncMessage; import flex.messaging.messages.CommandMessage; import flex.messaging.messages.ErrorMessage; import flex.messaging.messages.RemotingMessage; public class AmfUtils { /** * The maximum value for an <code>int</code> that will avoid promotion to an ActionScript Number when sent via AMF 3 is 2<sup>28</sup> - 1, or <code>0x0FFFFFFF</code>. */ public final static int INT28_MAX_VALUE = 0x0FFFFFFF; // 2^28 - 1 /** * The minimum value for an <code>int</code> that will avoid promotion to an ActionScript Number when sent via AMF 3 is -2<sup>28</sup> or <code>0xF0000000</code>. */ public final static int INT28_MIN_VALUE = 0xF0000000; // -2^28 in 2^29 scheme /** * Internal use only. * * @exclude */ private static int UINT29_MASK = 0x1FFFFFFF; // 2^29 - 1 public static AbstractAmfData amfFactory(final AmfDataType type, final AbstractAmfDataContainer<?> parent, final DataReference reference, final int transactionId, final AmfActionMessageRoot amfRoot) throws IllegalArgumentException { switch (type) { case kActionMessageHeader: return new AmfActionMessageHeader("", parent, transactionId, amfRoot); case kAmfAsyncMessage: return new AmfAsyncMessage("", false, parent, transactionId); case kAmfBody: return new AmfBody("", "", parent, transactionId, amfRoot); case kAmfCommandMessage: return new AmfCommandMessage("", false, parent, transactionId, amfRoot); case kAmfMessage: throw new IllegalArgumentException("Can't instantiate AmfMessage (it's abstract)"); case kAmfMessageBodies: return new AmfMessageBodies(parent, transactionId, amfRoot); case kAmfMessageHeaders: return new AmfMessageHeaders(parent, transactionId, amfRoot); case kAmfMessageRoot: return new AmfActionMessageRoot(3, transactionId); case kASObject: return new AmfASObject("", parent, transactionId); case kAssociativeArray: return new AmfAssociativeArrayData("", parent, transactionId); case kAvmPlusXml: case kString: case kXML: case kTrue: case kNull: case kInteger: case kDouble: case kBoolean: case kUndefined: case kFalse: return new AmfPrimitiveData("", type, new byte[0], parent, transactionId, true); case kBooleanArray: return new AmfBooleanArray("", parent, transactionId); case kByteArray: return new AmfByteArray("", parent, transactionId); case kCommandType: return new AmfOperation("", parent, transactionId); case kDate: return new AmfDateTime("", 0, parent, transactionId); case kDoubleArray: return new AmfDoubleArray("", parent, transactionId, amfRoot); case kIntArray: return new AmfIntArray("", parent, transactionId); case kObjectArray: return new AmfObjectArray("", parent, transactionId); case kServerSideObject: return new AmfServerSideObject("", parent, transactionId); case kAmfRemotingMessage: return new AmfRemotingMessage("", parent, transactionId); default: throw new IllegalArgumentException("Unknown AMF type: " + type.toString()); } } public static String getAmfValue(final AbstractAmfData data) { String value = null; if (data instanceof AmfPrimitiveData) { value = ((AmfPrimitiveData) data).getValue(); } // else if (data instanceof AmfGenericObject) // { // value = ((AmfGenericObject) data).getClassName(); // } else if (data instanceof AmfOperation) { value = ((AmfOperation) data).getDescription(); } if (value == null) { value = ""; } return value; } private static AmfPrimitiveArray getArray(final Object o, final AbstractAmfDataContainer<?> parent, final int transactionId) { if (o instanceof Boolean) { return new AmfBooleanArray("", parent, transactionId); } else if (o instanceof Integer || o instanceof Short || o instanceof Long) { return new AmfIntArray("", parent, transactionId); } else { return new AmfObjectArray("", parent, transactionId); } } public static AbstractAmfData parseAmfArray(final Object[] objects, final AbstractAmfDataContainer<?> parent, final int transactionId, final boolean mutableChildren) { AbstractAmfData array; if (objects.length > 0) { if (objects[0] instanceof Byte) { byte[] bytes = new byte[objects.length]; for (int i = 0; i < objects.length; i++) { bytes[i] = (Byte) objects[i]; } array = new AmfByteArray("", bytes, parent, transactionId); } else { array = getArray(objects[0], parent, transactionId); for (Object object : objects) { ((AmfPrimitiveArray) array).addChild(parseAmfData(object, parent, transactionId, mutableChildren)); } } } else { array = new AmfObjectArray("", parent, transactionId); } return array; } public static AbstractAmfData parseAmfCollection(final Collection<Object> collection, final AbstractAmfDataContainer<?> parent, final int transactionId, final boolean mutableChildren) { return parseAmfArray(collection.toArray(), parent, transactionId, mutableChildren); } // public static void writeAMFUTF(DataOutputStream outputStream, boolean writeType, String str) throws IOException // { // // int strlen = str.length(); // int utflen = 0; // int c, count = 0; // // char[] charr = new char[strlen]; // str.getChars(0, strlen, charr, 0); // // for (int i = 0; i < strlen; i++) // { // c = charr[i]; // if (c <= 0x007F) // { // utflen++; // } // else if (c > 0x07FF) // { // utflen += 3; // } // else // { // utflen += 2; // } // } // // int type; // // if (forceLong) // // { // // type = kLongStringType; // // } // // else // // { // if (utflen <= 65535) // type = AmfDataType.kStringType; // else // type = AmfDataType.kLongStringType; // // } // // byte[] bytearr; // if (writeType) // { // bytearr = new byte[(utflen + (type == AmfDataType.kStringType ? 3 : 5))]; // bytearr[count++] = (byte)(type); // } // else // bytearr = new byte[(utflen + (type == AmfDataType.kStringType ? 2 : 4))]; // // if (type == AmfDataType.kLongStringType) // { // bytearr[count++] = (byte)((utflen >>> 24) & 0xFF); // bytearr[count++] = (byte)((utflen >>> 16) & 0xFF); // } // // bytearr[count++] = (byte)((utflen >>> 8) & 0xFF); // // bytearr[count++] = (byte)((utflen) & 0xFF); // for (int i = 0; i < strlen; i++) // { // c = charr[i]; // if (c <= 0x007F) // { // bytearr[count++] = (byte)c; // } // else if (c > 0x07FF) // { // bytearr[count++] = (byte)(0xE0 | ((c >> 12) & 0x0F)); // bytearr[count++] = (byte)(0x80 | ((c >> 6) & 0x3F)); // bytearr[count++] = (byte)(0x80 | ((c) & 0x3F)); // } // else // { // bytearr[count++] = (byte)(0xC0 | ((c >> 6) & 0x1F)); // bytearr[count++] = (byte)(0x80 | ((c) & 0x3F)); // } // } // outputStream.write(bytearr, 0, count); // // // // // int strlen = s.length(); // // int utflen = 0; // // int c, count = 0; // // // // char[] charr = new char[strlen]; // // s.getChars(0, strlen, charr, 0); // // // // for (int i = 0; i < strlen; i++) // // { // // c = charr[i]; // // if (c <= 0x007F) // // { // // utflen++; // // } // // else if (c > 0x07FF) // // { // // utflen += 3; // // } // // else // // { // // utflen += 2; // // } // // } // // // // writeUInt29(outputStream, (utflen << 1) | 1); // // // // byte[] bytearr = new byte[utflen]; // // // // for (int i = 0; i < strlen; i++) // // { // // c = charr[i]; // // if (c <= 0x007F) // // { // // bytearr[count++] = (byte) c; // // } // // else if (c > 0x07FF) // // { // // bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); // // bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); // // bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); // // } // // else // // { // // bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); // // bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); // // } // // } // // outputStream.write(bytearr, 0, utflen); // } public static AbstractAmfData parseAmfData(final Object o, final AbstractAmfDataContainer<?> parent, final int transactionId, final boolean mutable) { AbstractAmfData data = null; if (o == null) { data = new AmfPrimitiveData("", AmfDataType.kNull, new byte[0], parent, transactionId, mutable); } else if (o instanceof String || o instanceof Character) { data = new AmfPrimitiveData("", o.toString().getBytes(), parent, transactionId, mutable); } else if (o instanceof Number) { if (o instanceof Integer || o instanceof Short || o instanceof Byte) { int i = ((Number) o).intValue(); data = new AmfPrimitiveData("", i, parent, transactionId, mutable); } else if (o instanceof BigInteger || o instanceof BigDecimal) { // Using double to write big numbers such as BigInteger or // BigDecimal can result in information loss so we write // them as String by default... data = new AmfPrimitiveData("", o.toString().getBytes(), parent, transactionId, mutable); } else { double d = ((Number) o).doubleValue(); data = new AmfPrimitiveData("", d, parent, transactionId, mutable); } } else if (o instanceof Boolean) { data = new AmfPrimitiveData("", ((Boolean) o).booleanValue(), parent, transactionId, mutable); } // We have a complex type... else if (o instanceof Date) { data = new AmfDateTime("", ((Date) o).getTime(), parent, transactionId); } else if (o instanceof Calendar) { data = new AmfDateTime("", ((Calendar) o).getTimeInMillis(), parent, transactionId); } else if (o instanceof Document) { String xml = HtmlNodeWriter.write((Document) o, true, null); data = new AmfPrimitiveData("", AmfDataType.kAvmPlusXml, xml.getBytes(), parent, transactionId, mutable); } else if (o instanceof ASObject) { data = new AmfASObject("", (ASObject) o, parent, transactionId); } else if (o instanceof Map && !(o instanceof ASObject)) { data = parseAmfMap((Map<Object, Object>) o, parent, transactionId, mutable); } else if (o instanceof Collection) { data = parseAmfCollection((Collection<Object>) o, parent, transactionId, mutable); } else if (o instanceof AmfServerSideObject) { AmfServerSideObject sso = (AmfServerSideObject) o; data = sso; sso.initialize(parent, transactionId); } else if (o instanceof CommandMessage) { data = new AmfCommandMessage("", (CommandMessage) o, o.getClass().getCanonicalName(), parent, transactionId); } else if (o instanceof ErrorMessage) { data = new AmfErrorMessage("", (ErrorMessage) o, parent, transactionId); } else if (o instanceof AcknowledgeMessage) { data = new AmfAcknowledgeMessage("", (AcknowledgeMessage) o, o.getClass().getCanonicalName(), parent, transactionId); } else if (o instanceof AsyncMessage) { data = new AmfAsyncMessage("", (AsyncMessage) o, o.getClass().getCanonicalName(), parent, transactionId); } else if (o instanceof RemotingMessage) { data = new AmfRemotingMessage("", (RemotingMessage) o, parent, transactionId); } else if (o.getClass().isArray()) { data = parseAmfArray((Object[]) o, parent, transactionId, mutable); } else { data = new AmfServerSideObject(o, parent, transactionId); // LOGGER.warn("Problem with unknown data type in AmfUtils: " + o.getClass().getCanonicalName() + "\n" + o.toString()); } return data; } // public static void WriteObjectTraits(DataOutputStream outputStream, boolean externalizable, boolean dynamic, int count, String className) // { // writeUInt29(outputStream, 3 | (externalizable ? 4 : 0) | (dynamic ? 8 : 0) | (count << 4)); // writeStringWithoutType(outputStream, className); // // if (!externalizable && propertyNames != null) // { // for (int i = 0; i < count; i++) // { // String propName = (String)ti.getProperty(i); // writeStringWithoutType(outputStream, propName); // } // } // // } public static AmfAssociativeArrayData parseAmfMap(final Map<Object, Object> map, final AbstractAmfDataContainer<?> parent, final int transactionId, final boolean mutableChildren) { AmfAssociativeArrayData data = new AmfAssociativeArrayData("", parent, transactionId); for (Object o : map.keySet()) { String key = o.toString(); data.putChild(key, parseAmfData(map.get(o), parent, transactionId, mutableChildren)); } return data; } public static void setAmfValue(final AbstractAmfData data, final String value) { if (data instanceof AmfPrimitiveData) { ((AmfPrimitiveData) data).setValue(value); } else if (data instanceof AmfGenericObject) { ((AmfGenericObject) data).setClassName(value); } } public static void writeAMFInt(final AmfOutputStream outputStream, int i, final boolean useAmf3Code) throws IOException { if (i >= INT28_MIN_VALUE && i <= INT28_MAX_VALUE) { // We have to be careful when the MSB is set, as (value >> 3) will sign extend. // We know there are only 29-bits of precision, so truncate. This requires // similar care when reading an integer. // i = ((i >> 3) & UINT29_MASK); i = i & UINT29_MASK; // Mask is 2^29 - 1 AmfDataType.kInteger.writeCode(outputStream, useAmf3Code); writeUInt29(outputStream, i); } else { // Promote large int to a double AmfDataType.kDouble.writeCode(outputStream, useAmf3Code); outputStream.writeDouble(i); } } public static void writeAMFUTF(final DataOutputStream outputStream, final boolean writeType, final String str, final boolean useAMF3) throws IOException { if (useAMF3) { writeAmfUTF3(outputStream, writeType, str); } else { writeAmfUTF0(outputStream, writeType, str); } } protected static void writeAmfUTF0(final DataOutputStream outputStream, final boolean writeType, final String str) throws IOException { int strlen = str.length(); int utflen = 0; int c, count = 0; char[] charr = new char[strlen]; str.getChars(0, strlen, charr, 0); for (int i = 0; i < strlen; i++) { c = charr[i]; if (c <= 0x007F) { utflen++; } else if (c > 0x07FF) { utflen += 3; } else { utflen += 2; } } int type; // if (forceLong) // { // type = kLongStringType; // } // else { if (utflen <= 65535) { type = AmfConstants.kStringType; } else { type = AmfConstants.kLongStringType; } } byte[] bytearr; if (writeType) { bytearr = new byte[utflen + (type == AmfConstants.kStringType ? 3 : 5)]; bytearr[count++] = (byte) type; } else { bytearr = new byte[utflen + (type == AmfConstants.kStringType ? 2 : 4)]; } if (type == AmfConstants.kLongStringType) { bytearr[count++] = (byte) (utflen >>> 24 & 0xFF); bytearr[count++] = (byte) (utflen >>> 16 & 0xFF); } bytearr[count++] = (byte) (utflen >>> 8 & 0xFF); bytearr[count++] = (byte) (utflen & 0xFF); for (int i = 0; i < strlen; i++) { c = charr[i]; if (c <= 0x007F) { bytearr[count++] = (byte) c; } else if (c > 0x07FF) { bytearr[count++] = (byte) (0xE0 | c >> 12 & 0x0F); bytearr[count++] = (byte) (0x80 | c >> 6 & 0x3F); bytearr[count++] = (byte) (0x80 | c & 0x3F); } else { bytearr[count++] = (byte) (0xC0 | c >> 6 & 0x1F); bytearr[count++] = (byte) (0x80 | c & 0x3F); } } outputStream.write(bytearr, 0, count); } // public static void writeStringWithoutType(DataOutputStream outputStream, String string) throws IOException // { // if (string.length() == 0) // { // // don't create a reference for the empty string, // // as it's represented by the one byte value 1 // // len = 0, ((len << 1) | 1). // writeUInt29(outputStream, 1); // } // else // { // writeAMFUTF(outputStream, string); // } // } protected static void writeAmfUTF3(final DataOutputStream outputStream, final boolean writeType, final String str) throws IOException { if (writeType) { outputStream.write(AmfConstants.kStringType); } if (str.length() == 0) { // don't create a reference for the empty string, // as it's represented by the one byte value 1 // len = 0, ((len << 1) | 1). writeUInt29(outputStream, 1); return; } int strlen = str.length(); int utflen = 0; int c, count = 0; char[] charr = new char[strlen]; str.getChars(0, strlen, charr, 0); for (int i = 0; i < strlen; i++) { c = charr[i]; if (c <= 0x007F) { utflen++; } else if (c > 0x07FF) { utflen += 3; } else { utflen += 2; } } writeUInt29(outputStream, utflen << 1 | 1); byte[] bytearr = new byte[utflen]; for (int i = 0; i < strlen; i++) { c = charr[i]; if (c <= 0x007F) { bytearr[count++] = (byte) c; } else if (c > 0x07FF) { bytearr[count++] = (byte) (0xE0 | c >> 12 & 0x0F); bytearr[count++] = (byte) (0x80 | c >> 6 & 0x3F); bytearr[count++] = (byte) (0x80 | c >> 0 & 0x3F); } else { bytearr[count++] = (byte) (0xC0 | c >> 6 & 0x1F); bytearr[count++] = (byte) (0x80 | c >> 0 & 0x3F); } } outputStream.write(bytearr, 0, utflen); } public static void writeBoolean(final AmfOutputStream outputStream, final boolean value, final boolean useAmf3Code) throws IOException { if (useAmf3Code) { if (value) { AmfDataType.kTrue.writeCode(outputStream, useAmf3Code); } else { AmfDataType.kFalse.writeCode(outputStream, useAmf3Code); } } else { AmfDataType.kBoolean.writeCode(outputStream, useAmf3Code); outputStream.writeBoolean(value); } } public static void writeUInt29(final DataOutputStream outputStream, final int value) throws IOException { // Represent smaller integers with fewer bytes using the most // significant bit of each byte. The worst case uses 32-bits // to represent a 29-bit number, which is what we would have // done with no compression. // 0x00000000 - 0x0000007F : 0xxxxxxx // 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx // 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx // 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx // 0x40000000 - 0xFFFFFFFF : throw range exception if (value < 0x80) { // 0x00000000 - 0x0000007F : 0xxxxxxx outputStream.writeByte(value); } else if (value < 0x4000) { // 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx outputStream.writeByte(value >> 7 & 0x7F | 0x80); outputStream.writeByte(value & 0x7F); } else if (value < 0x200000) { // 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx outputStream.writeByte(value >> 14 & 0x7F | 0x80); outputStream.writeByte(value >> 7 & 0x7F | 0x80); outputStream.writeByte(value & 0x7F); } else if (value < 0x40000000) { // 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx outputStream.writeByte(value >> 22 & 0x7F | 0x80); outputStream.writeByte(value >> 15 & 0x7F | 0x80); outputStream.writeByte(value >> 8 & 0x7F | 0x80); outputStream.writeByte(value & 0xFF); } else { // 0x40000000 - 0xFFFFFFFF : throw range exception throw new MessageException("Integer out of range: " + value); } } }