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