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