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, writeType=True):
    577          """
    578          Write string to the data stream.
    579
    580          @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    581          @param s: The string data to be encoded to the AMF0 data stream.
    582          @type writeType: C{bool}
    583          @param writeType: Write data type.
    584          """
    585          t = type(s)
    586
    587          if t is str:
    588              pass
    589          elif isinstance(s, unicode):
    590              s = s.encode('utf8')
    591          elif not isinstance(s, basestring):
    592              s = unicode(s).encode('utf8')
    593
    594          l = len(s)
    595
    596          if writeType:
    597              if l > 0xffff:
    598                  self.writeType(TYPE_LONGSTRING)
    599              else:
    600                  self.writeType(TYPE_STRING)
    601
    602          if l > 0xffff:
    603              self.stream.write_ulong(l)
    604          else:
    605              self.stream.write_ushort(l)
    606
    607          self.stream.write(s)
    608
    609 -    def writeReference(self, o):
    610          """
    611          Write reference to the data stream.
    612
    613          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    614          @param o: The reference data to be encoded to the AMF0 data
    615              stream.
    616          """
    617          idx = self.context.getObjectReference(o)
    618
    619          if idx is None:
    620              return None
    621
    622          self.writeType(TYPE_REFERENCE)
    623          self.stream.write_ushort(idx)
    624
    625          return idx
    626
    627 -    def _writeDict(self, o):
    628          """
    629          Write C{dict} to the data stream.
    630
    631          @type o: C{iterable}
    632          @param o: The C{dict} data to be encoded to the AMF0 data
    633              stream.
    634          """
    635          for key, val in o.iteritems():
    636              self.writeString(key, False)
    637              self.writeElement(val)
    638
    639 -    def writeMixedArray(self, o):
    640          """
    641          Write mixed array to the data stream.
    642
    643          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    644          @param o: The mixed array data to be encoded to the AMF0
    645              data stream.
    646          """
    647          if self.writeReference(o) is not None:
    648              return
    649
    650          self.context.addObject(o)
    651          self.writeType(TYPE_MIXEDARRAY)
    652
    653          # TODO: optimise this
    654          # work out the highest integer index
    655          try:
    656              # list comprehensions to save the day
    657              max_index = max([y[0] for y in o.items()
    658                  if isinstance(y[0], (int, long))])
    659
    660              if max_index < 0:
    661                  max_index = 0
    662          except ValueError:
    663              max_index = 0
    664
    665          self.stream.write_ulong(max_index)
    666
    667          self._writeDict(o)
    668          self._writeEndObject()
    669
    670 -    def _writeEndObject(self):
    671          self.stream.write('\x00\x00')
    672          self.writeType(TYPE_OBJECTTERM)
    673
    674 -    def writeObject(self, o):
    675          """
    676          Write object to the stream.
    677
    678          @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    679          @param o: The object data to be encoded to the AMF0 data stream.
    680          """
    681          if self.use_amf3:
    682              self.writeAMF3(o)
    683
    684              return
    685
    686          if self.writeReference(o) is not None:
    687              return
    688
    689          self.context.addObject(o)
    690          alias = self.context.getClassAlias(o.__class__)
    691
    692          alias.compile()
    693
    694          if alias.amf3:
    695              self.writeAMF3(o)
    696
    697              return
    698
    699          if alias.anonymous:
    700              self.writeType(TYPE_OBJECT)
    701          else:
    702              self.writeType(TYPE_TYPEDOBJECT)
    703              self.writeString(alias.alias, False)
    704
    705          sa, da = alias.getEncodableAttributes(o, codec=self)
    706
    707          if sa:
    708              for key in alias.static_attrs:
    709                  self.writeString(key, False)
    710                  self.writeElement(sa[key])
    711
    712          if da:
    713              for key, value in da.iteritems():
    714                  self.writeString(key, False)
    715                  self.writeElement(value)
    716
    717          self._writeEndObject()
    718
    719 -    def writeDate(self, d):
    720          """
    721          Writes a date to the data stream.
    722
    723          @type d: Instance of C{datetime.datetime}
    724          @param d: The date to be encoded to the AMF0 data stream.
    725          """
    726          if isinstance(d, datetime.time):
    727              raise pyamf.EncodeError('A datetime.time instance was found but '
    728                  'AMF0 has no way to encode time objects. Please use '
    729                  'datetime.datetime instead (got:%r)' % (d,))
    730
    731          # According to the Red5 implementation of AMF0, dates references are
    732          # created, but not used.
    733          if self.timezone_offset is not None:
    734              d -= self.timezone_offset
    735
    736          secs = util.get_timestamp(d)
    737          tz = 0
    738
    739          self.writeType(TYPE_DATE)
    740          self.stream.write_double(secs * 1000.0)
    741          self.stream.write_short(tz)
    742
    743 -    def writeXML(self, e):
    744          """
    745          Write XML to the data stream.
    746
    747          @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    748          @param e: The XML data to be encoded to the AMF0 data stream.
    749          """
    750          if self.use_amf3 is True:
    751              self.writeAMF3(e)
    752
    753              return
    754
    755          self.writeType(TYPE_XML)
    756
    757          data = util.ET.tostring(e, 'utf-8')
    758          self.stream.write_ulong(len(data))
    759          self.stream.write(data)
    760
    761 -    def writeAMF3(self, data):
    762          """
    763          Writes an element to the datastream in L{AMF3<pyamf.amf3>} format.
    764
    765          @type data: C{mixed}
    766          @param data: The data to be encoded to the AMF0 data stream.
    767          """
    768          if not hasattr(self.context, 'amf3_context'):
    769              self.context.amf3_context = pyamf.get_context(pyamf.AMF3, exceptions=False)
    770
    771          if not hasattr(self.context, 'amf3_encoder'):
    772              self.context.amf3_encoder = pyamf.get_encoder(
    773                  pyamf.AMF3, self.stream, self.context.amf3_context)
    774
    775          self.context.addAMF3Object(data)
    776          encoder = self.context.amf3_encoder
    777
    778          self.writeType(TYPE_AMF3)
    779          encoder.writeElement(data)
    780
    781
    782 -def decode(*args, **kwargs):
    783      """
    784      A helper function to decode an AMF0 datastream.
    785      """
    786      decoder = Decoder(*args, **kwargs)
    787
    788      while 1:
    789          try:
    790              yield decoder.readElement()
    791          except pyamf.EOStream:
    792              break
    793
    794
    795 -def encode(*args, **kwargs):
    796      """
    797      A helper function to encode an element into the AMF0 format.
    798
    799      @type element: C{mixed}
    800      @keyword element: The element to encode
    801      @type context: L{Context<pyamf.amf0.Context>}
    802      @keyword context: AMF0 C{Context} to use for the encoding. This holds
    803          previously referenced objects etc.
    804      @rtype: C{StringIO}
    805      @return: The encoded stream.
    806      """
    807      encoder = Encoder(**kwargs)
    808
    809      for element in args:
    810          encoder.writeElement(element)
    811
    812      return encoder.stream
    813
    814
    815 -class RecordSet(object):
    816      """
    817      I represent the C{RecordSet} class used in Adobe Flash Remoting to hold
    818      (amongst other things) SQL records.
    819
    820      @ivar columns: The columns to send.
    821      @type columns: List of strings.
    822      @ivar items: The C{RecordSet} data.
    823      @type items: List of lists, the order of the data corresponds to the order
    824          of the columns.
    825      @ivar service: Service linked to the C{RecordSet}.
    826      @type service:
    827      @ivar id: The id of the C{RecordSet}.
    828      @type id: C{str}
    829
    830      @see: U{RecordSet on OSFlash (external)
    831      <http://osflash.org/documentation/amf/recordset>}
    832      """
    833
    834 -    class __amf__:
    835          alias = 'RecordSet'
    836          static = ('serverInfo',)
    837          dynamic = False
    838
    839 -    def __init__(self, columns=[], items=[], service=None, id=None):
    840          self.columns = columns
    841          self.items = items
    842          self.service = service
    843          self.id = id
    844
    845 -    def _get_server_info(self):
    846          ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1,
    847              initialData=self.items, columnNames=self.columns)
    848
    849          if self.service is not None:
    850              ret.update({'serviceName': str(self.service['name'])})
    851
    852          if self.id is not None:
    853              ret.update({'id':str(self.id)})
    854
    855          return ret
    856
    857 -    def _set_server_info(self, val):
    858          self.columns = val['columnNames']
    859          self.items = val['initialData']
    860
    861          try:
    862              # TODO nick: find relevant service and link in here.
    863              self.service = dict(name=val['serviceName'])
    864          except KeyError:
    865              self.service = None
    866
    867          try:
    868              self.id = val['id']
    869          except KeyError:
    870              self.id = None
    871
    872      serverInfo = property(_get_server_info, _set_server_info)
    873
    874 -    def __repr__(self):
    875          ret = '<%s.%s object' % (self.__module__, self.__class__.__name__)
    876
    877          if self.id is not None:
    878              ret += ' id=%s' % self.id
    879
    880          if self.service is not None:
    881              ret += ' service=%s' % self.service
    882
    883          ret += ' at 0x%x>' % id(self)
    884
    885          return ret
    886
    887  pyamf.register_class(RecordSet)
    888
    889
    890 -def _check_for_int(x):
    891      """
    892      This is a compatibility function that takes a C{float} and converts it to an
    893      C{int} if the values are equal.
    894      """
    895      try:
    896          y = int(x)
    897      except (OverflowError, ValueError):
    898          pass
    899      else:
    900          # There is no way in AMF0 to distinguish between integers and floats
    901          if x == x and y == x:
    902              return y
    903
    904      return x
    905
    906  # check for some Python 2.3 problems with floats
    907  try:
    908      float('nan')
    909  except ValueError:
    910      pass
    911  else:
    912      if float('nan') == 0:
    913 -        def check_nan(func):
    914              def f2(x):
    915                  if str(x).lower().find('nan') >= 0:
    916                      return x
    917
    918                  return f2.func(x)
    919              f2.func = func
    920
    921              return f2
    922
    923          _check_for_int = check_nan(_check_for_int)
    924