def _writeDict(self, o): """ Write C{dict} to the data stream. @param o: The C{dict} data to be encoded to the AMF0 data stream. """ # For consistency with AMF3, all string indices are output first, # then all integer indices. We don't do any of the other special # handling for integer indices required in AMF3, though. int_items = [] str_items = [] for k, v in six.iteritems(o): if isinstance(k, six.integer_types): int_items.append((six.text_type(k).encode("utf-8"), v)) elif isinstance(k, six.text_type): str_items.append((k.encode("utf-8"), v)) elif isinstance(k, six.binary_type): str_items.append((k, v)) else: raise miniamf.EncodeError( "Unable to encode key %r in dict %r" % (k, o)) int_items.sort() str_items.sort() for k, v in int_items: self.serialiseString(k) self.writeElement(v) for k, v in str_items: self.serialiseString(k) self.writeElement(v)
def convert_Decimal(x, encoder): """ Called when an instance of U{decimal.Decimal<http:// docs.python.org/library/decimal.html#decimal-objects>} is about to be encoded to an AMF stream. @return: If the encoder is in 'strict' mode then C{x} will be converted to a float. Otherwise an L{miniamf.EncodeError} with a friendly message is raised. """ if encoder.strict is False: return float(x) raise miniamf.EncodeError( 'Unable to encode decimal.Decimal instances as there is no way to ' 'guarantee exact conversion. Use strict=False to convert to a float.')
def writeElement(self, data): """ Encodes C{data} to AMF. If the data is not able to be matched to an AMF type, then L{miniamf.EncodeError} will be raised. """ key = type(data) func = None try: func = self._func_cache[key] except KeyError: func = self.getTypeFunc(data) if func is None: raise miniamf.EncodeError('Unable to encode %r (type %r)' % ( data, key)) self._func_cache[key] = func func(data)
def writeDate(self, n): """ Writes a C{datetime} instance to the stream. Does not support C{datetime.time} instances because AMF3 has no way to encode time objects, so please use C{datetime.datetime} instead. @type n: L{datetime} @param n: The C{Date} data to be encoded to the AMF3 data stream. @raise EncodeError: A datetime.time instance was found """ if isinstance(n, datetime.time): raise miniamf.EncodeError( 'A datetime.time instance was found but AMF3 has no way to ' 'encode time objects. Please use datetime.datetime instead ' '(got:%r)' % (n,) ) self.stream.write(TYPE_DATE) ref = self.context.getObjectReference(n) if ref != -1: self._writeInteger(ref << 1) return self.context.addObject(n) self.stream.write_uchar(REFERENCE_BIT) if self.timezone_offset is not None: n -= self.timezone_offset ms = util.get_timestamp(n) self.stream.write_double(ms * 1000.0)
def writeDate(self, d): """ Writes a date to the data stream. @type d: Instance of C{datetime.datetime} @param d: The date to be encoded to the AMF0 data stream. """ if isinstance(d, datetime.time): raise miniamf.EncodeError( 'A datetime.time instance was found but AMF0 has no way to ' 'encode time objects. Please use datetime.datetime instead ' '(got:%r)' % (d, )) # According to the Red5 implementation of AMF0, dates references are # created, but not used. if self.timezone_offset is not None: d -= self.timezone_offset secs = util.get_timestamp(d) tz = 0 self.writeType(TYPE_DATE) self.stream.write_double(secs * 1000.0) self.stream.write_short(tz)
def writeObject(self, obj): """ Writes an object to the stream. """ self.stream.write(TYPE_OBJECT) ref = self.context.getObjectReference(obj) if ref != -1: self._writeInteger(ref << 1) return self.context.addObject(obj) # object is not referenced, serialise it kls = obj.__class__ definition = self.context.getClass(kls) alias = None class_ref = False # if the class definition is a reference if definition: class_ref = True alias = definition.alias else: alias = self.context.getClassAlias(kls) definition = ClassDefinition(alias) self.context.addClass(definition, alias.klass) if class_ref: self.stream.write(definition.reference) else: ref = 0 if definition.encoding != ObjectEncoding.EXTERNAL: ref += definition.attr_len << 4 final_reference = encode_int( ref | definition.encoding << 2 | REFERENCE_BIT << 1 | REFERENCE_BIT ) self.stream.write(final_reference) definition.reference = encode_int( definition.reference << 2 | REFERENCE_BIT) if alias.anonymous: self.stream.write(b'\x01') else: self.serialiseString(alias.alias) # work out what the final reference for the class will be. # this is okay because the next time an object of the same # class is encoded, class_ref will be True and never get here # again. if alias.external: obj.__writeamf__(DataOutput(self)) return attrs = alias.getEncodableAttributes(obj, codec=self) if alias.static_attrs: sattrs = sorted(alias.static_attrs) if not class_ref: for attr in sattrs: self.serialiseString(attr) for attr in sattrs: value = attrs.pop(attr) self.writeElement(value) if definition.encoding == ObjectEncoding.STATIC: return if definition.encoding == ObjectEncoding.DYNAMIC: if attrs: try: keys = sorted(attrs) except TypeError: raise miniamf.EncodeError( 'Unable to encode %r (unsortable attrs: %r)' % (obj, attrs)) for key in keys: value = attrs[key] if isinstance(key, six.integer_types): key = six.text_type(key) elif not isinstance(key, six.string_types): raise miniamf.EncodeError( 'Unable to encode %r (key %r not supported)' % (obj, key)) self.serialiseString(key) self.writeElement(value) self.stream.write(b'\x01')
def writeDict(self, n): """ Writes a C{dict} to the stream. @type n: C{__builtin__.dict} @param n: The C{dict} data to be encoded to the AMF3 data stream. @raise ValueError: Non int/string key value found in the C{dict} @raise EncodeError: C{dict} contains empty string keys. """ # Design bug in AMF3 that cannot read/write empty key strings # for more info if '' in n: raise miniamf.EncodeError("dicts cannot contain empty string keys") self.stream.write(TYPE_ARRAY) ref = self.context.getObjectReference(n) if ref != -1: self._writeInteger(ref << 1) return self.context.addObject(n) # The AMF3 spec demands that all string indices be listed first int_keys = [] str_keys = [] for x in n.keys(): if isinstance(x, six.integer_types): int_keys.append(x) elif isinstance(x, six.string_types): str_keys.append(x) else: raise ValueError("Non integer/string key value found in dict") # Make sure the integer keys are within range l = len(int_keys) for x in int_keys: if l < x <= 0: # treat as a string key str_keys.append(x) del int_keys[int_keys.index(x)] int_keys.sort() # If integer keys don't start at 0, they will be treated as strings if len(int_keys) > 0 and int_keys[0] != 0: for x in int_keys: str_keys.append(six.text_type(x)) del int_keys[int_keys.index(x)] self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT) str_keys.sort() for x in str_keys: self.serialiseString(x) self.writeElement(n[x]) self.stream.write_uchar(0x01) for k in int_keys: self.writeElement(n[k])