1
2
3
4
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 @author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>}
24 @author: U{Thijs Triemstra<mailto:info@collab.nl>}
25 @author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
26
27 @since: 0.1.0
28 """
29
30 import types, datetime, zlib
31
32 import pyamf
33 from pyamf import util
34
35 try:
36 set()
37 except NameError:
38 from sets import Set as set
39
41 """
42 All AMF3 data types used in ActionScript 3.0.
43
44 AMF represents ActionScript objects by a single byte representing type, and
45 then by a type-specific byte array that may be of fixed length, may contain
46 length information, or may come with its own end code.
47
48 @see: U{AMF3 data types on OSFlash (external)
49 <http://osflash.org/documentation/amf3#data_types>}
50 """
51
52
53 UNDEFINED = 0x00
54
55
56 NULL = 0x01
57
58
59
60
61
62 BOOL_FALSE = 0x02
63
64
65
66
67
68 BOOL_TRUE = 0x03
69
70
71
72
73 INTEGER = 0x04
74
75
76
77
78
79
80
81 NUMBER = 0x05
82
83
84
85
86
87
88
89 STRING = 0x06
90
91
92
93
94
95
96
97
98
99
100 XML = 0x07
101
102
103
104 DATE = 0x08
105
106
107 ARRAY = 0x09
108
109 OBJECT = 0x0a
110
111
112
113
114
115 XMLSTRING = 0x0b
116
117
118
119
120
121
122 BYTEARRAY = 0x0c
123
124
125 ACTIONSCRIPT_TYPES = []
126
127 for x in ASTypes.__dict__:
128 if not x.startswith('_'):
129 ACTIONSCRIPT_TYPES.append(ASTypes.__dict__[x])
130 del x
131
132
133 REFERENCE_BIT = 0x01
134
136 """
137 AMF object encodings.
138 """
139
140
141
142
143 STATIC = 0x00
144
145
146
147
148
149 EXTERNAL = 0x01
150
151
152
153
154
155
156 DYNAMIC = 0x02
157
158
159 PROXY = 0x03
160
162 """
163 I am a C{StringIO} type object containing byte data from the AMF stream.
164 ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
165 the manipulation of raw data in the form of an Array of bytes.
166 I provide a set of methods for writing binary data with ActionScript 3.0.
167
168 This class is the I/O counterpart to the L{DataInput} class, which reads
169 binary data.
170
171 @see: U{IDataOutput on Livedocs (external)
172 <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataOutput.html>}
173 """
175 """
176 @param encoder: Encoder containing the stream.
177 @type encoder: L{amf3.Encoder<pyamf.amf3.Encoder>}
178 """
179 assert isinstance(encoder, Encoder)
180
181 self.encoder = encoder
182 self.stream = encoder.stream
183
185 """
186 Writes a Boolean value.
187
188 @type value: C{bool}
189 @param value: A Boolean value determining which byte is written.
190 If the parameter is C{True}, C{1} is written; if C{False}, C{0} is
191 written.
192
193 @raise ValueError: Non-boolean value is found.
194 """
195 if isinstance(value, bool):
196 if value is True:
197 self.stream.write_uchar(1)
198 else:
199 self.stream.write_uchar(0)
200 else:
201 raise ValueError("Non-boolean value found")
202
204 """
205 Writes a byte.
206
207 @type value: C{int}
208 """
209 self.stream.write_char(value)
210
212 """
213 Writes an IEEE 754 double-precision (64-bit) floating
214 point number.
215
216 @type value: C{number}
217 """
218 self.stream.write_double(value)
219
221 """
222 Writes an IEEE 754 single-precision (32-bit) floating
223 point number.
224
225 @type value: C{float}
226 """
227 self.stream.write_float(value)
228
230 """
231 Writes a 32-bit signed integer.
232
233 @type value: C{int}
234 """
235 self.stream.write_long(value)
236
238 """
239 Writes a multibyte string to the datastream using the
240 specified character set.
241
242 @type value: C{str}
243 @param value: The string value to be written.
244 @type charset: C{str}
245 @param charset: The string denoting the character
246 set to use. Possible character set strings include
247 C{shift-jis}, C{cn-gb}, C{iso-8859-1} and others.
248 @see: U{Supported character sets on Livedocs (external)
249 <http://livedocs.adobe.com/flex/201/langref/charset-codes.html>}
250 """
251 self.stream.write(unicode(value).encode(charset))
252
254 """
255 Writes an object to data stream in AMF serialized format.
256
257 @param value: The object to be serialized.
258 @type use_references: C{bool}
259 @param use_references:
260 """
261 self.encoder.writeElement(value, use_references)
262
264 """
265 Writes a 16-bit integer.
266
267 @type value: C{int}
268 @param value: A byte value as an integer.
269 """
270 self.stream.write_short(value)
271
273 """
274 Writes a 32-bit unsigned integer.
275
276 @type value: C{int}
277 @param value: A byte value as an unsigned integer.
278 """
279 self.stream.write_ulong(value)
280
282 """
283 Writes a UTF-8 string to the data stream.
284
285 The length of the UTF-8 string in bytes is written first,
286 as a 16-bit integer, followed by the bytes representing the
287 characters of the string.
288
289 @type value: C{str}
290 @param value: The string value to be written.
291 """
292 if not isinstance(value, unicode):
293 value = unicode(value, 'utf8')
294
295 buf = util.BufferedByteStream()
296 buf.write_utf8_string(value)
297 bytes = buf.getvalue()
298
299 self.stream.write_ushort(len(bytes))
300 self.stream.write(bytes)
301
303 """
304 Writes a UTF-8 string. Similar to L{writeUTF}, but does
305 not prefix the string with a 16-bit length word.
306
307 @type value: C{str}
308 @param value: The string value to be written.
309 """
310 val = None
311
312 if isinstance(value, unicode):
313 val = value
314 else:
315 val = unicode(value, 'utf8')
316
317 self.stream.write_utf8_string(val)
318
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 """
509
511 if isinstance(other, ByteArray):
512 return cmp(self.getvalue(), other.getvalue())
513
514 return cmp(self._buffer, other)
515
517 buf = self.getvalue()
518
519 if self.compressed:
520 buf = zlib.compress(buf)
521
522 buf = buf[0] + '\xda' + buf[2:]
523
524 return buf
525
527 """
528 Forces compression of the underlying stream.
529 """
530 self.compressed = True
531
533 """
534 I contain meta relating to the class definition.
535
536 @ivar alias: The alias to this class definition. If this value is C{None},
537 or an empty string, the class is considered to be anonymous.
538 @type alias: L{ClassAlias<pyamf.ClassAlias>}
539 @ivar encoding: The type of encoding to use when serializing the object.
540 @type encoding: C{int}
541 """
543 if alias in (None, ''):
544 self.alias = None
545 elif isinstance(alias, pyamf.ClassAlias):
546 self.alias = alias
547 else:
548 self.alias = pyamf.get_class_alias(alias)
549
550 self.encoding = encoding
551
552 if self.alias and self.alias.attrs is not None:
553 self.static_attrs = self.alias.attrs
554 else:
555 self.static_attrs = []
556
558 if self.alias is None:
559
560 return ''
561
562 if 'anonymous' in self.alias.metadata:
563 return ''
564
565 return str(self.alias)
566
567 name = property(_get_name)
568
570 """
571 If C{alias} is C{None}, an L{anonymous class<pyamf.ASObject>} is
572 returned, otherwise the class is loaded externally.
573 """
574 if self.alias in (None, ''):
575
576 return pyamf.ASObject
577
578 return self.getClassAlias().klass
579
581 """
582 Gets the class alias that is held by this definition.
583
584 @see: L{load_class<pyamf.load_class>}.
585 @raise UnknownClassAlias: Anonymous class definitions do not have
586 class aliases.
587
588 @rtype: L{ClassAlias<pyamf.ClassAlias>}
589 @return: Class definition.
590 """
591 if not hasattr(self, '_alias'):
592 if self.name == '':
593 raise pyamf.UnknownClassAlias, 'Anonymous class definitions do not have class aliases'
594
595 self._alias = pyamf.load_class(self.alias)
596
597 return self._alias
598
600 """
601 Gets the referenced class that is held by this definition.
602 """
603 if hasattr(self, '_alias'):
604 return self._alias
605
606 self._klass = self._getClass()
607
608 return self._klass
609
610 klass = property(getClass)
611
613 """
614 Returns a C{tuple} containing a dict of static and dynamic attributes
615 for C{obj}.
616 """
617 attrs = util.get_instance_attrs(obj, self.alias)
618 static_attrs = dynamic_attrs = None
619
620 if self.alias:
621 if self.alias.attrs:
622 static_attrs = {}
623
624 for attr in self.alias.attrs:
625 static_attrs[attr] = getattr(obj, attr)
626
627 if self.alias.attr_func:
628 dynamic_attrs = {}
629
630 for attr in self.alias.attr_func(obj):
631 dynamic_attrs[attr] = getattr(obj, attr)
632 else:
633 dynamic_attrs = attrs
634 else:
635 dynamic_attrs = attrs
636
637 return (static_attrs, dynamic_attrs)
638
639 -class Context(pyamf.BaseContext):
640 """
641 I hold the AMF3 context for en/decoding streams.
642
643 @ivar strings: A list of string references.
644 @type strings: C{list}
645 @ivar classes: A list of L{ClassDefinition}.
646 @type classes: C{list}
647 @ivar legacy_xml: A list of legacy encoded XML documents.
648 @type legacy_xml: C{list}
649 """
651 """
652 Resets the context.
653 """
654 pyamf.BaseContext.clear(self)
655
656 self.strings = []
657
658 self.classes = []
659 self.rev_classes = {}
660 self.class_defs = {}
661 self.rev_class_defs = {}
662
663 self.legacy_xml = []
664 self.rev_legacy_xml = {}
665
666 - def getString(self, ref):
667 """
668 Gets a string based on a reference C{ref}.
669
670 @param ref: The reference index.
671 @type ref: C{str}
672 @raise ReferenceError: The referenced string could not be found.
673
674 @rtype: C{str}
675 @return: The referenced string.
676 """
677 try:
678 return self.strings[ref]
679 except IndexError:
680 raise pyamf.ReferenceError, "String reference %d not found" % ref
681
682 - def getStringReference(self, s):
683 """
684 Return string reference.
685
686 @type s: C{str}
687 @param s: The referenced string.
688 @raise ReferenceError: The string reference could not be found.
689 @return: The reference index to the string.
690 @rtype: C{int}
691 """
692 try:
693 return self.strings.index(s)
694 except ValueError:
695 raise pyamf.ReferenceError, "Reference for string %r not found" % s
696
697 - def addString(self, s):
698 """
699 Creates a reference to C{s}. If the reference already exists, that
700 reference is returned.
701
702 @type s: C{str}
703 @param s: The string to be referenced.
704 @raise ValueError: Trying to store a reference to an empty string.
705
706 @rtype: C{int}
707 @return: The reference index.
708 """
709 if not isinstance(s, basestring):
710 raise TypeError
711
712 if len(s) == 0:
713
714 raise ValueError, "Cannot store a reference to an empty string"
715
716 try:
717 return self.strings.index(s)
718 except ValueError:
719 self.strings.append(s)
720
721 return len(self.strings) - 1
722
723 - def getClassDefinition(self, ref):
724 """
725 Return class reference.
726
727 @raise ReferenceError: The class reference could not be found.
728 @return: Class reference.
729 """
730 try:
731 return self.classes[ref]
732 except IndexError:
733 raise pyamf.ReferenceError, "Class reference %d not found" % ref
734
736 """
737 Return class definition reference.
738
739 @type class_def: L{ClassDefinition} or C{instance} or C{class}
740 @param class_def: The class definition reference to be found.
741 @raise ReferenceError: The reference could not be found.
742 @return: The reference to C{class_def}.
743 @rtype: C{int}
744 """
745 if not isinstance(class_def, ClassDefinition):
746 if isinstance(class_def, (type, types.ClassType)):
747 try:
748 return self.rev_class_defs[class_def]
749 except KeyError:
750 raise pyamf.ReferenceError("Reference for class definition for %s not found" %
751 class_def)
752 elif isinstance(class_def, (types.InstanceType, types.ObjectType)):
753 try:
754 return self.class_defs[class_def.__class__]
755 except KeyError:
756 raise pyamf.ReferenceError("Reference for class definition for %s not found" %
757 class_def.__class__)
758
759 raise TypeError, 'unable to determine class for %r' % class_def
760 else:
761 try:
762 return self.rev_class_defs[id(class_def)]
763 except ValueError:
764 raise pyamf.ReferenceError, "Reference for class %s not found" % class_def.klass
765
766 - def addClassDefinition(self, class_def):
767 """
768 Creates a reference to C{class_def}.
769 """
770 try:
771 return self.rev_class_defs[id(class_def)]
772 except KeyError:
773 self.classes.append(class_def)
774 idx = len(self.classes) - 1
775
776 self.rev_classes[id(class_def)] = idx
777 self.class_defs[class_def.__class__] = class_def
778 self.rev_class_defs[id(class_def.__class__)] = idx
779
780 return idx
781
782 - def removeClassDefinition(self, class_def):
783 del self.rev_classes[id(class_def)]
784 del self.rev_class_defs[id(class_def.__class__)]
785 del self.class_defs[class_def.__class__]
786
787 - def getLegacyXML(self, ref):
788 """
789 Return the legacy XML reference. This is the C{flash.xml.XMLDocument}
790 class in ActionScript 3.0 and the top-level C{XML} class in
791 ActionScript 1.0 and 2.0.
792
793 @type ref: C{int}
794 @param ref: The reference index.
795 @raise ReferenceError: The reference could not be found.
796 @return: Instance of L{ET<util.ET>}
797 """
798 try:
799 return self.legacy_xml[ref]
800 except IndexError:
801 raise pyamf.ReferenceError(
802 "Legacy XML reference %d not found" % ref)
803
805 """
806 Return legacy XML reference.
807
808 @type doc: L{ET<util.ET>}
809 @param doc: The XML document to reference.
810 @raise ReferenceError: The reference could not be found.
811 @return: The reference to C{doc}.
812 @rtype: C{int}
813 """
814 try:
815 return self.rev_legacy_xml[id(doc)]
816 except KeyError:
817 raise pyamf.ReferenceError, "Reference for document %r not found" % doc
818
819 - def addLegacyXML(self, doc):
820 """
821 Creates a reference to U{doc}.
822
823 If U{doc} is already referenced that index will be returned. Otherwise
824 a new index will be created.
825
826 @type doc: L{ET<util.ET>}
827 @param doc: The XML document to reference.
828 @rtype: C{int}
829 @return: The reference to C{doc}.
830 """
831 try:
832 return self.rev_legacy_xml[id(doc)]
833 except KeyError:
834 self.legacy_xml.append(doc)
835
836 idx = len(self.legacy_xml) - 1
837 self.rev_legacy_xml[id(doc)] = idx
838
839 return idx
840
841 - def __copy__(self):
842 return self.__class__()
843
845 """
846 Decodes an AMF3 data stream.
847 """
848 context_class = Context
849
850 type_map = {
851 ASTypes.UNDEFINED: 'readUndefined',
852 ASTypes.NULL: 'readNull',
853 ASTypes.BOOL_FALSE: 'readBoolFalse',
854 ASTypes.BOOL_TRUE: 'readBoolTrue',
855 ASTypes.INTEGER: 'readSignedInteger',
856 ASTypes.NUMBER: 'readNumber',
857 ASTypes.STRING: 'readString',
858 ASTypes.XML: 'readXML',
859 ASTypes.DATE: 'readDate',
860 ASTypes.ARRAY: 'readArray',
861 ASTypes.OBJECT: 'readObject',
862 ASTypes.XMLSTRING: 'readXMLString',
863 ASTypes.BYTEARRAY: 'readByteArray',
864 }
865
867 """
868 Read and returns the next byte in the stream and determine its type.
869
870 @raise DecodeError: AMF3 type not recognized
871 @return: AMF3 type
872 @rtype: C{int}
873 """
874 type = self.stream.read_uchar()
875
876 if type not in ACTIONSCRIPT_TYPES:
877 raise pyamf.DecodeError, "Unknown AMF3 type 0x%02x at %d" % (type, self.stream.tell() - 1)
878
879 return type
880
886
888 """
889 Read null.
890
891 @return: C{None}
892 @rtype: C{None}
893 """
894 return None
895
897 """
898 Returns C{False}.
899
900 @return: C{False}
901 @rtype: C{bool}
902 """
903 return False
904
906 """
907 Returns C{True}.
908
909 @return: C{True}
910 @rtype: C{bool}
911 """
912 return True
913
915 """
916 Read number.
917 """
918 return self.stream.read_double()
919
921 """
922 Reads and returns an unsigned integer from the stream.
923 """
924 return self.readInteger(False)
925
927 """
928 Reads and returns a signed integer from the stream.
929 """
930 return self.readInteger(True)
931
933 """
934 Reads and returns an integer from the stream.
935
936 @see: U{Parsing integers on OSFlash
937 <http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data
938 format.
939 """
940 n = result = 0
941 b = self.stream.read_uchar()
942
943 while b & 0x80 != 0 and n < 3:
944 result <<= 7
945 result |= b & 0x7f
946 b = self.stream.read_uchar()
947 n += 1
948
949 if n < 3:
950 result <<= 7
951 result |= b
952 else:
953 result <<= 8
954 result |= b
955
956 if result & 0x10000000 != 0:
957 if signed:
958 result -= 0x20000000
959 else:
960 result <<= 1
961 result += 1
962
963 return result
964
966 """
967 Reads and returns a string from the stream.
968
969 @type use_references: C{bool}
970 """
971 def readLength():
972 x = self.readUnsignedInteger()
973
974 return (x >> 1, x & REFERENCE_BIT == 0)
975
976 length, is_reference = readLength()
977
978 if use_references and is_reference:
979 return self.context.getString(length)
980
981 buf = self.stream.read(length)
982 result = unicode(buf, "utf8")
983
984 if len(result) != 0 and use_references:
985 self.context.addString(result)
986
987 return result
988
1006
1008 """
1009 Reads an array from the stream.
1010
1011 @warning: There is a very specific problem with AMF3 where the first
1012 three bytes of an encoded empty C{dict} will mirror that of an encoded
1013 C{{'': 1, '2': 2}}
1014
1015 @see: U{Docuverse blog (external)
1016 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
1017 """
1018 size = self.readUnsignedInteger()
1019
1020 if size & REFERENCE_BIT == 0:
1021 return self.context.getObject(size >> 1)
1022
1023 size >>= 1
1024
1025 key = self.readString()
1026
1027 if key == "":
1028
1029 result = []
1030 self.context.addObject(result)
1031
1032 for i in xrange(size):
1033 result.append(self.readElement())
1034
1035 else:
1036 result = pyamf.MixedArray()
1037 self.context.addObject(result)
1038
1039 while key != "":
1040 el = self.readElement()
1041
1042 try:
1043 result[str(key)] = el
1044 except UnicodeError:
1045 result[key] = el
1046
1047 key = self.readString()
1048
1049 for i in xrange(size):
1050 el = self.readElement()
1051 result[i] = el
1052
1053 return result
1054
1056 """
1057 Reads class definition from the stream.
1058 """
1059 class_ref = ref & REFERENCE_BIT == 0
1060
1061 ref >>= 1
1062
1063 if class_ref:
1064 class_def = self.context.getClassDefinition(ref)
1065 else:
1066 class_def = ClassDefinition(self.readString(), encoding=ref & 0x03)
1067 self.context.addClassDefinition(class_def)
1068
1069 return class_ref, class_def, ref >> 2
1070
1072 """
1073 Reads an object from the stream.
1074 """
1075 def readStatic(is_ref, class_def, obj, num_attrs):
1076 if not is_ref:
1077 for i in range(num_attrs):
1078 key = self.readString()
1079
1080 class_def.static_attrs.append(key)
1081
1082 for attr in class_def.static_attrs:
1083 setattr(obj, attr, self.readElement())
1084
1085 def readDynamic(is_ref, class_def, obj):
1086 attr = self.readString()
1087
1088 while attr != "":
1089 setattr(obj, attr, self.readElement())
1090 attr = self.readString()
1091
1092 ref = self.readUnsignedInteger()
1093
1094 if ref & REFERENCE_BIT == 0:
1095 return self.context.getObject(ref >> 1)
1096
1097 ref >>= 1
1098
1099 class_ref, class_def, num_attrs = self._getClassDefinition(ref)
1100
1101 if class_def.alias and 'amf0' in class_def.alias.metadata:
1102 raise pyamf.EncodeError, "Decoding an object in amf3 tagged as amf0 only is not allowed"
1103
1104 if class_def.alias:
1105 obj = class_def.alias()
1106 else:
1107 klass = class_def.getClass()
1108 obj = klass()
1109
1110 obj_attrs = pyamf.ASObject()
1111 self.context.addObject(obj)
1112
1113 if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
1114 obj.__readamf__(DataInput(self))
1115 elif class_def.encoding == ObjectEncoding.DYNAMIC:
1116 readStatic(class_ref, class_def, obj_attrs, num_attrs)
1117 readDynamic(class_ref, class_def, obj_attrs)
1118 elif class_def.encoding == ObjectEncoding.STATIC:
1119 readStatic(class_ref, class_def, obj_attrs, num_attrs)
1120 else:
1121 raise pyamf.DecodeError, "Unknown object encoding"
1122
1123 if hasattr(obj, '__setstate__'):
1124 obj.__setstate__(obj_attrs)
1125 else:
1126 for k, v in obj_attrs.iteritems():
1127 setattr(obj, k, v)
1128
1129 return obj
1130
1132 """
1133 Reads an object from the stream.
1134
1135 @type legacy: C{bool}
1136 @param legacy: The read XML is in 'legacy' format.
1137 """
1138 ref = self.readUnsignedInteger()
1139
1140 if ref & REFERENCE_BIT == 0:
1141 return self.context.getObject(ref >> 1)
1142
1143 xmlstring = self.stream.read(ref >> 1)
1144
1145 x = util.ET.XML(xmlstring)
1146 self.context.addObject(x)
1147
1148 if legacy is True:
1149 self.context.addLegacyXML(x)
1150
1151 return x
1152
1154 """
1155 Reads a string from the data stream and converts it into an XML Tree.
1156
1157 @return: The XML Document.
1158 @rtype: L{ET<util.ET>}
1159 """
1160 return self._readXML()
1161
1163 """
1164 Read a legacy XML Document from the stream.
1165
1166 @return: The XML Document.
1167 @rtype: L{ET<util.ET>}
1168 """
1169 return self._readXML(True)
1170
1172 """
1173 Reads a string of data from the stream.
1174
1175 Detects if the L{ByteArray} was compressed using C{zlib}.
1176
1177 @see: L{ByteArray}
1178 @note: This is not supported in ActionScript 1.0 and 2.0.
1179 """
1180 ref = self.readUnsignedInteger()
1181
1182 if ref & REFERENCE_BIT == 0:
1183 return self.context.getObject(ref >> 1)
1184
1185 buffer = self.stream.read(ref >> 1)
1186
1187 try:
1188 buffer = zlib.decompress(buffer)
1189 compressed = True
1190 except zlib.error:
1191 compressed = False
1192
1193 obj = ByteArray(buffer, context=self.context)
1194 obj.compressed = compressed
1195
1196 self.context.addObject(obj)
1197
1198 return obj
1199
1201 """
1202 Encodes an AMF3 data stream.
1203 """
1204 context_class = Context
1205
1206 type_map = [
1207
1208 ((types.BuiltinFunctionType, types.BuiltinMethodType,),
1209 "writeUnsupported"),
1210 ((bool,), "writeBoolean"),
1211 ((types.NoneType,), "writeNull"),
1212 ((int,long), "writeInteger"),
1213 ((float,), "writeNumber"),
1214 ((types.StringTypes,), "writeString"),
1215 ((ByteArray,), "writeByteArray"),
1216 ((datetime.date, datetime.datetime), "writeDate"),
1217 ((util.ET.iselement,), "writeXML"),
1218 ((lambda x: x is pyamf.Undefined,), "writeUndefined"),
1219 ((types.InstanceType, types.ObjectType,), "writeInstance"),
1220 ]
1221
1223 """
1224 Writes the data.
1225
1226 @type data: C{mixed}
1227 @param data: The data to be encoded to the AMF3 data stream.
1228 @type use_references: C{bool}
1229 @param use_references:
1230 @raise EncodeError: Unable to encode data.
1231 """
1232 func = self._writeElementFunc(data)
1233
1234 if func is None:
1235
1236 self.writeUnsupported(data)
1237 else:
1238 try:
1239 if isinstance(func, pyamf.CustomTypeFunc):
1240 func(data)
1241 else:
1242 func(data, use_references=use_references)
1243 except (KeyboardInterrupt, SystemExit):
1244 raise
1245 except:
1246 raise
1247 raise pyamf.EncodeError, "Unable to encode '%r'" % data
1248
1250 """
1251 Writes the data type to the stream.
1252
1253 @param type: ActionScript type.
1254 @raise EncodeError: AMF3 type is not recognized.
1255 """
1256 if type not in ACTIONSCRIPT_TYPES:
1257 raise pyamf.EncodeError("Unknown AMF3 type 0x%02x at %d" % (
1258 type, self.stream.tell() - 1))
1259
1260 self.stream.write_uchar(type)
1261
1263 """
1264 Writes an C{pyamf.Undefined} value to the stream.
1265
1266 @type use_references: C{bool}
1267 """
1268 self.writeType(ASTypes.UNDEFINED)
1269
1270 - def writeNull(self, n, use_references=True):
1271 """
1272 Writes a C{null} value to the stream.
1273
1274 @type n:
1275 @param n: C{null} data.
1276 @type use_references: C{bool}
1277 @param use_references:
1278 """
1279 self.writeType(ASTypes.NULL)
1280
1282 """
1283 Writes a Boolean to the stream.
1284
1285 @param n:
1286 @type n: boolean data
1287 @type use_references: C{bool}
1288 @param use_references:
1289 """
1290 if n:
1291 self.writeType(ASTypes.BOOL_TRUE)
1292 else:
1293 self.writeType(ASTypes.BOOL_FALSE)
1294
1296 """
1297 AMF3 integers are encoded.
1298
1299 @see: U{Parsing Integers on OSFlash
1300 <http://osflash.org/documentation/amf3/parsing_integers>}
1301 for more info.
1302 """
1303 self.stream.write(_encode_int(n))
1304
1306 """
1307 Writes an integer to the stream.
1308
1309 @type n:
1310 @param n: integer data.
1311 @type use_references: C{bool}
1312 @param use_references:
1313 """
1314 if n & 0xf0000000 not in [0, 0xf0000000]:
1315 self.writeNumber(n)
1316
1317 return
1318
1319 self.writeType(ASTypes.INTEGER)
1320 self.stream.write(_encode_int(n))
1321
1323 """
1324 Writes a non integer to the stream.
1325
1326 @type n:
1327 @param n: number data.
1328 @type use_references: C{bool}
1329 @param use_references:
1330 """
1331 self.writeType(ASTypes.NUMBER)
1332 self.stream.write_double(n)
1333
1335 """
1336 Writes a raw string to the stream.
1337
1338 @type n: C{str} or C{unicode}
1339 @param n: string data.
1340 @type use_references: C{bool}
1341 @param use_references:
1342 """
1343 if not isinstance(n, basestring):
1344 bytes = unicode(n).encode('utf8')
1345 n = bytes
1346 elif isinstance(n, unicode):
1347 bytes = n.encode('utf8')
1348 else:
1349 bytes = n
1350
1351 if len(bytes) == 0:
1352 self._writeInteger(REFERENCE_BIT)
1353
1354 return
1355
1356 if use_references:
1357 try:
1358 ref = self.context.getStringReference(n)
1359 self._writeInteger(ref << 1)
1360
1361 return
1362 except pyamf.ReferenceError:
1363 self.context.addString(n)
1364
1365 self._writeInteger((len(bytes) << 1) | REFERENCE_BIT)
1366 self.stream.write(bytes)
1367
1369 """
1370 Writes a string to the stream. If C{n} is not a unicode string, an
1371 attempt will be made to convert it.
1372
1373 @type n: C{basestring}
1374 @param n: string data.
1375 @type use_references: C{bool}
1376 @param use_references:
1377 """
1378 self.writeType(ASTypes.STRING)
1379 self._writeString(n, use_references)
1380
1381 - def writeDate(self, n, use_references=True):
1405
1406 - def writeList(self, n, use_references=True):
1433
1434 - def writeDict(self, n, use_references=True):
1435 """
1436 Writes a C{dict} to the stream.
1437
1438 @type n: C{__builtin__.dict}
1439 @param n: C{dict} data.
1440 @type use_references: C{bool}
1441 @param use_references:
1442 @raise ValueError: Non C{int}/C{str} key value found in the C{dict}
1443 @raise EncodeError: C{dict} contains empty string keys.
1444 """
1445 self.writeType(ASTypes.ARRAY)
1446
1447 if use_references:
1448 try:
1449 ref = self.context.getObjectReference(n)
1450 self._writeInteger(ref << 1)
1451
1452 return
1453 except pyamf.ReferenceError:
1454 self.context.addObject(n)
1455
1456
1457 keys = n.keys()
1458 int_keys = []
1459 str_keys = []
1460
1461 for x in keys:
1462 if isinstance(x, (int, long)):
1463 int_keys.append(x)
1464 elif isinstance(x, (str, unicode)):
1465 str_keys.append(x)
1466 else:
1467 raise ValueError, "Non int/str key value found in dict"
1468
1469
1470 l = len(int_keys)
1471
1472 for x in int_keys:
1473 if l < x <= 0:
1474
1475 str_keys.append(x)
1476 del int_keys[int_keys.index(x)]
1477
1478 int_keys.sort()
1479
1480
1481 if len(int_keys) > 0 and int_keys[0] != 0:
1482 for x in int_keys:
1483 str_keys.append(str(x))
1484 del int_keys[int_keys.index(x)]
1485
1486 self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT)
1487
1488 for x in str_keys:
1489
1490
1491
1492 if x == '':
1493 raise pyamf.EncodeError, "dicts cannot contain empty string keys"
1494
1495 self._writeString(x)
1496 self.writeElement(n[x])
1497
1498 self.stream.write_uchar(0x01)
1499
1500 for k in int_keys:
1501 self.writeElement(n[k])
1502
1504 """
1505 Builds a class definition based on the C{obj}.
1506 """
1507 encoding = ObjectEncoding.DYNAMIC
1508
1509 alias = self.context.getClassAlias(obj.__class__)
1510
1511 if alias:
1512 if 'dynamic' in alias.metadata:
1513 encoding = ObjectEncoding.DYNAMIC
1514 elif 'static' in alias.metadata:
1515 encoding = ObjectEncoding.STATIC
1516 elif 'external' in alias.metadata:
1517 encoding = ObjectEncoding.EXTERNAL
1518
1519 class_def = ClassDefinition(alias, encoding)
1520
1521 if alias and encoding == ObjectEncoding.STATIC:
1522 if alias.attrs is not None:
1523 import copy
1524
1525 class_def.static_attrs = copy.copy(alias.attrs)
1526 else:
1527 if hasattr(obj, 'keys'):
1528 for k in obj.keys():
1529 class_def.static_attrs.append(unicode(k))
1530 elif hasattr(obj, 'iteritems'):
1531 for k, v in obj.iteritems():
1532 class_def.static_attrs.append(unicode(k))
1533 elif hasattr(obj, '__dict__'):
1534 for k in obj.__dict__.keys():
1535 class_def.static_attrs.append(unicode(k))
1536 else:
1537 raise pyamf.EncodingError, 'Unable to determine object attributes'
1538
1539 return class_def
1540
1542 """
1543 Read class definition.
1544
1545 @type use_references: C{bool}
1546 """
1547 if obj.__class__ == pyamf.MixedArray:
1548 self.writeDict(obj, use_references)
1549 elif obj.__class__ in (list, set, tuple):
1550 self.writeList(obj, use_references)
1551 else:
1552 self.writeObject(obj, use_references)
1553
1611
1636
1637 - def writeXML(self, n, use_references=True):
1667
1668 -def decode(stream, context=None):
1669 """
1670 A helper function to decode an AMF3 datastream.
1671
1672 @type stream: L{BufferedByteStream<util.BufferedByteStream>}
1673 @param stream: AMF3 data.
1674 @type context: L{Context}
1675 @param context: Context.
1676 """
1677 decoder = Decoder(stream, context)
1678
1679 while 1:
1680 try:
1681 yield decoder.readElement()
1682 except pyamf.EOStream:
1683 break
1684
1686 """
1687 A helper function to encode an element into AMF3 format.
1688
1689 @type args: List of args to encode.
1690 @param context: Any initial context to use.
1691 @type context: L{Context}
1692 @return: C{StringIO} type object containing the encoded AMF3 data.
1693 @rtype: L{util.BufferedByteStream}
1694 """
1695 context = kwargs.get('context', None)
1696 buf = util.BufferedByteStream()
1697 encoder = Encoder(buf, context)
1698
1699 for element in args:
1700 encoder.writeElement(element)
1701
1702 return buf
1703
1705 if n & 0xf0000000 not in [0, 0xf0000000]:
1706 raise ValueError, "Out of range"
1707
1708 bytes = ''
1709 real_value = None
1710
1711 if n < 0:
1712 n += 0x20000000
1713
1714 if n > 0x1fffff:
1715 real_value = n
1716 n >>= 1
1717 bytes += chr(0x80 | ((n >> 21) & 0xff))
1718
1719 if n > 0x3fff:
1720 bytes += chr(0x80 | ((n >> 14) & 0xff))
1721
1722 if n > 0x7f:
1723 bytes += chr(0x80 | ((n >> 7) & 0xff))
1724
1725 if real_value is not None:
1726 n = real_value
1727
1728 if n > 0x1fffff:
1729 bytes += chr(n & 0xff)
1730 else:
1731 bytes += chr(n & 0x7f)
1732
1733 return bytes
1734