Package pyamf :: Module amf0

[hide private]
[frames] | no frames]

# Source Code for Module pyamf.amf0

      1  # -*- coding: utf-8 -*-
      2  #
      3  # Copyright (c) 2007-2009 The PyAMF Project.
      4  # See LICENSE.txt for details.
      5
      6  """
      7  AMF0 implementation.
      8
      9  C{AMF0} supports the basic data types used for the NetConnection, NetStream,
     10  LocalConnection, SharedObjects and other classes in the Adobe Flash Player.
     11
     12  @see: U{Official AMF0 Specification in English (external)
     13  <http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf>}
     14  @see: U{Official AMF0 Specification in Japanese (external)
     15  <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf0_spec_121207.pdf>}
     16  @see: U{AMF documentation on OSFlash (external)
     17  <http://osflash.org/documentation/amf>}
     18
     19  @since: 0.1
     20  """
     21
     22  import datetime
     23  import types
     24  import copy
     25
     26  import pyamf
     27  from pyamf import util
     28
     29
     30  #: Represented as 9 bytes: 1 byte for C{0×00} and 8 bytes a double
     31  #: representing the value of the number.
     32  TYPE_NUMBER      = '\x00'
     33  #: Represented as 2 bytes: 1 byte for C{0×01} and a second, C{0×00}
     34  #: for C{False}, C{0×01} for C{True}.
     35  TYPE_BOOL        = '\x01'
     36  #: Represented as 3 bytes + len(String): 1 byte C{0×02}, then a UTF8 string,
     37  #: including the top two bytes representing string length as a C{int}.
     38  TYPE_STRING      = '\x02'
     39  #: Represented as 1 byte, C{0×03}, then pairs of UTF8 string, the key, and
     40  #: an AMF element, ended by three bytes, C{0×00} C{0×00} C{0×09}.
     41  TYPE_OBJECT      = '\x03'
     42  #: MovieClip does not seem to be supported by Remoting.
     43  #: It may be used by other AMF clients such as SharedObjects.
     44  TYPE_MOVIECLIP   = '\x04'
     45  #: 1 single byte, C{0×05} indicates null.
     46  TYPE_NULL        = '\x05'
     47  #: 1 single byte, C{0×06} indicates null.
     48  TYPE_UNDEFINED   = '\x06'
     49  #: When an ActionScript object refers to itself, such C{this.self = this},
     50  #: or when objects are repeated within the same scope (for example, as the
     51  #: two parameters of the same function called), a code of C{0×07} and an
     52  #: C{int}, the reference number, are written.
     53  TYPE_REFERENCE   = '\x07'
     54  #: A MixedArray is indicated by code C{0×08}, then a Long representing the
     55  #: highest numeric index in the array, or 0 if there are none or they are
     56  #: all negative. After that follow the elements in key : value pairs.
     57  TYPE_MIXEDARRAY  = '\x08'
     58  #: @see: L{TYPE_OBJECT}
     59  TYPE_OBJECTTERM  = '\x09'
     60  #: An array is indicated by C{0x0A}, then a Long for array length, then the
     61  #: array elements themselves. Arrays are always sparse; values for
     62  #: inexistant keys are set to null (C{0×06}) to maintain sparsity.
     63  TYPE_ARRAY       = '\x0A'
     64  #: Date is represented as C{00x0B}, then a double, then an C{int}. The double
     65  #: represents the number of milliseconds since 01/01/1970. The C{int} represents
     66  #: the timezone offset in minutes between GMT. Note for the latter than values
     67  #: greater than 720 (12 hours) are represented as M{2^16} - the value. Thus GMT+1
     68  #: is 60 while GMT-5 is 65236.
     69  TYPE_DATE        = '\x0B'
     70  #: LongString is reserved for strings larger then M{2^16} characters long. It
     71  #: is represented as C{00x0C} then a LongUTF.
     72  TYPE_LONGSTRING  = '\x0C'
     73  #: Trying to send values which don’t make sense, such as prototypes, functions,
     74  #: built-in objects, etc. will be indicated by a single C{00x0D} byte.
     75  TYPE_UNSUPPORTED = '\x0D'
     76  #: Remoting Server -> Client only.
     77  #: @see: L{RecordSet}
     78  #: @see: U{RecordSet structure on OSFlash (external)
     79  #: <http://osflash.org/documentation/amf/recordset>}
     80  TYPE_RECORDSET   = '\x0E'
     81  #: The XML element is indicated by C{00x0F} and followed by a LongUTF containing
     82  #: the string representation of the XML object. The receiving gateway may which
     83  #: to wrap this string inside a language-specific standard XML object, or simply
     84  #: pass as a string.
     85  TYPE_XML         = '\x0F'
     86  #: A typed object is indicated by C{0×10}, then a UTF string indicating class
     87  #: name, and then the same structure as a normal C{0×03} Object. The receiving
     88  #: gateway may use a mapping scheme, or send back as a vanilla object or
     89  #: associative array.
     90  TYPE_TYPEDOBJECT = '\x10'
     91  #: An AMF message sent from an AVM+ client such as the Flash Player 9 may break
     92  #: out into L{AMF3<pyamf.amf3>} mode. In this case the next byte will be the
     93  #: AMF3 type code and the data will be in AMF3 format until the decoded object
     94  #: reaches it’s logical conclusion (for example, an object has no more keys).
     95  TYPE_AMF3        = '\x11'
     96
     97
     98 -class Context(pyamf.BaseContext):
     99      """
    100      I hold the AMF0 context for en/decoding streams.
    101
    102      AMF0 object references start at index 1.
    103
    104      @ivar amf3_objs: A list of objects that have been decoded in
    105          L{AMF3<pyamf.amf3>}.
    106      @type amf3_objs: L{util.IndexedCollection}
    107      """
    108
    109 -    def __init__(self, **kwargs):
    110          self.amf3_objs = []
    111
    112          pyamf.BaseContext.__init__(self, **kwargs)
    113
    114 -    def clear(self):
    115          """
    116          Clears the context.
    117          """
    118          pyamf.BaseContext.clear(self)
    119
    120          self.amf3_objs = []
    121
    122          if hasattr(self, 'amf3_context'):
    123              self.amf3_context.clear()
    124
    125 -    def hasAMF3ObjectReference(self, obj):
    126          """
    127          Gets a reference for an object.
    128
    129          @raise ReferenceError: Unknown AMF3 object reference.
    130          """
    131          return obj in self.amf3_objs
    132          o = self.amf3_objs.getReferenceTo(obj)
    133
    134          if o is None and self.exceptions:
    135              raise pyamf.ReferenceError(
    136                  'Unknown AMF3 reference for (%r)' % (obj,))
    137
    138          return o
    139
    140 -    def addAMF3Object(self, obj):
    141          """
    142          Adds an AMF3 reference to C{obj}.
    143
    144          @type obj: C{mixed}
    145          @param obj: The object to add to the context.
    146          @rtype: C{int}
    147          @return: Reference to C{obj}.
    148          """
    149          return self.amf3_objs.append(obj)
    150
    151 -    def __copy__(self):
    152          cpy = self.__class__(exceptions=self.exceptions)
    153          cpy.amf3_objs = copy.copy(self.amf3_objs)
    154
    155          return cpy
    156
    157
    158 -class Decoder(pyamf.BaseDecoder):
    159      """
    160      Decodes an AMF0 stream.
    161      """
    162
    163      context_class = Context
    164
    165      # XXX nick: Do we need to support TYPE_MOVIECLIP here?
    166      type_map = {
    167          TYPE_NUMBER:     'readNumber',
    168          TYPE_BOOL:       'readBoolean',
    169          TYPE_STRING:     'readString',
    170          TYPE_OBJECT:     'readObject',
    171          TYPE_NULL:       'readNull',
    172          TYPE_UNDEFINED:  'readUndefined',
    173          TYPE_REFERENCE:  'readReference',
    174          TYPE_MIXEDARRAY: 'readMixedArray',
    175          TYPE_ARRAY:      'readList',
    176          TYPE_DATE:       'readDate',
    177          TYPE_LONGSTRING: 'readLongString',
    178          # TODO: do we need a special value here?
    179          TYPE_UNSUPPORTED:'readNull',
    180          TYPE_XML:        'readXML',
    181          TYPE_TYPEDOBJECT:'readTypedObject',
    182          TYPE_AMF3:       'readAMF3'
    183      }
    184
    185 -    def readNumber(self):
    186          """
    187          Reads a ActionScript C{Number} value.
    188
    189          In ActionScript 1 and 2 the C{NumberASTypes} type represents all numbers,
    190          both floats and integers.
    191
    192          @rtype: C{int} or C{float}
    193          """
    194          return _check_for_int(self.stream.read_double())
    195
    196 -    def readBoolean(self):
    197          """
    198          Reads a ActionScript C{Boolean} value.
    199
    200          @rtype: C{bool}
    201          @return: Boolean.
    202          """
    203          return bool(self.stream.read_uchar())
    204
    205 -    def readNull(self):
    206          """
    207          Reads a ActionScript C{null} value.
    208
    209          @return: C{None}
    210          @rtype: C{None}
    211          """
    212          return None
    213
    214 -    def readUndefined(self):
    215          """
    216          Reads an ActionScript C{undefined} value.
    217
    218          @return: L{Undefined<pyamf.Undefined>}
    219          """
    220          return pyamf.Undefined
    221
    222 -    def readMixedArray(self):
    223          """
    224          Read mixed array.
    225
    226          @rtype: C{dict}
    227          @return: C{dict} read from the stream
    228          """
    229          len = self.stream.read_ulong()
    230          obj = pyamf.MixedArray()
    231          self.context.addObject(obj)
    232          self._readObject(obj)
    233          ikeys = []
    234
    235          for key in obj.keys():
    236              try:
    237                  ikey = int(key)
    238                  ikeys.append((key, ikey))
    239                  obj[ikey] = obj[key]
    240                  del obj[key]
    241              except ValueError:
    242                  # XXX: do we want to ignore this?
    243                  pass
    244
    245          ikeys.sort()
    246
    247          return obj
    248
    249 -    def readList(self):
    250          """
    251          Read a C{list} from the data stream.
    252
    253          @rtype: C{list}
    254          @return: C{list}
    255          """
    256          obj = []
    257          self.context.addObject(obj)
    258          len = self.stream.read_ulong()
    259
    260          for i in xrange(len):
    261              obj.append(self.readElement())
    262
    263          return obj
    264
    265 -    def readTypedObject(self):
    266          """
    267          Reads an ActionScript object from the stream and attempts to
    268          'cast' it.
    269
    270          @see: L{load_class<pyamf.load_class>}
    271          """
    272          classname = self.readString()
    273          alias = None
    274
    275          try:
    276              alias = pyamf.get_class_alias(classname)
    277
    278              ret = alias.createInstance(codec=self)
    279          except pyamf.UnknownClassAlias:
    280              if self.strict:
    281                  raise
    282
    283              ret = pyamf.TypedObject(classname)
    284
    285          self.context.addObject(ret)
    286          self._readObject(ret, alias)
    287
    288          return ret
    289
    290 -    def readAMF3(self):
    291          """
    292          Read AMF3 elements from the data stream.
    293
    294          @rtype: C{mixed}
    295          @return: The AMF3 element read from the stream
    296          """
    297          if not hasattr(self.context, 'amf3_context'):
    298              self.context.amf3_context = pyamf.get_context(pyamf.AMF3, exceptions=False)
    299
    300          if not hasattr(self.context, 'amf3_decoder'):
    301              self.context.amf3_decoder = pyamf.get_decoder(
    302                  pyamf.AMF3, self.stream, self.context.amf3_context)
    303
    304          decoder = self.context.amf3_decoder
    305          element = decoder.readElement()
    306          self.context.addAMF3Object(element)
    307
    308          return element
    309
    310 -    def readString(self):
    311          """
    312          Reads a string from the data stream.
    313
    314          @rtype: C{str}
    315          @return: string
    316          """
    317          len = self.stream.read_ushort()
    318          return self.stream.read_utf8_string(len)
    319
    320 -    def _readObject(self, obj, alias=None):
    321          obj_attrs = dict()
    322
    323          key = self.readString().encode('utf8')
    324
    325          while self.stream.peek() != TYPE_OBJECTTERM:
    326              obj_attrs[key] = self.readElement()
    327              key = self.readString().encode('utf8')
    328
    329          # discard the end marker (TYPE_OBJECTTERM)
    330          self.stream.read(1)
    331
    332          if alias:
    333              alias.applyAttributes(obj, obj_attrs, codec=self)
    334          else:
    335              util.set_attrs(obj, obj_attrs)
    336
    337 -    def readObject(self):
    338          """
    339          Reads an object from the data stream.
    340
    341          @rtype: L{ASObject<pyamf.ASObject>}
    342          """
    343          obj = pyamf.ASObject()
    344          self.context.addObject(obj)
    345
    346          self._readObject(obj)
    347
    348          return obj
    349
    350 -    def readReference(self):
    351          """
    352          Reads a reference from the data stream.
    353
    354          @raise pyamf.ReferenceError: Unknown reference.
    355          """
    356          idx = self.stream.read_ushort()
    357
    358          o = self.context.getObject(idx)
    359
    360          if o is None:
    361              raise pyamf.ReferenceError('Unknown reference %d' % (idx,))
    362
    363          return o
    364
    365 -    def readDate(self):
    366          """
    367          Reads a UTC date from the data stream. Client and servers are
    368          responsible for applying their own timezones.
    369
    370          Date: C{0x0B T7 T6} .. C{T0 Z1 Z2 T7} to C{T0} form a 64 bit
    371          Big Endian number that specifies the number of nanoseconds
    372          that have passed since 1/1/1970 0:00 to the specified time.
    373          This format is UTC 1970. C{Z1} and C{Z0} for a 16 bit Big
    374          Endian number indicating the indicated time's timezone in
    375          minutes.
    376          """
    377          ms = self.stream.read_double() / 1000.0
    378          tz = self.stream.read_short()
    379
    380          # Timezones are ignored
    381          d = util.get_datetime(ms)
    382
    383          if self.timezone_offset:
    384              d = d + self.timezone_offset
    385
    386          self.context.addObject(d)
    387
    388          return d
    389
    390 -    def readLongString(self):
    391          """
    392          Read UTF8 string.
    393          """
    394          len = self.stream.read_ulong()
    395
    396          return self.stream.read_utf8_string(len)
    397
    398 -    def readXML(self):
    399          """
    400          Read XML.
    401          """
    402          data = self.readLongString()
    403          xml = util.ET.fromstring(data)
    404          self.context.addObject(xml)
    405
    406          return xml
    407
    408
    409 -class Encoder(pyamf.BaseEncoder):
    410      """
    411      Encodes an AMF0 stream.
    412
    413      @ivar use_amf3: A flag to determine whether this encoder knows about AMF3.
    414      @type use_amf3: C{bool}
    415      """
    416
    417      context_class = Context
    418
    419      type_map = [
    420          ((types.BuiltinFunctionType, types.BuiltinMethodType,
    421              types.FunctionType, types.GeneratorType, types.ModuleType,
    422              types.LambdaType, types.MethodType), "writeFunc"),
    423          ((types.NoneType,), "writeNull"),
    424          ((bool,), "writeBoolean"),
    425          ((int,long,float), "writeNumber"),
    426          ((types.StringTypes,), "writeString"),
    427          ((pyamf.ASObject,), "writeObject"),
    428          ((pyamf.MixedArray,), "writeMixedArray"),
    429          ((types.ListType, types.TupleType,), "writeArray"),
    430          ((datetime.date, datetime.datetime, datetime.time), "writeDate"),
    431          ((util.is_ET_element,), "writeXML"),
    432          ((lambda x: x is pyamf.Undefined,), "writeUndefined"),
    433          ((types.ClassType, types.TypeType), "writeClass"),
    434          ((types.InstanceType,types.ObjectType,), "writeObject"),
    435      ]
    436
    437 -    def __init__(self, *args, **kwargs):
    438          self.use_amf3 = kwargs.pop('use_amf3', False)
    439
    440          pyamf.BaseEncoder.__init__(self, *args, **kwargs)
    441
    442 -    def writeType(self, t):
    443          """
    444          Writes the type to the stream.
    445
    446          @type   t: C{str}
    447          @param  t: ActionScript type.
    448
    449          @raise pyamf.EncodeError: AMF0 type is not recognized.
    450          """
    451          self.stream.write(t)
    452
    453 -    def writeUndefined(self, data):
    454          """
    455          Writes the L{undefined<TYPE_UNDEFINED>} data type to the stream.
    456
    457          @param data: The C{undefined} data to be encoded to the AMF0 data
    458              stream.
    459          @type data: C{undefined} data
    460          """
    461          self.writeType(TYPE_UNDEFINED)
    462
    463 -    def writeClass(self, *args, **kwargs):
    464          """
    465          Classes cannot be serialised.
    466          """
    467          raise pyamf.EncodeError("Class objects cannot be serialised")
    468
    469 -    def writeFunc(self, *args, **kwargs):
    470          """
    471          Functions cannot be serialised.
    472          """
    473          raise pyamf.EncodeError("Callables cannot be serialised")
    474
    475 -    def writeUnsupported(self, data):
    476          """
    477          Writes L{unsupported<TYPE_UNSUPPORTED>} data type to the
    478          stream.
    479
    480          @param data: The C{unsupported} data to be encoded to the AMF0
    481              data stream.
    482          @type data: C{unsupported} data
    483          """
    484          self.writeType(TYPE_UNSUPPORTED)
    485
    486 -    def _writeElementFunc(self, data):
    487          """
    488          Gets a function based on the type of data.
    489
    490          @see: L{pyamf.BaseEncoder._writeElementFunc}
    491          """
    492          # There is a very specific use case that we must check for.
    493          # In the context there is an array of amf3_objs that contain
    494          # references to objects that are to be encoded in amf3.
    495
    496          if self.use_amf3 and self.context.hasAMF3ObjectReference(data):
    497              return self.writeAMF3
    498
    499          return pyamf.BaseEncoder._writeElementFunc(self, data)
    500
    501 -    def writeElement(self, data):
    502          """
    503          Writes the data.
    504
    505          @type data: C{mixed}
    506          @param data: The data to be encoded to the AMF0 data stream.
    507          @raise EncodeError: Cannot find encoder func.
    508          """
    509          func = self._writeElementFunc(data)
    510
    511          if func is None:
    512              raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,))
    513
    514          func(data)
    515
    516 -    def writeNull(self, n):
    517          """
    518          Write null type to data stream.
    519
    520          @type   n: C{None}
    521          @param  n: Is ignored.
    522          """
    523          self.writeType(TYPE_NULL)
    524
    525 -    def writeArray(self, a):
    526          """
    527          Write array to the stream.
    528
    529          @type a: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    530          @param a: The array data to be encoded to the AMF0 data stream.
    531          """
    532          alias = self.context.getClassAlias(a.__class__)
    533
    534          if alias.external:
    535              # a is a subclassed list with a registered alias - push to the
    536              # correct method
    537              self.writeObject(a)
    538
    539              return
    540
    541          if self.writeReference(a) is not None:
    542              return
    543
    544          self.context.addObject(a)
    545
    546          self.writeType(TYPE_ARRAY)
    547          self.stream.write_ulong(len(a))
    548
    549          for data in a:
    550              self.writeElement(data)
    551
    552 -    def writeNumber(self, n):
    553          """
    554          Write number to the data stream.
    555
    556          @type   n: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    557          @param  n: The number data to be encoded to the AMF0 data stream.
    558          """
    559          self.writeType(TYPE_NUMBER)
    560          self.stream.write_double(float(n))
    561
    562 -    def writeBoolean(self, b):
    563          """
    564          Write boolean to the data stream.
    565
    566          @type b: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    567          @param b: The boolean data to be encoded to the AMF0 data stream.
    568          """
    569          self.writeType(TYPE_BOOL)
    570
    571          if b:
    572              self.stream.write_uchar(1)
    573          else:
    574              self.stream.write_uchar(0)
    575
    576 -    def _writeString(self, s):
    577          l = len(s)
    578
    579          if l > 0xffff:
    580              self.stream.write_ulong(l)
    581          else:
    582              self.stream.write_ushort(l)
    583
    584          self.stream.write(s)
    585
    586 -    def writeString(self, s, writeType=True):
    587          """
    588          Write string to the data stream.
    589
    590          @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    591          @param s: The string data to be encoded to the AMF0 data stream.
    592          @type writeType: C{bool}
    593          @param writeType: Write data type.
    594          """
    595          t = type(s)
    596
    597          if t is str:
    598              pass
    599          elif isinstance(s, unicode):
    600              s = s.encode('utf8')
    601          elif not isinstance(s, basestring):
    602              s = unicode(s).encode('utf8')
    603
    604          if writeType:
    605              if len(s) > 0xffff:
    606                  self.writeType(TYPE_LONGSTRING)
    607              else:
    608                  self.writeType(TYPE_STRING)
    609
    610          self._writeString(s)
    611
    612 -    def writeReference(self, o):
    613          """
    614          Write reference to the data stream.
    615
    616          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    617          @param o: The reference data to be encoded to the AMF0 data
    618              stream.
    619          """
    620          idx = self.context.getObjectReference(o)
    621
    622          if idx is None:
    623              return None
    624
    625          self.writeType(TYPE_REFERENCE)
    626          self.stream.write_ushort(idx)
    627
    628          return idx
    629
    630 -    def _writeDict(self, o):
    631          """
    632          Write C{dict} to the data stream.
    633
    634          @type o: C{iterable}
    635          @param o: The C{dict} data to be encoded to the AMF0 data
    636              stream.
    637          """
    638          for key, val in o.iteritems():
    639              self.writeString(key, False)
    640              self.writeElement(val)
    641
    642 -    def writeMixedArray(self, o):
    643          """
    644          Write mixed array to the data stream.
    645
    646          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    647          @param o: The mixed array data to be encoded to the AMF0
    648              data stream.
    649          """
    650          if self.writeReference(o) is not None:
    651              return
    652
    653          self.context.addObject(o)
    654          self.writeType(TYPE_MIXEDARRAY)
    655
    656          # TODO: optimise this
    657          # work out the highest integer index
    658          try:
    659              # list comprehensions to save the day
    660              max_index = max([y[0] for y in o.items()
    661                  if isinstance(y[0], (int, long))])
    662
    663              if max_index < 0:
    664                  max_index = 0
    665          except ValueError:
    666              max_index = 0
    667
    668          self.stream.write_ulong(max_index)
    669
    670          self._writeDict(o)
    671          self._writeEndObject()
    672
    673 -    def _writeEndObject(self):
    674          self.stream.write('\x00\x00')
    675          self.writeType(TYPE_OBJECTTERM)
    676
    677 -    def writeObject(self, o):
    678          """
    679          Write object to the stream.
    680
    681          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    682          @param o: The object data to be encoded to the AMF0 data stream.
    683          """
    684          if self.use_amf3:
    685              self.writeAMF3(o)
    686
    687              return
    688
    689          if self.writeReference(o) is not None:
    690              return
    691
    692          self.context.addObject(o)
    693          alias = self.context.getClassAlias(o.__class__)
    694
    695          alias.compile()
    696
    697          if alias.amf3:
    698              self.writeAMF3(o)
    699
    700              return
    701
    702          if alias.anonymous:
    703              self.writeType(TYPE_OBJECT)
    704          else:
    705              self.writeType(TYPE_TYPEDOBJECT)
    706              self.writeString(alias.alias, False)
    707
    708          sa, da = alias.getEncodableAttributes(o, codec=self)
    709
    710          if sa:
    711              for key in alias.static_attrs:
    712                  self.writeString(key, False)
    713                  self.writeElement(sa[key])
    714
    715          if da:
    716              for key, value in da.iteritems():
    717                  self.writeString(key, False)
    718                  self.writeElement(value)
    719
    720          self._writeEndObject()
    721
    722 -    def writeDate(self, d):
    723          """
    724          Writes a date to the data stream.
    725
    726          @type d: Instance of C{datetime.datetime}
    727          @param d: The date to be encoded to the AMF0 data stream.
    728          """
    729          if isinstance(d, datetime.time):
    730              raise pyamf.EncodeError('A datetime.time instance was found but '
    731                  'AMF0 has no way to encode time objects. Please use '
    732                  'datetime.datetime instead (got:%r)' % (d,))
    733
    734          # According to the Red5 implementation of AMF0, dates references are
    735          # created, but not used.
    736          if self.timezone_offset is not None:
    737              d -= self.timezone_offset
    738
    739          secs = util.get_timestamp(d)
    740          tz = 0
    741
    742          self.writeType(TYPE_DATE)
    743          self.stream.write_double(secs * 1000.0)
    744          self.stream.write_short(tz)
    745
    746 -    def writeXML(self, e):
    747          """
    748          Write XML to the data stream.
    749
    750          @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    751          @param e: The XML data to be encoded to the AMF0 data stream.
    752          """
    753          if self.use_amf3 is True:
    754              self.writeAMF3(e)
    755
    756              return
    757
    758          self.writeType(TYPE_XML)
    759
    760          data = util.ET.tostring(e, 'utf-8')
    761          self.stream.write_ulong(len(data))
    762          self.stream.write(data)
    763
    764 -    def writeAMF3(self, data):
    765          """
    766          Writes an element to the datastream in L{AMF3<pyamf.amf3>} format.
    767
    768          @type data: C{mixed}
    769          @param data: The data to be encoded to the AMF0 data stream.
    770          """
    771          if not hasattr(self.context, 'amf3_context'):
    772              self.context.amf3_context = pyamf.get_context(pyamf.AMF3, exceptions=False)
    773
    774          if not hasattr(self.context, 'amf3_encoder'):
    775              self.context.amf3_encoder = pyamf.get_encoder(
    776                  pyamf.AMF3, self.stream, self.context.amf3_context)
    777
    778          self.context.addAMF3Object(data)
    779          encoder = self.context.amf3_encoder
    780
    781          self.writeType(TYPE_AMF3)
    782          encoder.writeElement(data)
    783
    784
    785 -def decode(*args, **kwargs):
    786      """
    787      A helper function to decode an AMF0 datastream.
    788      """
    789      decoder = Decoder(*args, **kwargs)
    790
    791      while 1:
    792          try:
    793              yield decoder.readElement()
    794          except pyamf.EOStream:
    795              break
    796
    797
    798 -def encode(*args, **kwargs):
    799      """
    800      A helper function to encode an element into the AMF0 format.
    801
    802      @type element: C{mixed}
    803      @keyword element: The element to encode
    804      @type context: L{Context<pyamf.amf0.Context>}
    805      @keyword context: AMF0 C{Context} to use for the encoding. This holds
    806          previously referenced objects etc.
    807      @rtype: C{StringIO}
    808      @return: The encoded stream.
    809      """
    810      encoder = Encoder(**kwargs)
    811
    812      for element in args:
    813          encoder.writeElement(element)
    814
    815      return encoder.stream
    816
    817
    818 -class RecordSet(object):
    819      """
    820      I represent the C{RecordSet} class used in Adobe Flash Remoting to hold
    821      (amongst other things) SQL records.
    822
    823      @ivar columns: The columns to send.
    824      @type columns: List of strings.
    825      @ivar items: The C{RecordSet} data.
    826      @type items: List of lists, the order of the data corresponds to the order
    827          of the columns.
    828      @ivar service: Service linked to the C{RecordSet}.
    829      @type service:
    830      @ivar id: The id of the C{RecordSet}.
    831      @type id: C{str}
    832
    833      @see: U{RecordSet on OSFlash (external)
    834      <http://osflash.org/documentation/amf/recordset>}
    835      """
    836
    837 -    class __amf__:
    838          alias = 'RecordSet'
    839          static = ('serverInfo',)
    840          dynamic = False
    841
    842 -    def __init__(self, columns=[], items=[], service=None, id=None):
    843          self.columns = columns
    844          self.items = items
    845          self.service = service
    846          self.id = id
    847
    848 -    def _get_server_info(self):
    849          ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1,
    850              initialData=self.items, columnNames=self.columns)
    851
    852          if self.service is not None:
    853              ret.update({'serviceName': str(self.service['name'])})
    854
    855          if self.id is not None:
    856              ret.update({'id':str(self.id)})
    857
    858          return ret
    859
    860 -    def _set_server_info(self, val):
    861          self.columns = val['columnNames']
    862          self.items = val['initialData']
    863
    864          try:
    865              # TODO nick: find relevant service and link in here.
    866              self.service = dict(name=val['serviceName'])
    867          except KeyError:
    868              self.service = None
    869
    870          try:
    871              self.id = val['id']
    872          except KeyError:
    873              self.id = None
    874
    875      serverInfo = property(_get_server_info, _set_server_info)
    876
    877 -    def __repr__(self):
    878          ret = '<%s.%s object' % (self.__module__, self.__class__.__name__)
    879
    880          if self.id is not None:
    881              ret += ' id=%s' % self.id
    882
    883          if self.service is not None:
    884              ret += ' service=%s' % self.service
    885
    886          ret += ' at 0x%x>' % id(self)
    887
    888          return ret
    889
    890  pyamf.register_class(RecordSet)
    891
    892
    893 -def _check_for_int(x):
    894      """
    895      This is a compatibility function that takes a C{float} and converts it to an
    896      C{int} if the values are equal.
    897      """
    898      try:
    899          y = int(x)
    900      except (OverflowError, ValueError):
    901          pass
    902      else:
    903          # There is no way in AMF0 to distinguish between integers and floats
    904          if x == x and y == x:
    905              return y
    906
    907      return x
    908
    909  # check for some Python 2.3 problems with floats
    910  try:
    911      float('nan')
    912  except ValueError:
    913      pass
    914  else:
    915      if float('nan') == 0:
    916 -        def check_nan(func):
    917              def f2(x):
    918                  if str(x).lower().find('nan') >= 0:
    919                      return x
    920
    921                  return f2.func(x)
    922              f2.func = func
    923
    924              return f2
    925
    926          _check_for_int = check_nan(_check_for_int)
    927