diff --git a/cpyamf/amf3.pxd b/cpyamf/amf3.pxd index a32ef444..f7cd9e4f 100644 --- a/cpyamf/amf3.pxd +++ b/cpyamf/amf3.pxd @@ -47,10 +47,16 @@ cdef class Decoder(codec.Decoder): cdef int _readStatic(self, ClassDefinition class_def, dict obj) except -1 cdef int _readDynamic(self, ClassDefinition class_def, dict obj) except -1 + cdef object readASDictionary(self) cdef object readBytes(self) cdef object readInteger(self, int signed=?) cdef object readByteArray(self) + cdef object readDoubleVector(self) + cdef object readIntVector(self) + cdef object readObjectVector(self) cdef object readProxy(self, obj) + cdef object readUintVector(self) + cdef object readVector(self, vector_class) cdef class Encoder(codec.Encoder): @@ -59,3 +65,5 @@ cdef class Encoder(codec.Encoder): cdef int writeByteArray(self, object obj) except -1 cdef int writeProxy(self, obj) except -1 + cdef int writeVector(self, object obj) except -1 + cdef int writeASDictionary(self, object obj) except -1 diff --git a/cpyamf/amf3.pyx b/cpyamf/amf3.pyx index 8b5c29d6..428206b7 100644 --- a/cpyamf/amf3.pyx +++ b/cpyamf/amf3.pyx @@ -38,6 +38,11 @@ cdef char TYPE_ARRAY = '\x09' cdef char TYPE_OBJECT = '\x0A' cdef char TYPE_XMLSTRING = '\x0B' cdef char TYPE_BYTEARRAY = '\x0C' +cdef char TYPE_INT_VECTOR = '\x0D' +cdef char TYPE_UINT_VECTOR = '\x0E' +cdef char TYPE_DOUBLE_VECTOR = '\x0F' +cdef char TYPE_OBJECT_VECTOR = '\x10' +cdef char TYPE_DICTIONARY = '\x11' cdef unsigned int REFERENCE_BIT = 0x01 cdef char REF_CHAR = '\x01' @@ -54,6 +59,12 @@ cdef int OBJECT_ENCODING_DYNAMIC = 0x02 cdef int OBJECT_ENCODING_PROXY = 0x03 cdef object ByteArrayType = amf3.ByteArray +cdef object BaseVectorType = amf3.BaseVector +cdef object IntVectorType = amf3.IntVector +cdef object UintVectorType = amf3.UintVector +cdef object DoubleVectorType = amf3.DoubleVector +cdef object ObjectVectorType = amf3.ObjectVector +cdef object ASDictionaryType = amf3.ASDictionary cdef object DataInput = amf3.DataInput cdef object DataOutput = amf3.DataOutput cdef str empty_string = str('') @@ -563,6 +574,62 @@ cdef class Decoder(codec.Decoder): """ return self.context.getObjectForProxy(obj) + cdef object readVector(self, vector_class): + """ + Reads a vector of a specific datatype. + + @note: This is not supported in ActionScript 1.0, 2.0 or early + versions of 3.0. + """ + cdef int ref = _read_ref(self.stream) + cdef int i + + if ref & REFERENCE_BIT == 0: + return self.context.getObject(ref >> 1) + + cdef object obj = vector_class() + obj.fixed = self.stream.read_uchar() + + if isinstance(obj, ObjectVectorType): + obj.classname = self.readString() + + cdef int num_items = ref >> 1 + for 0 <= i < num_items: + obj.append(obj.reader(self)()) + + return obj + + cdef object readIntVector(self): + return self.readVector(IntVectorType) + + cdef object readUintVector(self): + return self.readVector(UintVectorType) + + cdef object readDoubleVector(self): + return self.readVector(DoubleVectorType) + + cdef object readObjectVector(self): + return self.readVector(ObjectVectorType) + + cdef object readASDictionary(self): + cdef int ref = _read_ref(self.stream) + cdef int i + assert ref & REFERENCE_BIT + + ref >>= 1 + + result = ASDictionaryType() + weak_keys = self.stream.read_uchar() + assert weak_keys in [0x00, 0x01] + result.weak_keys = bool(weak_keys) + + for 0 <= i < ref: + key = self.readElement() + value = self.readElement() + result[key] = value + + return result + cdef object readConcreteElement(self, char t): if t == TYPE_STRING: return self.readString() @@ -590,6 +657,16 @@ cdef class Decoder(codec.Decoder): return self.readXML() elif t == TYPE_XMLSTRING: return self.readXML() + elif t == TYPE_INT_VECTOR: + return self.readIntVector() + elif t == TYPE_UINT_VECTOR: + return self.readUintVector() + elif t == TYPE_DOUBLE_VECTOR: + return self.readDoubleVector() + elif t == TYPE_OBJECT_VECTOR: + return self.readObjectVector() + elif t == TYPE_DICTIONARY: + return self.readASDictionary() raise pyamf.DecodeError("Unsupported ActionScript type") @@ -1030,12 +1107,50 @@ cdef class Encoder(codec.Encoder): return self.writeObject(proxy, 1) + cdef int writeVector(self, object n) except -1: + cdef Py_ssize_t ref = self.context.getObjectReference(n) + + self.writeType(ord(n.datatype)) + + if ref != -1: + _encode_integer(self.stream, ref << 1) + return 0 + + self.context.addObject(n) + _encode_integer(self.stream, (len(n) << 1) | REFERENCE_BIT) + self.stream.write_uchar(1 if n.fixed else 0) + + if isinstance(n, ObjectVectorType): + self.serialiseString(n.classname) + + for item in n: + n.writer(self)(item) + + cdef int writeASDictionary(self, object n) except -1: + self.writeType(TYPE_DICTIONARY) + _encode_integer(self.stream, (len(n) << 1) | REFERENCE_BIT) + self.stream.write_uchar(1 if n.weak_keys else 0) + + for key, value in n.iteritems(): + self.writeElement(key) + self.writeElement(value) + cdef int handleBasicTypes(self, object element, object py_type) except -1: cdef int ret = codec.Encoder.handleBasicTypes(self, element, py_type) if ret == 1: # not handled if py_type is ByteArrayType: return self.writeByteArray(element) + elif py_type is IntVectorType: + return self.writeVector(element) + elif py_type is UintVectorType: + return self.writeVector(element) + elif py_type is DoubleVectorType: + return self.writeVector(element) + elif py_type is ObjectVectorType: + return self.writeVector(element) + elif py_type is ASDictionaryType: + return self.writeASDictionary(element) return ret diff --git a/pyamf/amf3.py b/pyamf/amf3.py index d2413f9d..4ac94272 100644 --- a/pyamf/amf3.py +++ b/pyamf/amf3.py @@ -109,6 +109,14 @@ #: @see: U{Parsing ByteArrays on OSFlash (external) #: } TYPE_BYTEARRAY = '\x0C' +#: Vector types were added to ActionScript 3.0 in Flash Player 10. +TYPE_INT_VECTOR = '\x0D' +TYPE_UINT_VECTOR = '\x0E' +TYPE_DOUBLE_VECTOR = '\x0F' +TYPE_OBJECT_VECTOR = '\x10' +#: A native Dictionary type with object keys and values was added in +#: Flash Player 10 as well. +TYPE_DICTIONARY = '\x11' #: Reference bit. REFERENCE_BIT = 0x01 @@ -555,6 +563,74 @@ def compress(self): self.compressed = True +class BaseVector(list): + fixed = False + + def __repr__(self): + return "%s(%s, %s)" % ( + self.__class__.__name__, + super(BaseVector, self).__repr__(), + self._get_attributes()) + + def _get_attributes(self): + return 'fixed=%s' % repr(self.fixed) + + +class IntVector(BaseVector): + datatype = TYPE_INT_VECTOR + + def reader(self, decoder): + return decoder.stream.read_long + + def writer(self, encoder): + return encoder.stream.write_long + + +class UintVector(BaseVector): + datatype = TYPE_UINT_VECTOR + + def reader(self, decoder): + return decoder.stream.read_ulong + + def writer(self, encoder): + return encoder.stream.write_ulong + + +class DoubleVector(BaseVector): + datatype = TYPE_DOUBLE_VECTOR + + def reader(self, decoder): + return decoder.stream.read_double + + def writer(self, encoder): + return encoder.stream.write_double + + +class ObjectVector(BaseVector): + classname = None + datatype = TYPE_OBJECT_VECTOR + + def reader(self, decoder): + return decoder.readElement + + def writer(self, encoder): + return encoder.writeElement + + def _get_attributes(self): + return ( + super(ObjectVector, self)._get_attributes() + + ', classname=' + repr(self.classname)) + + +class ASDictionary(dict): + weak_keys = False + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + super(ASDictionary, self).__repr__()) + + class ClassDefinition(object): """ This is an internal class used by L{Encoder}/L{Decoder} to hold details @@ -795,6 +871,16 @@ def getTypeFunc(self, data): return self.readXMLString elif data == TYPE_BYTEARRAY: return self.readByteArray + elif data == TYPE_INT_VECTOR: + return self.readIntVector + elif data == TYPE_UINT_VECTOR: + return self.readUintVector + elif data == TYPE_DOUBLE_VECTOR: + return self.readDoubleVector + elif data == TYPE_OBJECT_VECTOR: + return self.readObjectVector + elif data == TYPE_DICTIONARY: + return self.readASDictionary def readProxy(self, obj): """ @@ -956,6 +1042,27 @@ def readArray(self): return result + def readASDictionary(self): + length, is_reference = self._readLength() + + # AS3 does not allow Dictionary types to have references + assert not is_reference + + result = ASDictionary() + + # Set weak-keys property + weak_keys = self.stream.read_uchar() + assert weak_keys in [0x00, 0x01] + result.weak_keys = bool(weak_keys) + + # Read key-value pairs + for i in xrange(length): + key = self.readElement() + value = self.readElement() + result[key] = value + + return result + def _getClassDefinition(self, ref): """ Reads class definition from the stream. @@ -1128,6 +1235,47 @@ def readByteArray(self): return obj + def readVector(self, vector_class): + """ + Reads an array of a specific datatype. + + @see: L{TypedVector} + @note: This is not supported in ActionScript 1.0, 2.0, or early + versions of 3.0 + """ + ref = self.readInteger(False) + + if ref & REFERENCE_BIT == 0: + return self.context.getObject(ref >> 1) + + obj = vector_class() + + # This metadata indicates whether the (de)serialized object should + # use a fixed memory allocation. For python, this is semi-pointless, + # but we will need it to properly re-serialize the data. + obj.fixed = self.stream.read_uchar() + + if isinstance(obj, ObjectVector): + obj.classname = self.readString() + + num_items = ref >> 1 + for i in xrange(num_items): + obj.append(obj.reader(self)()) + + return obj + + def readIntVector(self): + return self.readVector(IntVector) + + def readUintVector(self): + return self.readVector(UintVector) + + def readDoubleVector(self): + return self.readVector(DoubleVector) + + def readObjectVector(self): + return self.readVector(ObjectVector) + class Encoder(codec.Encoder): """ @@ -1155,6 +1303,10 @@ def getTypeFunc(self, data): return self.writeByteArray elif t is pyamf.MixedArray: return self.writeDict + elif isinstance(data, BaseVector): + return self.writeVector + elif t is ASDictionary: + return self.writeASDictionary return codec.Encoder.getTypeFunc(self, data) @@ -1531,6 +1683,40 @@ def writeByteArray(self, n): self._writeInteger(l << 1 | REFERENCE_BIT) self.stream.write(buf) + def writeVector(self, n): + assert isinstance(n, BaseVector) + + self.stream.write(n.datatype) + + ref = self.context.getObjectReference(n) + + if ref != -1: + self._writeInteger(ref << 1) + + return + + self.context.addObject(n) + + self._writeInteger(len(n) << 1 | REFERENCE_BIT) + self.stream.write_uchar(1 if n.fixed else 0) + + if isinstance(n, ObjectVector): + self.writeString(n.classname) + + for item in n: + n.writer(self)(item) + + def writeASDictionary(self, n): + assert isinstance(n, ASDictionary) + + self.stream.write(TYPE_DICTIONARY) + self._writeInteger(len(n) << 1 | REFERENCE_BIT) + self.stream.write_uchar(0x01 if n.weak_keys else 0x00) + + for key, value in n.iteritems(): + self.writeElement(key) + self.writeElement(value) + def writeXML(self, n): """ Writes a XML string to the data stream.