Package pyamf :: Module amf3 |
[hide private]
[frames] | no frames]

# Source Code for Module pyamf.amf3

       1  # -*- coding: utf-8 -*-
       2  #
       3  # Copyright (c) 2007-2009 The PyAMF Project.
       4  # See LICENSE.txt for details.
       5
       6  """
       7  AMF3 implementation.
       8
       9  C{AMF3} is the default serialization for
      10  U{ActionScript<http://en.wikipedia.org/wiki/ActionScript>} 3.0 and provides
      11  various advantages over L{AMF0<pyamf.amf0>}, which is used for ActionScript 1.0
      12  and 2.0. It adds support for sending C{int} and C{uint} objects as integers and
      13  supports data types that are available only in ActionScript 3.0, such as
      14  L{ByteArray} and L{ArrayCollection}.
      15
      16  @see: U{Official AMF3 Specification in English (external)
      17  <http://opensource.adobe.com/wiki/download/attachments/1114283/amf3_spec_05_05_08.pdf>}
      18  @see: U{Official AMF3 Specification in Japanese (external)
      19  <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf3_spec_121207.pdf>}
      20  @see: U{AMF3 documentation on OSFlash (external)
      21  <http://osflash.org/documentation/amf3>}
      22
      23  @since: 0.1
      24  """
      25
      26  import types
      27  import datetime
      28  import zlib
      29
      30  import pyamf
      31  from pyamf import util
      32  from pyamf.flex import ObjectProxy, ArrayCollection
      33
      34  #: If True encode/decode lists/tuples to L{ArrayCollections<ArrayCollection>}
      35  #: and dicts to L{ObjectProxy}
      36  use_proxies_default = False
      37
      38  try:
      39      set()
      40  except NameError:
      41      from sets import Set as set
      42
      43
      44  #: The undefined type is represented by the undefined type marker. No further
      45  #: information is encoded for this value.
      46  TYPE_UNDEFINED = '\x00'
      47  #: The null type is represented by the null type marker. No further
      48  #: information is encoded for this value.
      49  TYPE_NULL = '\x01'
      50  #: The false type is represented by the false type marker and is used to
      51  #: encode a Boolean value of C{false}. No further information is encoded for
      52  #: this value.
      53  TYPE_BOOL_FALSE = '\x02'
      54  #: The true type is represented by the true type marker and is used to encode
      55  #: a Boolean value of C{true}. No further information is encoded for this
      56  #: value.
      57  TYPE_BOOL_TRUE = '\x03'
      58  #: In AMF 3 integers are serialized using a variable length signed 29-bit
      59  #: integer.
      60  #: @see: U{Parsing Integers on OSFlash (external)
      61  #: <http://osflash.org/documentation/amf3/parsing_integers>}
      62  TYPE_INTEGER = '\x04'
      63  #: This type is used to encode an ActionScript Number or an ActionScript
      64  #: C{int} of value greater than or equal to 2^28 or an ActionScript uint of
      65  #: value greater than or equal to 2^29. The encoded value is is always an 8
      66  #: byte IEEE-754 double precision floating point value in network byte order
      67  #: (sign bit in low memory). The AMF 3 number type is encoded in the same
      68  #: manner as the AMF 0 L{Number<pyamf.amf0.TYPE_NUMBER>} type.
      69  TYPE_NUMBER = '\x05'
      70  #: ActionScript String values are represented using a single string type in
      71  #: AMF 3 - the concept of string and long string types from AMF 0 is not used.
      72  #: Strings can be sent as a reference to a previously occurring String by
      73  #: using an index to the implicit string reference table. Strings are encoding
      74  #: using UTF-8 - however the header may either describe a string literal or a
      75  #: string reference.
      76  TYPE_STRING = '\x06'
      77  #: ActionScript 3.0 introduced a new XML type however the legacy C{XMLDocument}
      78  #: type from ActionScript 1.0 and 2.0.is retained in the language as
      79  #: C{flash.xml.XMLDocument}. Similar to AMF 0, the structure of an
      80  #: C{XMLDocument} needs to be flattened into a string representation for
      81  #: serialization. As with other strings in AMF, the content is encoded in
      82  #: UTF-8. XMLDocuments can be sent as a reference to a previously occurring
      83  #: C{XMLDocument} instance by using an index to the implicit object reference
      84  #: table.
      85  #: @see: U{OSFlash documentation (external)
      86  #: <http://osflash.org/documentation/amf3#x07_-_xml_legacy_flash.xml.xmldocument_class>}
      87  TYPE_XML = '\x07'
      88  #: In AMF 3 an ActionScript Date is serialized simply as the number of
      89  #: milliseconds elapsed since the epoch of midnight, 1st Jan 1970 in the
      90  #: UTC time zone. Local time zone information is not sent.
      91  TYPE_DATE = '\x08'
      92  #: ActionScript Arrays are described based on the nature of their indices,
      93  #: i.e. their type and how they are positioned in the Array.
      94  TYPE_ARRAY = '\x09'
      95  #: A single AMF 3 type handles ActionScript Objects and custom user classes.
      96  TYPE_OBJECT = '\x0A'
      97  #: ActionScript 3.0 introduces a new top-level XML class that supports
      98  #: U{E4X<http://en.wikipedia.org/wiki/E4X>} syntax.
      99  #: For serialization purposes the XML type needs to be flattened into a
     100  #: string representation. As with other strings in AMF, the content is
     101  #: encoded using UTF-8.
     102  TYPE_XMLSTRING = '\x0B'
     103  #: ActionScript 3.0 introduces the L{ByteArray} type to hold an Array
     104  #: of bytes. AMF 3 serializes this type using a variable length encoding
     105  #: 29-bit integer for the byte-length prefix followed by the raw bytes
     106  #: of the L{ByteArray}.
     107  #: @see: U{Parsing ByteArrays on OSFlash (external)
     108  #: <http://osflash.org/documentation/amf3/parsing_byte_arrays>}
     109  TYPE_BYTEARRAY = '\x0C'
     110
     111  #: Reference bit.
     112  REFERENCE_BIT = 0x01
     113
     114  #: The maximum that can be represented by an signed 29 bit integer.
     115  MAX_29B_INT = 0x0FFFFFFF
     116
     117  #: The minimum that can be represented by an signed 29 bit integer.
     118  MIN_29B_INT = -0x10000000
     119
     120  ENCODED_INT_CACHE = {}
     121
     122
     123 -class ObjectEncoding:
     124      """
     125      AMF object encodings.
     126      """
     127      #: Property list encoding.
     128      #: The remaining integer-data represents the number of class members that
     129      #: exist. The property names are read as string-data. The values are then
     130      #: read as AMF3-data.
     131      STATIC = 0x00
     132
     133      #: Externalizable object.
     134      #: What follows is the value of the "inner" object, including type code.
     135      #: This value appears for objects that implement IExternalizable, such as
     136      #: L{ArrayCollection} and L{ObjectProxy}.
     137      EXTERNAL = 0x01
     138
     139      #: Name-value encoding.
     140      #: The property names and values are encoded as string-data followed by
     141      #: AMF3-data until there is an empty string property name. If there is a
     142      #: class-def reference there are no property names and the number of values
     143      #: is equal to the number of properties in the class-def.
     144      DYNAMIC = 0x02
     145
     146      #: Proxy object.
     147      PROXY = 0x03
     148
     149
     150 -class DataOutput(object):
     151      """
     152      I am a C{StringIO} type object containing byte data from the AMF stream.
     153      ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
     154      the manipulation of raw data in the form of an Array of bytes.
     155      I provide a set of methods for writing binary data with ActionScript 3.0.
     156
     157      This class is the I/O counterpart to the L{DataInput} class, which reads
     158      binary data.
     159
     160      @see: U{IDataOutput on Livedocs (external)
     161      <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataOutput.html>}
     162      """
     163 -    def __init__(self, encoder):
     164          """
     165          @param encoder: Encoder containing the stream.
     166          @type encoder: L{amf3.Encoder<pyamf.amf3.Encoder>}
     167          """
     168          self.encoder = encoder
     169          self.stream = encoder.stream
     170
     171 -    def writeBoolean(self, value):
     172          """
     173          Writes a Boolean value.
     174
     175          @type value: C{bool}
     176          @param value: A C{Boolean} value determining which byte is written.
     177          If the parameter is C{True}, C{1} is written; if C{False}, C{0} is
     178          written.
     179
     180          @raise ValueError: Non-boolean value found.
     181          """
     182          if isinstance(value, bool):
     183              if value is True:
     184                  self.stream.write_uchar(1)
     185              else:
     186                  self.stream.write_uchar(0)
     187          else:
     188              raise ValueError("Non-boolean value found")
     189
     190 -    def writeByte(self, value):
     191          """
     192          Writes a byte.
     193
     194          @type value: C{int}
     195          """
     196          self.stream.write_char(value)
     197
     198 -    def writeUnsignedByte(self, value):
     199          """
     200          Writes an unsigned byte.
     201
     202          @type value: C{int}
     203          @since: 0.5
     204          """
     205          return self.stream.write_uchar(value)
     206
     207 -    def writeDouble(self, value):
     208          """
     209          Writes an IEEE 754 double-precision (64-bit) floating
     210          point number.
     211
     212          @type value: C{number}
     213          """
     214          self.stream.write_double(value)
     215
     216 -    def writeFloat(self, value):
     217          """
     218          Writes an IEEE 754 single-precision (32-bit) floating
     219          point number.
     220
     221          @type value: C{float}
     222          """
     223          self.stream.write_float(value)
     224
     225 -    def writeInt(self, value):
     226          """
     227          Writes a 32-bit signed integer.
     228
     229          @type value: C{int}
     230          """
     231          self.stream.write_long(value)
     232
     233 -    def writeMultiByte(self, value, charset):
     234          """
     235          Writes a multibyte string to the datastream using the
     236          specified character set.
     237
     238          @type value: C{str}
     239          @param value: The string value to be written.
     240          @type charset: C{str}
     241          @param charset: The string denoting the character set to use. Possible
     242              character set strings include C{shift-jis}, C{cn-gb},
     243              C{iso-8859-1} and others.
     244          @see: U{Supported character sets on Livedocs (external)
     245              <http://livedocs.adobe.com/flex/201/langref/charset-codes.html>}
     246          """
     247          self.stream.write(unicode(value).encode(charset))
     248
     249 -    def writeObject(self, value, use_references=True, use_proxies=None):
     250          """
     251          Writes an object to data stream in AMF serialized format.
     252
     253          @param value: The object to be serialized.
     254          @type use_references: C{bool}
     255          @param use_references:
     256          """
     257          self.encoder.writeElement(value, use_references, use_proxies)
     258
     259 -    def writeShort(self, value):
     260          """
     261          Writes a 16-bit integer.
     262
     263          @type value: C{int}
     264          @param value: A byte value as an integer.
     265          """
     266          self.stream.write_short(value)
     267
     268 -    def writeUnsignedShort(self, value):
     269          """
     270          Writes a 16-bit unsigned integer.
     271
     272          @type value: C{int}
     273          @param value: A byte value as an integer.
     274          @since: 0.5
     275          """
     276          self.stream.write_ushort(value)
     277
     278 -    def writeUnsignedInt(self, value):
     279          """
     280          Writes a 32-bit unsigned integer.
     281
     282          @type value: C{int}
     283          @param value: A byte value as an unsigned integer.
     284          """
     285          self.stream.write_ulong(value)
     286
     287 -    def writeUTF(self, value):
     288          """
     289          Writes a UTF-8 string to the data stream.
     290
     291          The length of the UTF-8 string in bytes is written first,
     292          as a 16-bit integer, followed by the bytes representing the
     293          characters of the string.
     294
     295          @type value: C{str}
     296          @param value: The string value to be written.
     297          """
     298          if not isinstance(value, unicode):
     299              value = unicode(value, 'utf8')
     300
     301          buf = util.BufferedByteStream()
     302          buf.write_utf8_string(value)
     303          bytes = buf.getvalue()
     304
     305          self.stream.write_ushort(len(bytes))
     306          self.stream.write(bytes)
     307
     308 -    def writeUTFBytes(self, value):
     309          """
     310          Writes a UTF-8 string. Similar to L{writeUTF}, but does
     311          not prefix the string with a 16-bit length word.
     312
     313          @type value: C{str}
     314          @param value: The string value to be written.
     315          """
     316          val = None
     317
     318          if isinstance(value, unicode):
     319              val = value
     320          else:
     321              val = unicode(value, 'utf8')
     322
     323          self.stream.write_utf8_string(val)
     324
     325
     326 -class DataInput(object):
     327      """
     328      I provide a set of methods for reading binary data with ActionScript 3.0.
     329
     330      This class is the I/O counterpart to the L{DataOutput} class,
     331      which writes binary data.
     332
     333      @see: U{IDataInput on Livedocs (external)
     334      <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataInput.html>}
     335      """
     336
     337 -    def __init__(self, decoder):
     338          """
     339          @param decoder: AMF3 decoder containing the stream.
     340          @type decoder: L{amf3.Decoder<pyamf.amf3.Decoder>}
     341          """
     342          assert isinstance(decoder, Decoder)
     343
     344          self.decoder = decoder
     345          self.stream = decoder.stream
     346
     347 -    def readBoolean(self):
     348          """
     349          Read C{Boolean}.
     350
     351          @raise ValueError: Error reading Boolean.
     352          @rtype: C{bool}
     353          @return: A Boolean value, C{True} if the byte
     354          is nonzero, C{False} otherwise.
     355          """
     356          byte = self.stream.read(1)
     357
     358          if byte == '\x00':
     359              return False
     360          elif byte == '\x01':
     361              return True
     362          else:
     363              raise ValueError("Error reading boolean")
     364
     365 -    def readByte(self):
     366          """
     367          Reads a signed byte.
     368
     369          @rtype: C{int}
     370          @return: The returned value is in the range -128 to 127.
     371          """
     372          return self.stream.read_char()
     373
     374 -    def readDouble(self):
     375          """
     376          Reads an IEEE 754 double-precision floating point number from the
     377          data stream.
     378
     379          @rtype: C{number}
     380          @return: An IEEE 754 double-precision floating point number.
     381          """
     382          return self.stream.read_double()
     383
     384 -    def readFloat(self):
     385          """
     386          Reads an IEEE 754 single-precision floating point number from the
     387          data stream.
     388
     389          @rtype: C{number}
     390          @return: An IEEE 754 single-precision floating point number.
     391          """
     392          return self.stream.read_float()
     393
     394 -    def readInt(self):
     395          """
     396          Reads a signed 32-bit integer from the data stream.
     397
     398          @rtype: C{int}
     399          @return: The returned value is in the range -2147483648 to 2147483647.
     400          """
     401          return self.stream.read_long()
     402
     403 -    def readMultiByte(self, length, charset):
     404          """
     405          Reads a multibyte string of specified length from the data stream
     406          using the specified character set.
     407
     408          @type length: C{int}
     409          @param length: The number of bytes from the data stream to read.
     410          @type charset: C{str}
     411          @param charset: The string denoting the character set to use.
     412
     413          @rtype: C{str}
     414          @return: UTF-8 encoded string.
     415          """
     416          #FIXME nick: how to work out the code point byte size (on the fly)?
     417          bytes = self.stream.read(length)
     418
     419          return unicode(bytes, charset)
     420
     421 -    def readObject(self):
     422          """
     423          Reads an object from the data stream.
     424
     425          @return: The deserialized object.
     426          """
     427          return self.decoder.readElement()
     428
     429 -    def readShort(self):
     430          """
     431          Reads a signed 16-bit integer from the data stream.
     432
     433          @rtype: C{uint}
     434          @return: The returned value is in the range -32768 to 32767.
     435          """
     436          return self.stream.read_short()
     437
     438 -    def readUnsignedByte(self):
     439          """
     440          Reads an unsigned byte from the data stream.
     441
     442          @rtype: C{uint}
     443          @return: The returned value is in the range 0 to 255.
     444          """
     445          return self.stream.read_uchar()
     446
     447 -    def readUnsignedInt(self):
     448          """
     449          Reads an unsigned 32-bit integer from the data stream.
     450
     451          @rtype: C{uint}
     452          @return: The returned value is in the range 0 to 4294967295.
     453          """
     454          return self.stream.read_ulong()
     455
     456 -    def readUnsignedShort(self):
     457          """
     458          Reads an unsigned 16-bit integer from the data stream.
     459
     460          @rtype: C{uint}
     461          @return: The returned value is in the range 0 to 65535.
     462          """
     463          return self.stream.read_ushort()
     464
     465 -    def readUTF(self):
     466          """
     467          Reads a UTF-8 string from the data stream.
     468
     469          The string is assumed to be prefixed with an unsigned
     470          short indicating the length in bytes.
     471
     472          @rtype: C{str}
     473          @return: A UTF-8 string produced by the byte
     474          representation of characters.
     475          """
     476          length = self.stream.read_ushort()
     477          return self.stream.read_utf8_string(length)
     478
     479 -    def readUTFBytes(self, length):
     480          """
     481          Reads a sequence of C{length} UTF-8 bytes from the data
     482          stream and returns a string.
     483
     484          @type length: C{int}
     485          @param length: The number of bytes from the data stream to read.
     486          @rtype: C{str}
     487          @return: A UTF-8 string produced by the byte representation of
     488          characters of specified C{length}.
     489          """
     490          return self.readMultiByte(length, 'utf-8')
     491
     492
     493 -class ByteArray(util.BufferedByteStream, DataInput, DataOutput):
     494      """
     495      I am a C{StringIO} type object containing byte data from the AMF stream.
     496      ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
     497      the manipulation of raw data in the form of an Array of bytes.
     498
     499      Supports C{zlib} compression.
     500
     501      Possible uses of the C{ByteArray} class:
     502       - Creating a custom protocol to connect to a client.
     503       - Writing your own AMF/Remoting packet.
     504       - Optimizing the size of your data by using custom data types.
     505
     506      @see: U{ByteArray on Livedocs (external)
     507      <http://livedocs.adobe.com/flex/201/langref/flash/utils/ByteArray.html>}
     508      """
     509
     510 -    class __amf__:
     511          amf3 = True
     512
     513 -    def __init__(self, *args, **kwargs):
     514          self.context = kwargs.pop('context', Context())
     515
     516          util.BufferedByteStream.__init__(self, *args, **kwargs)
     517          DataInput.__init__(self, Decoder(self, self.context))
     518          DataOutput.__init__(self, Encoder(self, self.context))
     519
     520          self.compressed = False
     521
     522 -    def __cmp__(self, other):
     523          if isinstance(other, ByteArray):
     524              return cmp(self.getvalue(), other.getvalue())
     525
     526          return cmp(self.getvalue(), other)
     527
     528 -    def __str__(self):
     529          buf = self.getvalue()
     530
     531          if self.compressed:
     532              buf = zlib.compress(buf)
     533              #FIXME nick: hacked
     534              buf = buf[0] + '\xda' + buf[2:]
     535
     536          return buf
     537
     538 -    def compress(self):
     539          """
     540          Forces compression of the underlying stream.
     541          """
     542          self.compressed = True
     543
     544
     545 -class ClassDefinition(object):
     546      """
     547      """
     548
     549 -    def __init__(self, alias):
     550          self.alias = alias
     551          self.reference = None
     552
     553          alias.compile()
     554
     555          self.attr_len = 0
     556
     557          if alias.static_attrs:
     558              self.attr_len = len(alias.static_attrs)
     559
     560          self.encoding = ObjectEncoding.DYNAMIC
     561
     562          if alias.external:
     563              self.encoding = ObjectEncoding.EXTERNAL
     564          elif not alias.dynamic:
     565              if alias.static_attrs == alias.encodable_properties:
     566                  self.encoding = ObjectEncoding.STATIC
     567
     568 -    def __repr__(self):
     569          return '<%s.ClassDefinition reference=%r encoding=%r alias=%r at 0x%x>' % (
     570              self.__class__.__module__, self.reference, self.encoding, self.alias, id(self))
     571
     572
     573 -class Context(pyamf.BaseContext):
     574      """
     575      I hold the AMF3 context for en/decoding streams.
     576
     577      @ivar strings: A list of string references.
     578      @type strings: C{list}
     579      @ivar classes: A list of L{ClassDefinition}.
     580      @type classes: C{list}
     581      @ivar legacy_xml: A list of legacy encoded XML documents.
     582      @type legacy_xml: C{list}
     583      """
     584
     585 -    def __init__(self, exceptions=True):
     586          self.strings = util.IndexedCollection(use_hash=True, exceptions=False)
     587          self.classes = {}
     588          self.class_ref = {}
     589          self.legacy_xml = util.IndexedCollection(exceptions=False)
     590          self.object_aliases = util.IndexedMap(exceptions=False) # Maps one object to another
     591
     592          self.class_idx = 0
     593
     594          pyamf.BaseContext.__init__(self, exceptions=exceptions)
     595
     596 -    def clear(self):
     597          """
     598          Clears the context.
     599          """
     600          pyamf.BaseContext.clear(self)
     601
     602          self.strings.clear()
     603          self.classes = {}
     604          self.class_ref = {}
     605          self.legacy_xml.clear()
     606          self.object_aliases.clear()
     607
     608          self.class_idx = 0
     609
     610 -    def setObjectAlias(self, obj, alias):
     611          """
     612          Maps an object to an aliased object.
     613
     614          @since: 0.4
     615          """
     616          self.object_aliases.map(obj, alias)
     617
     618 -    def getObjectAlias(self, obj):
     619          """
     620          Get an alias of an object.
     621
     622          @since: 0.4
     623          @raise pyamf.ReferenceError: Unknown object alias.
     624          @raise pyamf.ReferenceError: Unknown mapped alias.
     625          """
     626          ref = self.object_aliases.getReferenceTo(obj)
     627
     628          if ref is None:
     629              if self.exceptions is False:
     630                  return None
     631
     632              raise pyamf.ReferenceError('Unknown object alias for %r' % (obj,))
     633
     634          mapped = self.object_aliases.getMappedByReference(ref)
     635
     636          if mapped is None:
     637              if self.exceptions is False:
     638                  return None
     639
     640              raise pyamf.ReferenceError('Unknown mapped alias for %r' % (obj,))
     641
     642          return mapped
     643
     644 -    def getString(self, ref):
     645          """
     646          Gets a string based on a reference C{ref}.
     647
     648          @param ref: The reference index.
     649          @type ref: C{str}
     650          @raise pyamf.ReferenceError: The referenced string could not be found.
     651
     652          @rtype: C{str}
     653          @return: The referenced string.
     654          """
     655          i = self.strings.getByReference(ref)
     656
     657          if i is None and self.exceptions:
     658              raise pyamf.ReferenceError("String reference %r not found" % (ref,))
     659
     660          return i
     661
     662 -    def getStringReference(self, s):
     663          """
     664          Return string reference.
     665
     666          @type s: C{str}
     667          @param s: The referenced string.
     668          @raise pyamf.ReferenceError: The string reference could not be found.
     669          @return: The reference index to the string.
     670          @rtype: C{int}
     671          """
     672          i = self.strings.getReferenceTo(s)
     673
     674          if i is None and self.exceptions:
     675              raise pyamf.ReferenceError("Reference for string %r not found" % (s,))
     676
     677          return i
     678
     679 -    def addString(self, s):
     680          """
     681          Creates a reference to C{s}. If the reference already exists, that
     682          reference is returned.
     683
     684          @type s: C{str}
     685          @param s: The string to be referenced.
     686          @rtype: C{int}
     687          @return: The reference index.
     688
     689          @raise TypeError: The parameter C{s} is not of C{basestring} type.
     690          @raise pyamf.ReferenceError: Trying to store a reference to an empty string.
     691          """
     692          if not isinstance(s, basestring):
     693              raise TypeError
     694
     695          if len(s) == 0:
     696              if not self.exceptions:
     697                  return None
     698
     699              # do not store empty string references
     700              raise pyamf.ReferenceError("Cannot store a reference to an empty string")
     701
     702          return self.strings.append(s)
     703
     704 -    def getClassByReference(self, ref):
     705          """
     706          Return class reference.
     707
     708          @raise pyamf.ReferenceError: The class reference could not be found.
     709          @return: Class reference.
     710          """
     711          try:
     712              return self.class_ref[ref]
     713          except KeyError:
     714              if not self.exceptions:
     715                  return None
     716
     717              raise pyamf.ReferenceError("Class reference %r not found" % (
     718                  ref,))
     719
     720 -    def getClass(self, klass):
     721          """
     722          Return class reference.
     723
     724          @raise pyamf.ReferenceError: The class reference could not be found.
     725          @return: Class reference.
     726          """
     727          try:
     728              return self.classes[klass]
     729          except KeyError:
     730              if not self.exceptions:
     731                  return None
     732
     733              raise pyamf.ReferenceError("Class alias for %r not found" % (
     734                  klass,))
     735
     736 -    def addClass(self, alias, klass):
     737          """
     738          Creates a reference to C{class_def}.
     739
     740          @param alias: C{ClassDefinition} instance.
     741          """
     742          ref = self.class_idx
     743
     744          self.class_ref[ref] = alias
     745          cd = self.classes[klass] = alias
     746
     747          cd.reference = ref
     748
     749          self.class_idx += 1
     750
     751          return ref
     752
     753 -    def getLegacyXML(self, ref):
     754          """
     755          Return the legacy XML reference. This is the C{flash.xml.XMLDocument}
     756          class in ActionScript 3.0 and the top-level C{XML} class in
     757          ActionScript 1.0 and 2.0.
     758
     759          @type ref: C{int}
     760          @param ref: The reference index.
     761          @raise pyamf.ReferenceError: The legacy XML reference could not be found.
     762          @return: Instance of L{ET<util.ET>}
     763          """
     764          i = self.legacy_xml.getByReference(ref)
     765
     766          if i is None:
     767              if not self.exceptions:
     768                  return None
     769
     770              raise pyamf.ReferenceError("Legacy XML reference %r not found" % (ref,))
     771
     772          return i
     773
     774 -    def getLegacyXMLReference(self, doc):
     775          """
     776          Return legacy XML reference.
     777
     778          @type doc: L{ET<util.ET>}
     779          @param doc: The XML document to reference.
     780          @raise pyamf.ReferenceError: The reference could not be found.
     781          @return: The reference to C{doc}.
     782          @rtype: C{int}
     783          """
     784          i = self.legacy_xml.getReferenceTo(doc)
     785
     786          if i is None:
     787              if not self.exceptions:
     788                  return None
     789
     790              raise pyamf.ReferenceError("Reference for document %r not found" % (doc,))
     791
     792          return i
     793
     794 -    def addLegacyXML(self, doc):
     795          """
     796          Creates a reference to C{doc}.
     797
     798          If C{doc} is already referenced that index will be returned. Otherwise
     799          a new index will be created.
     800
     801          @type doc: L{ET<util.ET>}
     802          @param doc: The XML document to reference.
     803          @rtype: C{int}
     804          @return: The reference to C{doc}.
     805          """
     806          return self.legacy_xml.append(doc)
     807
     808 -    def __copy__(self):
     809          return self.__class__(exceptions=self.exceptions)
     810
     811
     812 -class Decoder(pyamf.BaseDecoder):
     813      """
     814      Decodes an AMF3 data stream.
     815      """
     816      context_class = Context
     817
     818      type_map = {
     819          TYPE_UNDEFINED:  'readUndefined',
     820          TYPE_NULL:       'readNull',
     821          TYPE_BOOL_FALSE: 'readBoolFalse',
     822          TYPE_BOOL_TRUE:  'readBoolTrue',
     823          TYPE_INTEGER:    'readSignedInteger',
     824          TYPE_NUMBER:     'readNumber',
     825          TYPE_STRING:     'readString',
     826          TYPE_XML:        'readXML',
     827          TYPE_DATE:       'readDate',
     828          TYPE_ARRAY:      'readArray',
     829          TYPE_OBJECT:     'readObject',
     830          TYPE_XMLSTRING:  'readXMLString',
     831          TYPE_BYTEARRAY:  'readByteArray',
     832      }
     833
     834 -    def __init__(self, *args, **kwargs):
     835          self.use_proxies = kwargs.pop('use_proxies', use_proxies_default)
     836
     837          pyamf.BaseDecoder.__init__(self, *args, **kwargs)
     838
     839 -    def readUndefined(self):
     840          """
     841          Read undefined.
     842          """
     843          return pyamf.Undefined
     844
     845 -    def readNull(self):
     846          """
     847          Read null.
     848
     849          @return: C{None}
     850          @rtype: C{None}
     851          """
     852          return None
     853
     854 -    def readBoolFalse(self):
     855          """
     856          Returns C{False}.
     857
     858          @return: C{False}
     859          @rtype: C{bool}
     860          """
     861          return False
     862
     863 -    def readBoolTrue(self):
     864          """
     865          Returns C{True}.
     866
     867          @return: C{True}
     868          @rtype: C{bool}
     869          """
     870          return True
     871
     872 -    def readNumber(self):
     873          """
     874          Read number.
     875          """
     876          return self.stream.read_double()
     877
     878 -    def readUnsignedInteger(self):
     879          """
     880          Reads and returns an unsigned integer from the stream.
     881          """
     882          return self.readInteger(False)
     883
     884 -    def readSignedInteger(self):
     885          """
     886          Reads and returns a signed integer from the stream.
     887          """
     888          return self.readInteger(True)
     889
     890 -    def readInteger(self, signed=False):
     891          """
     892          Reads and returns an integer from the stream.
     893
     894          @type signed: C{bool}
     895          @see: U{Parsing integers on OSFlash
     896          <http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data
     897          format.
     898          """
     899          return decode_int(self.stream, signed)
     900
     901 -    def readString(self, use_references=True):
     902          """
     903          Reads and returns a string from the stream.
     904
     905          @type use_references: C{bool}
     906          """
     907          def readLength():
     908              x = self.readUnsignedInteger()
     909
     910              return (x >> 1, x & REFERENCE_BIT == 0)
     911
     912          length, is_reference = readLength()
     913
     914          if use_references and is_reference:
     915              return self.context.getString(length)
     916
     917          if length == 0:
     918              return u''
     919
     920          result = self.stream.read_utf8_string(length)
     921
     922          if len(result) != 0 and use_references:
     923              self.context.addString(result)
     924
     925          return result
     926
     927 -    def readDate(self):
     928          """
     929          Read date from the stream.
     930
     931          The timezone is ignored as the date is always in UTC.
     932          """
     933          ref = self.readUnsignedInteger()
     934
     935          if ref & REFERENCE_BIT == 0:
     936              return self.context.getObject(ref >> 1)
     937
     938          ms = self.stream.read_double()
     939          result = util.get_datetime(ms / 1000.0)
     940
     941          if self.timezone_offset is not None:
     942              result += self.timezone_offset
     943
     944          self.context.addObject(result)
     945
     946          return result
     947
     948 -    def readArray(self):
     949          """
     950          Reads an array from the stream.
     951
     952          @warning: There is a very specific problem with AMF3 where the first
     953          three bytes of an encoded empty C{dict} will mirror that of an encoded
     954          C{{'': 1, '2': 2}}
     955
     956          @see: U{Docuverse blog (external)
     957          <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
     958          """
     959          size = self.readUnsignedInteger()
     960
     961          if size & REFERENCE_BIT == 0:
     962              return self.context.getObject(size >> 1)
     963
     964          size >>= 1
     965
     966          key = self.readString().encode('utf8')
     967
     968          if key == '':
     969              # integer indexes only -> python list
     970              result = []
     971              self.context.addObject(result)
     972
     973              for i in xrange(size):
     974                  result.append(self.readElement())
     975
     976              return result
     977
     978          result = pyamf.MixedArray()
     979          self.context.addObject(result)
     980
     981          while key != "":
     982              result[key] = self.readElement()
     983              key = self.readString().encode('utf8')
     984
     985          for i in xrange(size):
     986              el = self.readElement()
     987              result[i] = el
     988
     989          return result
     990
     991 -    def _getClassDefinition(self, ref):
     992          """
     993          Reads class definition from the stream.
     994          """
     995          is_ref = ref & REFERENCE_BIT == 0
     996          ref >>= 1
     997
     998          if is_ref:
     999              class_def = self.context.getClassByReference(ref)
    1000
    1001              return class_def, class_def.alias
    1002
    1003          name = self.readString()
    1004          alias = None
    1005
    1006          if name == '':
    1007              name = pyamf.ASObject
    1008
    1009          try:
    1010              alias = pyamf.get_class_alias(name)
    1011          except pyamf.UnknownClassAlias:
    1012              if self.strict:
    1013                  raise
    1014
    1015              alias = pyamf.TypedObjectClassAlias(pyamf.TypedObject, name)
    1016
    1017          class_def = ClassDefinition(alias)
    1018
    1019          class_def.encoding = ref & 0x03
    1020          class_def.attr_len = ref >> 2
    1021          class_def.static_properties = []
    1022
    1023          if class_def.attr_len > 0:
    1024              for i in xrange(class_def.attr_len):
    1025                  key = self.readString().encode('utf8')
    1026
    1027                  class_def.static_properties.append(key)
    1028
    1029          self.context.addClass(class_def, alias.klass)
    1030
    1031          return class_def, alias
    1032
    1033 -    def readObject(self, use_proxies=None):
    1034          """
    1035          Reads an object from the stream.
    1036
    1037          @raise pyamf.EncodeError: Decoding an object in amf3 tagged as amf0
    1038              only is not allowed.
    1039          @raise pyamf.DecodeError: Unknown object encoding.
    1040          """
    1041          if use_proxies is None:
    1042              use_proxies = self.use_proxies
    1043
    1044          def readStatic(class_def, obj):
    1045              for attr in class_def.static_properties:
    1046                  obj[attr] = self.readElement()
    1047
    1048          def readDynamic(class_def, obj):
    1049              attr = self.readString().encode('utf8')
    1050
    1051              while attr != '':
    1052                  obj[attr] = self.readElement()
    1053                  attr = self.readString().encode('utf8')
    1054
    1055          ref = self.readUnsignedInteger()
    1056
    1057          if ref & REFERENCE_BIT == 0:
    1058              obj = self.context.getObject(ref >> 1)
    1059
    1060              if use_proxies is True:
    1061                  obj = self.readProxyObject(obj)
    1062
    1063              return obj
    1064
    1065          ref >>= 1
    1066
    1067          class_def, alias = self._getClassDefinition(ref)
    1068
    1069          obj = alias.createInstance(codec=self)
    1070          obj_attrs = dict()
    1071
    1072          self.context.addObject(obj)
    1073
    1074          if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
    1075              obj.__readamf__(DataInput(self))
    1076          elif class_def.encoding == ObjectEncoding.DYNAMIC:
    1077              readStatic(class_def, obj_attrs)
    1078              readDynamic(class_def, obj_attrs)
    1079          elif class_def.encoding == ObjectEncoding.STATIC:
    1080              readStatic(class_def, obj_attrs)
    1081          else:
    1082              raise pyamf.DecodeError("Unknown object encoding")
    1083
    1084          alias.applyAttributes(obj, obj_attrs, codec=self)
    1085
    1086          if use_proxies is True:
    1087              obj = self.readProxyObject(obj)
    1088
    1089          return obj
    1090
    1091 -    def readProxyObject(self, proxy):
    1092          """
    1093          Return the source object of a proxied object.
    1094
    1095          @since: 0.4
    1096          """
    1097          if isinstance(proxy, ArrayCollection):
    1098              return list(proxy)
    1099          elif isinstance(proxy, ObjectProxy):
    1100              return proxy._amf_object
    1101
    1102          return proxy
    1103
    1104 -    def _readXML(self, legacy=False):
    1105          """
    1106          Reads an object from the stream.
    1107
    1108          @type legacy: C{bool}
    1109          @param legacy: The read XML is in 'legacy' format.
    1110          """
    1111          ref = self.readUnsignedInteger()
    1112
    1113          if ref & REFERENCE_BIT == 0:
    1114              return self.context.getObject(ref >> 1)
    1115
    1116          xmlstring = self.stream.read(ref >> 1)
    1117
    1118          x = util.ET.fromstring(xmlstring)
    1119          self.context.addObject(x)
    1120
    1121          if legacy is True:
    1122              self.context.addLegacyXML(x)
    1123
    1124          return x
    1125
    1126 -    def readXMLString(self):
    1127          """
    1128          Reads a string from the data stream and converts it into
    1129          an XML Tree.
    1130
    1131          @return: The XML Document.
    1132          @rtype: L{ET<util.ET>}
    1133          """
    1134          return self._readXML()
    1135
    1136 -    def readXML(self):
    1137          """
    1138          Read a legacy XML Document from the stream.
    1139
    1140          @return: The XML Document.
    1141          @rtype: L{ET<util.ET>}
    1142          """
    1143          return self._readXML(True)
    1144
    1145 -    def readByteArray(self):
    1146          """
    1147          Reads a string of data from the stream.
    1148
    1149          Detects if the L{ByteArray} was compressed using C{zlib}.
    1150
    1151          @see: L{ByteArray}
    1152          @note: This is not supported in ActionScript 1.0 and 2.0.
    1153          """
    1154          ref = self.readUnsignedInteger()
    1155
    1156          if ref & REFERENCE_BIT == 0:
    1157              return self.context.getObject(ref >> 1)
    1158
    1159          buffer = self.stream.read(ref >> 1)
    1160
    1161          try:
    1162              buffer = zlib.decompress(buffer)
    1163              compressed = True
    1164          except zlib.error:
    1165              compressed = False
    1166
    1167          obj = ByteArray(buffer, context=self.context)
    1168          obj.compressed = compressed
    1169
    1170          self.context.addObject(obj)
    1171
    1172          return obj
    1173
    1174
    1175 -class Encoder(pyamf.BaseEncoder):
    1176      """
    1177      Encodes an AMF3 data stream.
    1178      """
    1179      context_class = Context
    1180
    1181      type_map = [
    1182          ((types.BuiltinFunctionType, types.BuiltinMethodType,
    1183              types.FunctionType, types.GeneratorType, types.ModuleType,
    1184              types.LambdaType, types.MethodType), "writeFunc"),
    1185          ((bool,), "writeBoolean"),
    1186          ((types.NoneType,), "writeNull"),
    1187          ((int,long), "writeInteger"),
    1188          ((float,), "writeNumber"),
    1189          (types.StringTypes, "writeString"),
    1190          ((ByteArray,), "writeByteArray"),
    1191          ((datetime.date, datetime.datetime, datetime.time), "writeDate"),
    1192          ((util.is_ET_element,), "writeXML"),
    1193          ((pyamf.UndefinedType,), "writeUndefined"),
    1194          ((types.ClassType, types.TypeType), "writeClass"),
    1195          ((types.InstanceType, types.ObjectType,), "writeInstance"),
    1196      ]
    1197
    1198 -    def __init__(self, *args, **kwargs):
    1199          self.use_proxies = kwargs.pop('use_proxies', use_proxies_default)
    1200          self.string_references = kwargs.pop('string_references', True)
    1201
    1202          pyamf.BaseEncoder.__init__(self, *args, **kwargs)
    1203
    1204 -    def writeElement(self, data, use_references=True, use_proxies=None):
    1205          """
    1206          Writes the data.
    1207
    1208          @param data: The data to be encoded to the AMF3 data stream.
    1209          @type data: C{mixed}
    1210          @param use_references: Default is C{True}.
    1211          @type use_references: C{bool}
    1212          @raise EncodeError: Cannot find encoder func for C{data}.
    1213          """
    1214          func = self._writeElementFunc(data)
    1215
    1216          if func is None:
    1217              raise pyamf.EncodeError("Unknown type %r" % (data,))
    1218
    1219          func(data, use_references=use_references, use_proxies=use_proxies)
    1220
    1221 -    def writeClass(self, *args, **kwargs):
    1222          """
    1223          Classes cannot be serialised.
    1224          """
    1225          raise pyamf.EncodeError("Class objects cannot be serialised")
    1226
    1227 -    def writeUndefined(self, *args, **kwargs):
    1228          """
    1229          Writes an C{pyamf.Undefined} value to the stream.
    1230          """
    1231          self.stream.write(TYPE_UNDEFINED)
    1232
    1233 -    def writeNull(self, *args, **kwargs):
    1234          """
    1235          Writes a C{null} value to the stream.
    1236          """
    1237          self.stream.write(TYPE_NULL)
    1238
    1239 -    def writeBoolean(self, n, **kwargs):
    1240          """
    1241          Writes a Boolean to the stream.
    1242          """
    1243          t = TYPE_BOOL_TRUE
    1244
    1245          if not n:
    1246              t = TYPE_BOOL_FALSE
    1247
    1248          self.stream.write(t)
    1249
    1250 -    def _writeInteger(self, n):
    1251          """
    1252          AMF3 integers are encoded.
    1253
    1254          @param n: The integer data to be encoded to the AMF3 data stream.
    1255          @type n: integer data
    1256
    1257          @see: U{Parsing Integers on OSFlash
    1258          <http://osflash.org/documentation/amf3/parsing_integers>}
    1259          for more info.
    1260          """
    1261          try:
    1262              self.stream.write(ENCODED_INT_CACHE[n])
    1263          except KeyError:
    1264              ENCODED_INT_CACHE[n] = encode_int(n)
    1265              self.stream.write(ENCODED_INT_CACHE[n])
    1266
    1267 -    def writeInteger(self, n, **kwargs):
    1268          """
    1269          Writes an integer to the stream.
    1270
    1271          @type   n: integer data
    1272          @param  n: The integer data to be encoded to the AMF3 data stream.
    1273          @type   use_references: C{bool}
    1274          @kwarg  use_references: Default is C{True}.
    1275          """
    1276          if n < MIN_29B_INT or n > MAX_29B_INT:
    1277              self.writeNumber(float(n))
    1278
    1279              return
    1280
    1281          self.stream.write(TYPE_INTEGER)
    1282          self.stream.write(encode_int(n))
    1283
    1284 -    def writeNumber(self, n, **kwargs):
    1285          """
    1286          Writes a float to the stream.
    1287
    1288          @type n: C{float}
    1289          """
    1290          self.stream.write(TYPE_NUMBER)
    1291          self.stream.write_double(n)
    1292
    1293 -    def _writeString(self, n, **kwargs):
    1294          """
    1295          Writes a raw string to the stream.
    1296
    1297          @type   n: C{str} or C{unicode}
    1298          @param  n: The string data to be encoded to the AMF3 data stream.
    1299          """
    1300          if n == '':
    1301              self.stream.write_uchar(REFERENCE_BIT)
    1302
    1303              return
    1304
    1305          t = type(n)
    1306
    1307          if t is str:
    1308              bytes = n
    1309          elif t is unicode:
    1310              bytes = n.encode('utf8')
    1311          else:
    1312              bytes = unicode(n).encode('utf8')
    1313              n = bytes
    1314
    1315          if self.string_references:
    1316              ref = self.context.getStringReference(n)
    1317
    1318              if ref is not None:
    1319                  self._writeInteger(ref << 1)
    1320
    1321                  return
    1322
    1323              self.context.addString(n)
    1324
    1325          self._writeInteger((len(bytes) << 1) | REFERENCE_BIT)
    1326          self.stream.write(bytes)
    1327
    1328 -    def writeString(self, n, writeType=True, **kwargs):
    1329          """
    1330          Writes a string to the stream. If C{n} is not a unicode string, an
    1331          attempt will be made to convert it.
    1332
    1333          @type   n: C{basestring}
    1334          @param  n: The string data to be encoded to the AMF3 data stream.
    1335          """
    1336          if writeType:
    1337              self.stream.write(TYPE_STRING)
    1338
    1339          self._writeString(n, **kwargs)
    1340
    1341 -    def writeDate(self, n, use_references=True, **kwargs):
    1342          """
    1343          Writes a C{datetime} instance to the stream.
    1344
    1345          @type n: L{datetime}
    1346          @param n: The C{Date} data to be encoded to the AMF3 data stream.
    1347          @type use_references: C{bool}
    1348          @param use_references: Default is C{True}.
    1349          """
    1350          if isinstance(n, datetime.time):
    1351              raise pyamf.EncodeError('A datetime.time instance was found but '
    1352                  'AMF3 has no way to encode time objects. Please use '
    1353                  'datetime.datetime instead (got:%r)' % (n,))
    1354
    1355          self.stream.write(TYPE_DATE)
    1356
    1357          if use_references is True:
    1358              ref = self.context.getObjectReference(n)
    1359
    1360              if ref is not None:
    1361                  self._writeInteger(ref << 1)
    1362
    1363                  return
    1364
    1365              self.context.addObject(n)
    1366
    1367          self.stream.write_uchar(REFERENCE_BIT)
    1368
    1369          if self.timezone_offset is not None:
    1370              n -= self.timezone_offset
    1371
    1372          ms = util.get_timestamp(n)
    1373          self.stream.write_double(ms * 1000.0)
    1374
    1375 -    def writeList(self, n, use_references=True, use_proxies=None):
    1376          """
    1377          Writes a C{tuple}, C{set} or C{list} to the stream.
    1378
    1379          @type n: One of C{__builtin__.tuple}, C{__builtin__.set}
    1380              or C{__builtin__.list}
    1381          @param n: The C{list} data to be encoded to the AMF3 data stream.
    1382          @type use_references: C{bool}
    1383          @param use_references: Default is C{True}.
    1384          """
    1385          # Encode lists as ArrayCollections
    1386          if use_proxies is None:
    1387              use_proxies = self.use_proxies
    1388
    1389          if use_proxies:
    1390              ref_obj = self.context.getObjectAlias(n)
    1391
    1392              if ref_obj is None:
    1393                  proxy = ArrayCollection(n)
    1394                  self.context.setObjectAlias(n, proxy)
    1395                  ref_obj = proxy
    1396
    1397              self.writeObject(ref_obj, use_references, use_proxies=False)
    1398
    1399              return
    1400
    1401          self.stream.write(TYPE_ARRAY)
    1402
    1403          if use_references:
    1404              ref = self.context.getObjectReference(n)
    1405
    1406              if ref is not None:
    1407                  self._writeInteger(ref << 1)
    1408
    1409                  return
    1410
    1411              self.context.addObject(n)
    1412
    1413          self._writeInteger((len(n) << 1) | REFERENCE_BIT)
    1414          self.stream.write_uchar(0x01)
    1415
    1416          [self.writeElement(x) for x in n]
    1417
    1418 -    def writeDict(self, n, use_references=True, use_proxies=None):
    1419          """
    1420          Writes a C{dict} to the stream.
    1421
    1422          @type n: C{__builtin__.dict}
    1423          @param n: The C{dict} data to be encoded to the AMF3 data stream.
    1424          @type use_references: C{bool}
    1425          @param use_references: Default is C{True}.
    1426          @raise ValueError: Non C{int}/C{str} key value found in the C{dict}
    1427          @raise EncodeError: C{dict} contains empty string keys.
    1428          """
    1429
    1430          # Design bug in AMF3 that cannot read/write empty key strings
    1431          # http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug
    1432          # for more info
    1433          if '' in n:
    1434              raise pyamf.EncodeError("dicts cannot contain empty string keys")
    1435
    1436          if use_proxies is None:
    1437              use_proxies = self.use_proxies
    1438
    1439          if use_proxies is True:
    1440              ref_obj = self.context.getObjectAlias(n)
    1441
    1442              if ref_obj is None:
    1443                  proxy = ObjectProxy(pyamf.ASObject(n))
    1444                  self.context.setObjectAlias(n, proxy)
    1445                  ref_obj = proxy
    1446
    1447              self.writeObject(ref_obj, use_references, use_proxies=False)
    1448
    1449              return
    1450
    1451          self.stream.write(TYPE_ARRAY)
    1452
    1453          if use_references:
    1454              ref = self.context.getObjectReference(n)
    1455
    1456              if ref is not None:
    1457                  self._writeInteger(ref << 1)
    1458
    1459                  return
    1460
    1461              self.context.addObject(n)
    1462
    1463          # The AMF3 spec demands that all str based indicies be listed first
    1464          keys = n.keys()
    1465          int_keys = []
    1466          str_keys = []
    1467
    1468          for x in keys:
    1469              if isinstance(x, (int, long)):
    1470                  int_keys.append(x)
    1471              elif isinstance(x, (str, unicode)):
    1472                  str_keys.append(x)
    1473              else:
    1474                  raise ValueError("Non int/str key value found in dict")
    1475
    1476          # Make sure the integer keys are within range
    1477          l = len(int_keys)
    1478
    1479          for x in int_keys:
    1480              if l < x <= 0:
    1481                  # treat as a string key
    1482                  str_keys.append(x)
    1483                  del int_keys[int_keys.index(x)]
    1484
    1485          int_keys.sort()
    1486
    1487          # If integer keys don't start at 0, they will be treated as strings
    1488          if len(int_keys) > 0 and int_keys[0] != 0:
    1489              for x in int_keys:
    1490                  str_keys.append(str(x))
    1491                  del int_keys[int_keys.index(x)]
    1492
    1493          self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT)
    1494
    1495          for x in str_keys:
    1496              self._writeString(x)
    1497              self.writeElement(n[x])
    1498
    1499          self.stream.write_uchar(0x01)
    1500
    1501          for k in int_keys:
    1502              self.writeElement(n[k])
    1503
    1504 -    def writeInstance(self, obj, **kwargs):
    1505          """
    1506          Read class definition.
    1507
    1508          @param obj: The class instance to be encoded.
    1509          """
    1510          kls = obj.__class__
    1511
    1512          if kls is pyamf.MixedArray:
    1513              f = self._write_elem_func_cache[kls] = self.writeDict
    1514          elif kls in (list, set, tuple):
    1515              f = self._write_elem_func_cache[kls] = self.writeList
    1516          else:
    1517              f = self._write_elem_func_cache[kls] = self.writeObject
    1518
    1519          f(obj, **kwargs)
    1520
    1521 -    def writeObject(self, obj, use_references=True, use_proxies=None):
    1522          """
    1523          Writes an object to the stream.
    1524
    1525          @param obj: The object data to be encoded to the AMF3 data stream.
    1526          @type obj: object data
    1527          @param use_references: Default is C{True}.
    1528          @type use_references: C{bool}
    1529          @raise EncodeError: Encoding an object in amf3 tagged as amf0 only.
    1530          """
    1531          if use_proxies is None:
    1532              use_proxies = self.use_proxies
    1533
    1534          if use_proxies is True and obj.__class__ is dict:
    1535              ref_obj = self.context.getObjectAlias(obj)
    1536
    1537              if ref_obj is None:
    1538                  proxy = ObjectProxy(obj)
    1539                  self.context.setObjectAlias(obj, proxy)
    1540                  ref_obj = proxy
    1541
    1542              self.writeObject(ref_obj, use_references, use_proxies=False)
    1543
    1544              return
    1545
    1546          self.stream.write(TYPE_OBJECT)
    1547
    1548          if use_references:
    1549              ref = self.context.getObjectReference(obj)
    1550
    1551              if ref is not None:
    1552                  self._writeInteger(ref << 1)
    1553
    1554                  return
    1555
    1556              self.context.addObject(obj)
    1557
    1558          # object is not referenced, serialise it
    1559          kls = obj.__class__
    1560          definition = self.context.getClass(kls)
    1561          alias = None
    1562          class_ref = False # if the class definition is a reference
    1563
    1564          if definition:
    1565              class_ref = True
    1566              alias = definition.alias
    1567
    1568              if alias.anonymous and definition.reference is not None:
    1569                  class_ref = True
    1570          else:
    1571              try:
    1572                  alias = pyamf.get_class_alias(kls)
    1573              except pyamf.UnknownClassAlias:
    1574                  alias_klass = util.get_class_alias(kls)
    1575                  meta = util.get_class_meta(kls)
    1576
    1577                  alias = alias_klass(kls, defer=True, **meta)
    1578
    1579              definition = ClassDefinition(alias)
    1580
    1581              self.context.addClass(definition, alias.klass)
    1582
    1583          if class_ref:
    1584              self.stream.write(definition.reference)
    1585          else:
    1586              ref = 0
    1587
    1588              if definition.encoding != ObjectEncoding.EXTERNAL:
    1589                  ref += definition.attr_len << 4
    1590
    1591              final_reference = encode_int(ref | definition.encoding << 2 |
    1592                  REFERENCE_BIT << 1 | REFERENCE_BIT)
    1593
    1594              self.stream.write(final_reference)
    1595
    1596              definition.reference = encode_int(
    1597                  definition.reference << 2 | REFERENCE_BIT)
    1598
    1599              if alias.anonymous:
    1600                  self.stream.write_uchar(0x01)
    1601              else:
    1602                  self._writeString(alias.alias)
    1603
    1604              # work out what the final reference for the class will be.
    1605              # this is okay because the next time an object of the same
    1606              # class is encoded, class_ref will be True and never get here
    1607              # again.
    1608
    1609          if alias.external:
    1610              obj.__writeamf__(DataOutput(self))
    1611
    1612              return
    1613
    1614          sa, da = alias.getEncodableAttributes(obj, codec=self)
    1615
    1616          if sa:
    1617              if not class_ref:
    1618                  [self._writeString(attr) for attr in alias.static_attrs]
    1619
    1620              [self.writeElement(sa[attr]) for attr in alias.static_attrs]
    1621
    1622              if definition.encoding == ObjectEncoding.STATIC:
    1623                  return
    1624
    1625          if definition.encoding == ObjectEncoding.DYNAMIC:
    1626              if da:
    1627                  for attr, value in da.iteritems():
    1628                      self._writeString(attr)
    1629                      self.writeElement(value)
    1630
    1631              self.stream.write_uchar(0x01)
    1632
    1633 -    def writeByteArray(self, n, use_references=True, **kwargs):
    1634          """
    1635          Writes a L{ByteArray} to the data stream.
    1636
    1637          @param n: The L{ByteArray} data to be encoded to the AMF3 data stream.
    1638          @type n: L{ByteArray}
    1639          @param use_references: Default is C{True}.
    1640          @type use_references: C{bool}
    1641          """
    1642          self.stream.write(TYPE_BYTEARRAY)
    1643
    1644          if use_references:
    1645              ref = self.context.getObjectReference(n)
    1646
    1647              if ref is not None:
    1648                  self._writeInteger(ref << 1)
    1649
    1650                  return
    1651
    1652              self.context.addObject(n)
    1653
    1654          buf = str(n)
    1655          l = len(buf)
    1656          self._writeInteger(l << 1 | REFERENCE_BIT)
    1657          self.stream.write(buf)
    1658
    1659 -    def writeXML(self, n, use_references=True, use_proxies=None):
    1660          """
    1661          Writes a XML string to the data stream.
    1662
    1663          @type   n: L{ET<util.ET>}
    1664          @param  n: The XML Document to be encoded to the AMF3 data stream.
    1665          @type   use_references: C{bool}
    1666          @param  use_references: Default is C{True}.
    1667          """
    1668          i = self.context.getLegacyXMLReference(n)
    1669
    1670          if i is None:
    1671              is_legacy = True
    1672          else:
    1673              is_legacy = False
    1674
    1675          if is_legacy is True:
    1676              self.stream.write(TYPE_XMLSTRING)
    1677          else:
    1678              self.stream.write(TYPE_XML)
    1679
    1680          if use_references:
    1681              ref = self.context.getObjectReference(n)
    1682
    1683              if ref is not None:
    1684                  self._writeInteger(ref << 1)
    1685
    1686                  return
    1687
    1688              self.context.addObject(n)
    1689
    1690          self._writeString(util.ET.tostring(n, 'utf-8'))
    1691
    1692
    1693 -def decode(stream, context=None, strict=False):
    1694      """
    1695      A helper function to decode an AMF3 datastream.
    1696
    1697      @type   stream: L{BufferedByteStream<util.BufferedByteStream>}
    1698      @param  stream: AMF3 data.
    1699      @type   context: L{Context}
    1700      @param  context: Context.
    1701      """
    1702      decoder = Decoder(stream, context, strict)
    1703
    1704      while 1:
    1705          try:
    1706              yield decoder.readElement()
    1707          except pyamf.EOStream:
    1708              break
    1709
    1710
    1711 -def encode(*args, **kwargs):
    1712      """
    1713      A helper function to encode an element into AMF3 format.
    1714
    1715      @type args: List of args to encode.
    1716      @keyword context: Any initial context to use.
    1717      @type context: L{Context}
    1718      @return: C{StringIO} type object containing the encoded AMF3 data.
    1719      @rtype: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    1720      """
    1721      context = kwargs.get('context', None)
    1722      buf = util.BufferedByteStream()
    1723      encoder = Encoder(buf, context)
    1724
    1725      for element in args:
    1726          encoder.writeElement(element)
    1727
    1728      return buf
    1729
    1730
    1731 -def encode_int(n):
    1732      """
    1733      Encodes an int as a variable length signed 29-bit integer as defined by
    1734      the spec.
    1735
    1736      @param n: The integer to be encoded
    1737      @return: The encoded string
    1738      @rtype: C{str}
    1739      @raise OverflowError: Out of range.
    1740      """
    1741      if n < MIN_29B_INT or n > MAX_29B_INT:
    1742          raise OverflowError("Out of range")
    1743
    1744      if n < 0:
    1745          n += 0x20000000
    1746
    1747      bytes = ''
    1748      real_value = None
    1749
    1750      if n > 0x1fffff:
    1751          real_value = n
    1752          n >>= 1
    1753          bytes += chr(0x80 | ((n >> 21) & 0xff))
    1754
    1755      if n > 0x3fff:
    1756          bytes += chr(0x80 | ((n >> 14) & 0xff))
    1757
    1758      if n > 0x7f:
    1759          bytes += chr(0x80 | ((n >> 7) & 0xff))
    1760
    1761      if real_value is not None:
    1762          n = real_value
    1763
    1764      if n > 0x1fffff:
    1765          bytes += chr(n & 0xff)
    1766      else:
    1767          bytes += chr(n & 0x7f)
    1768
    1769      return bytes
    1770
    1771
    1772 -def decode_int(stream, signed=False):
    1773      """
    1774      Decode C{int}.
    1775      """
    1776      n = result = 0
    1777      b = stream.read_uchar()
    1778
    1779      while b & 0x80 != 0 and n < 3:
    1780          result <<= 7
    1781          result |= b & 0x7f
    1782          b = stream.read_uchar()
    1783          n += 1
    1784
    1785      if n < 3:
    1786          result <<= 7
    1787          result |= b
    1788      else:
    1789          result <<= 8
    1790          result |= b
    1791
    1792          if result & 0x10000000 != 0:
    1793              if signed:
    1794                  result -= 0x20000000
    1795              else:
    1796                  result <<= 1
    1797                  result += 1
    1798
    1799      return result
    1800
    1801  try:
    1802      from cpyamf.amf3 import encode_int, decode_int
    1803  except ImportError:
    1804      pass
    1805
    1806
    1807  pyamf.register_class(ByteArray)
    1808
    1809  for x in range(0, 20):
    1810      ENCODED_INT_CACHE[x] = encode_int(x)
    1811  del x
    1812