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_121207.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):
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) 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):
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) 637 638 dynamic_attrs = util.get_attrs(obj) 639 static_attrs = {} 640 641 for a in self.getStaticAttrs(obj): 642 static_attrs[a] = dynamic_attrs[a] 643 del dynamic_attrs[a] 644 645 return static_attrs, dynamic_attrs
646
647 - def createInstance(self):
648 """ 649 Creates a new instance. 650 """ 651 if self.alias: 652 obj = self.alias.createInstance() 653 else: 654 klass = self.getClass() 655 obj = klass() 656 657 return obj
658
659 - def applyAttributes(self, obj, attrs):
660 """ 661 Applies a collection of attributes C{attrs} to object C{obj} 662 """ 663 if self.alias: 664 self.alias.applyAttributes(obj, attrs) 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 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() 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() 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() 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() 1151 1152 while attr != '': 1153 obj[attr] = self.readElement() 1154 attr = self.readString() 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() 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) 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.XML(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 @raise EncodeError: Unable to encode data. 1318 """ 1319 func = self._writeElementFunc(data) 1320 1321 if func is None: 1322 raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,)) 1323 else: 1324 try: 1325 if isinstance(func, pyamf.CustomTypeFunc): 1326 func(data) 1327 else: 1328 func(data, use_references=use_references) 1329 except (KeyboardInterrupt, SystemExit): 1330 raise 1331 except: 1332 raise
1333
1334 - def writeType(self, type):
1335 """ 1336 Writes the data type to the stream. 1337 1338 @param type: ActionScript type. 1339 @raise EncodeError: AMF3 type is not recognized. 1340 @see: L{ACTIONSCRIPT_TYPES} 1341 """ 1342 if type not in ACTIONSCRIPT_TYPES: 1343 raise pyamf.EncodeError("Unknown AMF3 type 0x%02x at %d" % ( 1344 type, self.stream.tell() - 1)) 1345 1346 self.stream.write_uchar(type)
1347
1348 - def writeFunc(self, *args, **kwargs):
1349 """ 1350 Functions cannot be serialised. 1351 """ 1352 raise pyamf.EncodeError("Callables cannot be serialised")
1353
1354 - def writeUndefined(self, d, use_references=True):
1355 """ 1356 Writes an C{pyamf.Undefined} value to the stream. 1357 1358 @param d: The C{undefined} data to be encoded to the AMF3 data stream. 1359 @type use_references: C{bool} 1360 @param use_references: Default is C{True}. 1361 """ 1362 self.writeType(ASTypes.UNDEFINED)
1363
1364 - def writeNull(self, n, use_references=True):
1365 """ 1366 Writes a C{null} value to the stream. 1367 1368 @param n: The C{null} data to be encoded to the AMF3 data stream. 1369 @type n: C{null} data. 1370 @type use_references: C{bool} 1371 @param use_references: Default is C{True}. 1372 """ 1373 self.writeType(ASTypes.NULL)
1374
1375 - def writeBoolean(self, n, use_references=True):
1376 """ 1377 Writes a Boolean to the stream. 1378 1379 @param n: The C{boolean} data to be encoded to the AMF3 data stream. 1380 @type n: C{bool} 1381 @type use_references: C{bool} 1382 @param use_references: Default is C{True}. 1383 """ 1384 if n: 1385 self.writeType(ASTypes.BOOL_TRUE) 1386 else: 1387 self.writeType(ASTypes.BOOL_FALSE)
1388
1389 - def _writeInteger(self, n):
1390 """ 1391 AMF3 integers are encoded. 1392 1393 @param n: The integer data to be encoded to the AMF3 data stream. 1394 @type n: integer data 1395 1396 @see: U{Parsing Integers on OSFlash 1397 <http://osflash.org/documentation/amf3/parsing_integers>} 1398 for more info. 1399 """ 1400 self.stream.write(encode_int(n))
1401
1402 - def writeInteger(self, n, use_references=True):
1403 """ 1404 Writes an integer to the stream. 1405 1406 @type n: integer data 1407 @param n: The integer data to be encoded to the AMF3 data stream. 1408 @type use_references: C{bool} 1409 @param use_references: Default is C{True}. 1410 """ 1411 if n & 0xf0000000 not in [0, 0xf0000000]: 1412 self.writeNumber(n) 1413 1414 return 1415 1416 self.writeType(ASTypes.INTEGER) 1417 self.stream.write(encode_int(n))
1418
1419 - def writeNumber(self, n, use_references=True):
1420 """ 1421 Writes a non integer to the stream. 1422 1423 @type n: number data 1424 @param n: The number data to be encoded to the AMF3 data stream. 1425 @type use_references: C{bool} 1426 @param use_references: Default is C{True} 1427 """ 1428 self.writeType(ASTypes.NUMBER) 1429 self.stream.write_double(n)
1430
1431 - def _writeString(self, n, use_references=True):
1432 """ 1433 Writes a raw string to the stream. 1434 1435 @type n: C{str} or C{unicode} 1436 @param n: The string data to be encoded to the AMF3 data stream. 1437 @type use_references: C{bool} 1438 @param use_references: Default is C{True}. 1439 """ 1440 if not isinstance(n, basestring): 1441 bytes = unicode(n).encode('utf8') 1442 n = bytes 1443 elif isinstance(n, unicode): 1444 bytes = n.encode('utf8') 1445 else: 1446 bytes = n 1447 1448 if len(bytes) == 0: 1449 self._writeInteger(REFERENCE_BIT) 1450 1451 return 1452 1453 if use_references: 1454 try: 1455 ref = self.context.getStringReference(n) 1456 self._writeInteger(ref << 1) 1457 1458 return 1459 except pyamf.ReferenceError: 1460 self.context.addString(n) 1461 1462 self._writeInteger((len(bytes) << 1) | REFERENCE_BIT) 1463 self.stream.write(bytes)
1464
1465 - def writeString(self, n, use_references=True):
1466 """ 1467 Writes a string to the stream. If C{n} is not a unicode string, an 1468 attempt will be made to convert it. 1469 1470 @type n: C{basestring} 1471 @param n: The string data to be encoded to the AMF3 data stream. 1472 @type use_references: C{bool} 1473 @param use_references: Default is C{True}. 1474 """ 1475 self.writeType(ASTypes.STRING) 1476 self._writeString(n, use_references)
1477
1478 - def writeDate(self, n, use_references=True):
1479 """ 1480 Writes a C{datetime} instance to the stream. 1481 1482 @type n: L{datetime} 1483 @param n: The C{Date} data to be encoded to the AMF3 data stream. 1484 @type use_references: C{bool} 1485 @param use_references: Default is C{True}. 1486 """ 1487 self.writeType(ASTypes.DATE) 1488 1489 if use_references is True: 1490 try: 1491 ref = self.context.getObjectReference(n) 1492 self._writeInteger(ref << 1) 1493 1494 return 1495 except pyamf.ReferenceError: 1496 self.context.addObject(n) 1497 1498 self._writeInteger(REFERENCE_BIT) 1499 1500 ms = util.get_timestamp(n) 1501 self.stream.write_double(ms * 1000.0)
1502
1503 - def writeList(self, n, use_references=True, _use_proxies=None):
1504 """ 1505 Writes a C{tuple}, C{set} or C{list} to the stream. 1506 1507 @type n: One of C{__builtin__.tuple}, C{__builtin__.set} 1508 or C{__builtin__.list} 1509 @param n: The C{list} data to be encoded to the AMF3 data stream. 1510 @type use_references: C{bool} 1511 @param use_references: Default is C{True}. 1512 """ 1513 # Encode lists as ArrayCollections 1514 if _use_proxies is None: 1515 _use_proxies = self.use_proxies 1516 1517 if _use_proxies is True: 1518 try: 1519 ref_obj = self.context.getObjectAlias(n) 1520 except pyamf.ReferenceError: 1521 proxy = ArrayCollection(n) 1522 self.context.setObjectAlias(n, proxy) 1523 ref_obj = proxy 1524 1525 self.writeObject(ref_obj, use_references) 1526 1527 return 1528 1529 self.writeType(ASTypes.ARRAY) 1530 1531 if use_references is True: 1532 try: 1533 ref = self.context.getObjectReference(n) 1534 self._writeInteger(ref << 1) 1535 1536 return 1537 except pyamf.ReferenceError: 1538 self.context.addObject(n) 1539 1540 self._writeInteger(len(n) << 1 | REFERENCE_BIT) 1541 1542 self.stream.write_uchar(0x01) 1543 1544 for x in n: 1545 self.writeElement(x)
1546
1547 - def writeDict(self, n, use_references=True, _use_proxies=None):
1548 """ 1549 Writes a C{dict} to the stream. 1550 1551 @type n: C{__builtin__.dict} 1552 @param n: The C{dict} data to be encoded to the AMF3 data stream. 1553 @type use_references: C{bool} 1554 @param use_references: Default is C{True}. 1555 @raise ValueError: Non C{int}/C{str} key value found in the C{dict} 1556 @raise EncodeError: C{dict} contains empty string keys. 1557 """ 1558 1559 # Design bug in AMF3 that cannot read/write empty key strings 1560 # http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug 1561 # for more info 1562 if n.has_key(''): 1563 raise pyamf.EncodeError("dicts cannot contain empty string keys") 1564 1565 if _use_proxies is None: 1566 _use_proxies = self.use_proxies 1567 1568 if _use_proxies is True: 1569 try: 1570 ref_obj = self.context.getObjectAlias(n) 1571 except pyamf.ReferenceError: 1572 dictObj = pyamf.ASObject(n) 1573 proxy = ObjectProxy(dictObj) 1574 self.context.setObjectAlias(n, proxy) 1575 ref_obj = proxy 1576 1577 self.writeObject(ref_obj, use_references) 1578 1579 return 1580 1581 self.writeType(ASTypes.ARRAY) 1582 1583 if use_references: 1584 try: 1585 ref = self.context.getObjectReference(n) 1586 self._writeInteger(ref << 1) 1587 1588 return 1589 except pyamf.ReferenceError: 1590 self.context.addObject(n) 1591 1592 # The AMF3 spec demands that all str based indicies be listed first 1593 keys = n.keys() 1594 int_keys = [] 1595 str_keys = [] 1596 1597 for x in keys: 1598 if isinstance(x, (int, long)): 1599 int_keys.append(x) 1600 elif isinstance(x, (str, unicode)): 1601 str_keys.append(x) 1602 else: 1603 raise ValueError("Non int/str key value found in dict") 1604 1605 # Make sure the integer keys are within range 1606 l = len(int_keys) 1607 1608 for x in int_keys: 1609 if l < x <= 0: 1610 # treat as a string key 1611 str_keys.append(x) 1612 del int_keys[int_keys.index(x)] 1613 1614 int_keys.sort() 1615 1616 # If integer keys don't start at 0, they will be treated as strings 1617 if len(int_keys) > 0 and int_keys[0] != 0: 1618 for x in int_keys: 1619 str_keys.append(str(x)) 1620 del int_keys[int_keys.index(x)] 1621 1622 self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT) 1623 1624 for x in str_keys: 1625 self._writeString(x) 1626 self.writeElement(n[x]) 1627 1628 self.stream.write_uchar(0x01) 1629 1630 for k in int_keys: 1631 self.writeElement(n[k])
1632
1633 - def _getClassDefinition(self, obj):
1634 """ 1635 Builds a class definition based on the C{obj}. 1636 1637 @raise pyamf.EncodeError: Unable to determine object attributes. 1638 """ 1639 encoding = ObjectEncoding.DYNAMIC 1640 1641 alias = self.context.getClassAlias(obj.__class__) 1642 1643 if alias: 1644 if 'dynamic' in alias.metadata: 1645 encoding = ObjectEncoding.DYNAMIC 1646 elif 'static' in alias.metadata: 1647 encoding = ObjectEncoding.STATIC 1648 elif 'external' in alias.metadata: 1649 encoding = ObjectEncoding.EXTERNAL 1650 1651 class_def = ClassDefinition(alias, encoding) 1652 1653 return class_def
1654
1655 - def writeInstance(self, obj, use_references=True):
1656 """ 1657 Read class definition. 1658 1659 @param obj: The class instance data to be encoded to the AMF3 1660 data stream. 1661 @type obj: instance data 1662 @type use_references: C{bool} 1663 @param use_references: Default is C{True}. 1664 """ 1665 if obj.__class__ in (pyamf.MixedArray,): 1666 self.writeDict(obj, use_references) 1667 elif obj.__class__ in (list, set, tuple): 1668 self.writeList(obj, use_references) 1669 else: 1670 self.writeObject(obj, use_references)
1671
1672 - def writeObject(self, obj, use_references=True, _use_proxies=None):
1673 """ 1674 Writes an object to the stream. 1675 1676 @param obj: The object data to be encoded to the AMF3 data stream. 1677 @type obj: object data 1678 @param use_references: Default is C{True}. 1679 @type use_references: C{bool} 1680 @raise EncodeError: Encoding an object in amf3 tagged as amf0 only. 1681 """ 1682 if _use_proxies is None: 1683 _use_proxies = self.use_proxies 1684 1685 if _use_proxies is True and obj.__class__ is dict: 1686 try: 1687 ref_obj = self.context.getObjectAlias(obj) 1688 except pyamf.ReferenceError: 1689 dictObj = pyamf.ASObject(obj) 1690 proxy = ObjectProxy(dictObj) 1691 1692 self.context.setObjectAlias(obj, proxy) 1693 ref_obj = proxy 1694 1695 self.writeObject(ref_obj, use_references) 1696 1697 return 1698 1699 self.writeType(ASTypes.OBJECT) 1700 1701 if use_references is True: 1702 try: 1703 ref = self.context.getObjectReference(obj) 1704 self._writeInteger(ref << 1) 1705 1706 return 1707 except pyamf.ReferenceError: 1708 self.context.addObject(obj) 1709 1710 try: 1711 ref = self.context.getClassDefinitionReference(obj) 1712 class_ref = True 1713 1714 self._writeInteger(ref << 2 | REFERENCE_BIT) 1715 except pyamf.ReferenceError: 1716 class_def = self._getClassDefinition(obj) 1717 1718 if class_def.alias and 'amf0' in class_def.alias.metadata: 1719 raise pyamf.EncodeError("Encoding an object in amf3 tagged as amf0 only") 1720 1721 self.context.addClassDefinition(class_def) 1722 1723 class_ref = False 1724 ref = 0 1725 1726 if class_def.encoding != ObjectEncoding.EXTERNAL: 1727 ref += len(class_def.getStaticAttrs(obj)) << 4 1728 1729 self._writeInteger(ref | class_def.encoding << 2 | REFERENCE_BIT << 1 | REFERENCE_BIT) 1730 self._writeString(class_def.name) 1731 else: 1732 class_def = self._getClassDefinition(obj) 1733 1734 if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY): 1735 obj.__writeamf__(DataOutput(self)) 1736 else: 1737 static_attrs, dynamic_attrs = class_def.getAttrs(obj) 1738 1739 if static_attrs is not None: 1740 attrs = class_def.getStaticAttrs(obj) 1741 1742 if not class_ref: 1743 [self._writeString(attr) for attr in attrs] 1744 1745 [self.writeElement(static_attrs[attr]) for attr in attrs] 1746 1747 if class_def.encoding is ObjectEncoding.DYNAMIC and dynamic_attrs is not None: 1748 for attr, value in dynamic_attrs.iteritems(): 1749 self._writeString(attr) 1750 self.writeElement(value) 1751 1752 self._writeString('')
1753
1754 - def writeByteArray(self, n, use_references=True):
1755 """ 1756 Writes a L{ByteArray} to the data stream. 1757 1758 @type n: L{ByteArray} 1759 @param n: The L{ByteArray} data to be encoded to the AMF3 data stream. 1760 @type use_references: C{bool} 1761 @param use_references: Default is C{True}. 1762 """ 1763 self.writeType(ASTypes.BYTEARRAY) 1764 1765 if use_references: 1766 try: 1767 ref = self.context.getObjectReference(n) 1768 self._writeInteger(ref << 1) 1769 1770 return 1771 except pyamf.ReferenceError: 1772 self.context.addObject(n) 1773 1774 buf = str(n) 1775 l = len(buf) 1776 self._writeInteger(l << 1 | REFERENCE_BIT) 1777 self.stream.write(buf)
1778
1779 - def writeXML(self, n, use_references=True):
1780 """ 1781 Writes a XML string to the data stream. 1782 1783 @type n: L{ET<util.ET>} 1784 @param n: The XML Document to be encoded to the AMF3 data stream. 1785 @type use_references: C{bool} 1786 @param use_references: Default is C{True}. 1787 """ 1788 try: 1789 self.context.getLegacyXMLReference(n) 1790 is_legacy = True 1791 except pyamf.ReferenceError: 1792 is_legacy = False 1793 1794 if is_legacy is True: 1795 self.writeType(ASTypes.XML) 1796 else: 1797 self.writeType(ASTypes.XMLSTRING) 1798 1799 if use_references: 1800 try: 1801 ref = self.context.getObjectReference(n) 1802 self._writeInteger(ref << 1) 1803 1804 return 1805 except pyamf.ReferenceError: 1806 self.context.addObject(n) 1807 1808 self._writeString(util.ET.tostring(n, 'utf-8'), False)
1809
1810 -def decode(stream, context=None, strict=False):
1811 """ 1812 A helper function to decode an AMF3 datastream. 1813 1814 @type stream: L{BufferedByteStream<util.BufferedByteStream>} 1815 @param stream: AMF3 data. 1816 @type context: L{Context} 1817 @param context: Context. 1818 """ 1819 decoder = Decoder(stream, context, strict) 1820 1821 while 1: 1822 try: 1823 yield decoder.readElement() 1824 except pyamf.EOStream: 1825 break
1826
1827 -def encode(*args, **kwargs):
1828 """ 1829 A helper function to encode an element into AMF3 format. 1830 1831 @type args: List of args to encode. 1832 @keyword context: Any initial context to use. 1833 @type context: L{Context} 1834 @return: C{StringIO} type object containing the encoded AMF3 data. 1835 @rtype: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 1836 """ 1837 context = kwargs.get('context', None) 1838 buf = util.BufferedByteStream() 1839 encoder = Encoder(buf, context) 1840 1841 for element in args: 1842 encoder.writeElement(element) 1843 1844 return buf
1845
1846 -def _encode_int(n):
1847 """ 1848 @raise OverflowError: Out of range. 1849 """ 1850 if n & 0xf0000000 not in [0, 0xf0000000]: 1851 raise OverflowError("Out of range") 1852 1853 bytes = '' 1854 real_value = None 1855 1856 if n < 0: 1857 n += 0x20000000 1858 1859 if n > 0x1fffff: 1860 real_value = n 1861 n >>= 1 1862 bytes += chr(0x80 | ((n >> 21) & 0xff)) 1863 1864 if n > 0x3fff: 1865 bytes += chr(0x80 | ((n >> 14) & 0xff)) 1866 1867 if n > 0x7f: 1868 bytes += chr(0x80 | ((n >> 7) & 0xff)) 1869 1870 if real_value is not None: 1871 n = real_value 1872 1873 if n > 0x1fffff: 1874 bytes += chr(n & 0xff) 1875 else: 1876 bytes += chr(n & 0x7f) 1877 1878 return bytes
1879
1880 -def _decode_int(stream, signed=False):
1881 n = result = 0 1882 b = stream.read_uchar() 1883 1884 while b & 0x80 != 0 and n < 3: 1885 result <<= 7 1886 result |= b & 0x7f 1887 b = stream.read_uchar() 1888 n += 1 1889 1890 if n < 3: 1891 result <<= 7 1892 result |= b 1893 else: 1894 result <<= 8 1895 result |= b 1896 1897 if result & 0x10000000 != 0: 1898 if signed: 1899 result -= 0x20000000 1900 else: 1901 result <<= 1 1902 result += 1 1903 1904 return result
1905 1906 try: 1907 from cpyamf.amf3 import encode_int 1908 except ImportError: 1909 encode_int = _encode_int 1910 try: 1911 from cpyamf.amf3 import decode_int 1912 except ImportError: 1913 decode_int = _decode_int 1914