| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 flex.messaging.io.amf; |
| |
| import java.io.IOException; |
| import java.io.UTFDataFormatException; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import flex.messaging.io.PropertyProxy; |
| import flex.messaging.io.SerializationContext; |
| import flex.messaging.io.SerializationException; |
| import flex.messaging.io.UnknownTypeException; |
| import flex.messaging.util.ClassUtil; |
| |
| /** |
| * An Amf0 input object. |
| * |
| */ |
| public class Amf0Input extends AbstractAmfInput implements AmfTypes |
| { |
| /** |
| * Unfortunately the Flash Player starts AMF 3 messages off with the legacy |
| * AMF 0 format and uses a type, AmfTypes.kAvmPlusObjectType, to indicate |
| * that the next object in the stream is to be deserialized differently. The |
| * original hope was for two independent encoding versions... but for now |
| * we just keep a reference to objectInput here. |
| * |
| */ |
| protected ActionMessageInput avmPlusInput; |
| |
| /** |
| * |
| */ |
| protected List objectsTable; |
| |
| public Amf0Input(SerializationContext context) |
| { |
| super(context); |
| |
| objectsTable = new ArrayList(64); |
| } |
| |
| /** |
| * Clear all object reference information so that the instance |
| * can be used to deserialize another data structure. |
| * |
| * Reset should be called before reading a top level object, |
| * such as a new header or a new body. |
| */ |
| @Override |
| public void reset() |
| { |
| super.reset(); |
| |
| objectsTable.clear(); |
| |
| if (avmPlusInput != null) |
| avmPlusInput.reset(); |
| } |
| |
| |
| // |
| // java.io.ObjectInput SERIALIZATION IMPLEMENTATIONS |
| // |
| |
| /** |
| * Public entry point to read a top level AMF Object, such as |
| * a header value or a message body. |
| */ |
| public Object readObject() throws ClassNotFoundException, IOException |
| { |
| int type = in.readByte(); |
| |
| Object value = readObjectValue(type); |
| return value; |
| } |
| |
| protected Object readObjectValue(int type) throws ClassNotFoundException, IOException |
| { |
| Object value = null; |
| switch (type) |
| { |
| case kNumberType: |
| value = Double.valueOf(readDouble()); |
| break; |
| |
| case kBooleanType: |
| value = Boolean.valueOf(readBoolean()); |
| break; |
| |
| case kStringType: |
| value = readString(); |
| break; |
| |
| case kAvmPlusObjectType: |
| |
| if (avmPlusInput == null) |
| { |
| avmPlusInput = new Amf3Input(context); |
| avmPlusInput.setDebugTrace(trace); |
| avmPlusInput.setInputStream(in); |
| } |
| value = avmPlusInput.readObject(); |
| break; |
| |
| case kStrictArrayType: |
| value = readArrayValue(); |
| break; |
| |
| case kTypedObjectType: |
| String typeName = in.readUTF(); |
| value = readObjectValue(typeName); |
| break; |
| |
| case kLongStringType: |
| ClassUtil.validateCreation(String.class); |
| |
| value = readLongUTF(); |
| if (isDebug) |
| trace.writeString((String)value); |
| break; |
| |
| case kObjectType: |
| value = readObjectValue(null); |
| break; |
| |
| case kXMLObjectType: |
| value = readXml(); |
| break; |
| |
| case kNullType: |
| if (isDebug) |
| trace.writeNull(); |
| break; |
| |
| case kDateType: |
| value = readDate(); |
| break; |
| |
| case kECMAArrayType: |
| value = readECMAArrayValue(); |
| break; |
| |
| case kReferenceType: |
| int refNum = in.readUnsignedShort(); |
| |
| if (isDebug) |
| trace.writeRef(refNum); |
| |
| value = objectsTable.get(refNum); |
| break; |
| |
| case kUndefinedType: |
| |
| if (isDebug) |
| trace.writeUndefined(); |
| break; |
| |
| case kUnsupportedType: |
| |
| if (isDebug) |
| trace.write("UNSUPPORTED"); |
| |
| //Unsupported type found in AMF stream. |
| UnknownTypeException ex = new UnknownTypeException(); |
| ex.setMessage(10302); |
| throw ex; |
| |
| case kObjectEndType: |
| |
| if (isDebug) |
| trace.write("UNEXPECTED OBJECT END"); |
| |
| //Unexpected object end tag in AMF stream. |
| UnknownTypeException ex1 = new UnknownTypeException(); |
| ex1.setMessage(10303); |
| throw ex1; |
| |
| case kRecordsetType: |
| |
| if (isDebug) |
| trace.write("UNEXPECTED RECORDSET"); |
| |
| //AMF Recordsets are not supported. |
| UnknownTypeException ex2 = new UnknownTypeException(); |
| ex2.setMessage(10304); |
| throw ex2; |
| |
| default: |
| |
| if (isDebug) |
| trace.write("UNKNOWN TYPE"); |
| |
| UnknownTypeException ex3 = new UnknownTypeException(); |
| ex3.setMessage(10301, new Object[]{new Integer(type)}); |
| throw ex3; |
| } |
| return value; |
| } |
| |
| protected Date readDate() throws IOException |
| { |
| ClassUtil.validateCreation(Date.class); |
| |
| long time = (long)in.readDouble(); |
| /* |
| We read in the timezone but do nothing with the value as |
| we expect dates to be written in the UTC timezone. Client |
| and servers are responsible for applying their own |
| timezones. |
| */ |
| in.readShort(); |
| |
| Date d = new Date(time); |
| |
| if (isDebug) |
| trace.write(d.toString()); |
| |
| return d; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean readBoolean() throws IOException |
| { |
| ClassUtil.validateCreation(Boolean.class); |
| |
| boolean b = super.readBoolean(); |
| if (isDebug) |
| trace.write(b); |
| return b; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public double readDouble() throws IOException |
| { |
| ClassUtil.validateCreation(Double.class); |
| |
| double d = super.readDouble(); |
| if (isDebug) |
| trace.write(d); |
| return d; |
| } |
| |
| /** |
| * Deserialize the bits of an ECMA array w/o a prefixing type byte. |
| */ |
| protected Map readECMAArrayValue() throws ClassNotFoundException, IOException |
| { |
| ClassUtil.validateCreation(HashMap.class); |
| |
| int size = in.readInt(); |
| HashMap h; |
| if (size == 0) |
| { |
| h = new HashMap(); |
| } |
| else |
| { |
| int initialCapacity = size < INITIAL_COLLECTION_CAPACITY? size : INITIAL_COLLECTION_CAPACITY; |
| h = new HashMap(initialCapacity); |
| } |
| |
| rememberObject(h); |
| |
| if (isDebug) |
| trace.startECMAArray(objectsTable.size() - 1); |
| |
| String name = in.readUTF(); |
| int type = in.readByte(); |
| while (type != kObjectEndType) |
| { |
| if (type != kObjectEndType) |
| { |
| if (isDebug) |
| trace.namedElement(name); |
| |
| // Always read value but be careful to ignore erroneous 'length' prop that is sometimes sent by the player. |
| Object value = readObjectValueOneLevelDown(type, true); |
| if (!name.equals("length")) |
| { |
| ClassUtil.validateAssignment(h, name, value); |
| h.put(name, value); |
| } |
| } |
| |
| name = in.readUTF(); |
| type = in.readByte(); |
| } |
| |
| if (isDebug) |
| trace.endAMFArray(); |
| |
| return h; |
| } |
| |
| protected String readString() throws IOException |
| { |
| ClassUtil.validateCreation(String.class); |
| |
| String s = readUTF(); |
| if (isDebug) |
| trace.writeString(s); |
| return s; |
| } |
| |
| |
| /** |
| * Deserialize the bits of an array w/o a prefixing type byte. |
| */ |
| protected Object readArrayValue() throws ClassNotFoundException, IOException |
| { |
| int size = in.readInt(); |
| // Don't instantiate List/Array right away with the supplied size if it is more than |
| // INITIAL_COLLECTION_CAPACITY in case the supplied size has been tampered. |
| boolean useListTemporarily = false; |
| Object l; |
| if (context.legacyCollection || size > INITIAL_COLLECTION_CAPACITY) |
| { |
| useListTemporarily = !context.legacyCollection; |
| ClassUtil.validateCreation(ArrayList.class); |
| int initialCapacity = size < INITIAL_COLLECTION_CAPACITY? size : INITIAL_COLLECTION_CAPACITY; |
| l = new ArrayList(initialCapacity); |
| } |
| else |
| { |
| ClassUtil.validateCreation(Object[].class); |
| l = new Object[size]; |
| } |
| int objectId = rememberObject(l); // Remember the List/Object[]. |
| |
| if (isDebug) |
| trace.startAMFArray(objectsTable.size() - 1); |
| |
| for (int i = 0; i < size; ++i) |
| { |
| if (isDebug) |
| trace.arrayElement(i); |
| |
| // Add value to the array |
| int type = in.readByte(); |
| Object value = readObjectValueOneLevelDown(type, true); |
| ClassUtil.validateAssignment(l, i, value); |
| if (l instanceof ArrayList) |
| ((ArrayList)l).add(value); |
| else |
| Array.set(l, i, value); |
| } |
| |
| if (isDebug) |
| trace.endAMFArray(); |
| |
| if (useListTemporarily) |
| { |
| l = ((ArrayList)l).toArray(); |
| objectsTable.set(objectId, l); |
| } |
| |
| return l; |
| } |
| |
| /** |
| * Deserialize the bits of a map w/o a prefixing type byte. |
| */ |
| protected Object readObjectValue(String className) throws ClassNotFoundException, IOException |
| { |
| // Prepare the parameters for createObjectInstance(). Use an array as a holder |
| // to simulate two 'by-reference' parameters className and (initially null) proxy |
| Object[] params = new Object[] {className, null}; |
| Object object = createObjectInstance(params); |
| |
| // Retrieve any changes to the className and the proxy parameters |
| className = (String)params[0]; |
| PropertyProxy proxy = (PropertyProxy)params[1]; |
| |
| int objectId = rememberObject(object); |
| |
| if (isDebug) |
| trace.startAMFObject(className, objectsTable.size() - 1); |
| |
| boolean isCollectionClass = isCollectionClass(object); |
| String propertyName = in.readUTF(); |
| int type = in.readByte(); |
| while (type != kObjectEndType) |
| { |
| if (isDebug) |
| trace.namedElement(propertyName); |
| Object value = readObjectValueOneLevelDown(type, isCollectionClass); |
| proxy.setValue(object, propertyName, value); |
| propertyName = in.readUTF(); |
| type = in.readByte(); |
| } |
| |
| if (isDebug) |
| trace.endAMFObject(); |
| |
| // This lets the BeanProxy substitute a new instance into the BeanProxy |
| // at the end of the serialization. You might for example create a Map, store up |
| // the properties, then construct the instance based on that. Note that this does |
| // not support recursive references to the parent object however. |
| Object newObj = proxy.instanceComplete(object); |
| |
| // TODO: It is possible we gave out references to the |
| // temporary object. it would be possible to warn users about |
| // that problem by tracking if we read any references to this object |
| // in the readObject call above. |
| if (newObj != object) |
| { |
| objectsTable.set(objectId, newObj); |
| object = newObj; |
| } |
| |
| return object; |
| } |
| |
| /** |
| * This code borrows heavily from DataInputStreat.readUTF(). |
| * However, it uses a 32-bit string length. |
| * |
| * @return the read String |
| * @throws java.io.UTFDataFormatException if the UTF-8 encoding is incorrect |
| * @throws IOException if an I/O error occurs. |
| */ |
| protected String readLongUTF() throws IOException |
| { |
| int utflen = in.readInt(); |
| checkUTFLength(utflen); |
| |
| int c, char2, char3; |
| char[] charr = getTempCharArray(utflen); |
| byte bytearr [] = getTempByteArray(utflen); |
| int count = 0; |
| int chCount = 0; |
| |
| in.readFully(bytearr, 0, utflen); |
| |
| while (count < utflen) |
| { |
| c = (int)bytearr[count] & 0xff; |
| switch (c >> 4) |
| { |
| case 0: |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| case 6: |
| case 7: |
| /* 0xxxxxxx*/ |
| count++; |
| charr[chCount] = (char)c; |
| break; |
| case 12: |
| case 13: |
| /* 110x xxxx 10xx xxxx*/ |
| count += 2; |
| if (count > utflen) |
| throw new UTFDataFormatException(); |
| char2 = (int)bytearr[count - 1]; |
| if ((char2 & 0xC0) != 0x80) |
| throw new UTFDataFormatException(); |
| charr[chCount] = (char)(((c & 0x1F) << 6) | (char2 & 0x3F)); |
| break; |
| case 14: |
| /* 1110 xxxx 10xx xxxx 10xx xxxx */ |
| count += 3; |
| if (count > utflen) |
| throw new UTFDataFormatException(); |
| char2 = (int)bytearr[count - 2]; |
| char3 = (int)bytearr[count - 1]; |
| if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) |
| throw new UTFDataFormatException(); |
| charr[chCount] = (char) |
| (((c & 0x0F) << 12) | |
| ((char2 & 0x3F) << 6) | |
| ((char3 & 0x3F) << 0)); |
| break; |
| default: |
| /* 10xx xxxx, 1111 xxxx */ |
| throw new UTFDataFormatException(); |
| } |
| chCount++; |
| } |
| // The number of chars produced may be less than utflen |
| return new String(charr, 0, chCount); |
| } |
| |
| protected Object readXml() throws IOException |
| { |
| String xml = readLongUTF(); |
| |
| if (isDebug) |
| trace.write(xml); |
| |
| return stringToDocument(xml); |
| } |
| |
| |
| /** |
| * Remember a deserialized object so that you can use it later through a reference. |
| */ |
| protected int rememberObject(Object obj) |
| { |
| int id = objectsTable.size(); |
| objectsTable.add(obj); |
| return id; |
| } |
| |
| protected Object readObjectValueOneLevelDown(int type, boolean nestCollectionLevelDown) throws ClassNotFoundException, IOException |
| { |
| increaseNestObjectLevel(); |
| if (nestCollectionLevelDown) |
| increaseNestCollectionLevel(); |
| Object value = readObjectValue(type); |
| decreaseNestObjectLevel(); |
| if (nestCollectionLevelDown) |
| decreaseNestCollectionLevel(); |
| return value; |
| } |
| } |