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