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_05_05_08.pdf>}
18 @see: U{Official AMF3 Specification in Japanese (external)
19 <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf3_spec_121207.pdf>}
20 @see: U{AMF3 documentation on OSFlash (external)
21 <http://osflash.org/documentation/amf3>}
22
23 @since: 0.1
24 """
25
26 import types
27 import datetime
28 import zlib
29
30 import pyamf
31 from pyamf import util
32 from pyamf.flex import ObjectProxy, ArrayCollection
33
34
35
36 use_proxies_default = False
37
38 try:
39 set()
40 except NameError:
41 from sets import Set as set
42
43
44
45
46 TYPE_UNDEFINED = '\x00'
47
48
49 TYPE_NULL = '\x01'
50
51
52
53 TYPE_BOOL_FALSE = '\x02'
54
55
56
57 TYPE_BOOL_TRUE = '\x03'
58
59
60
61
62 TYPE_INTEGER = '\x04'
63
64
65
66
67
68
69 TYPE_NUMBER = '\x05'
70
71
72
73
74
75
76 TYPE_STRING = '\x06'
77
78
79
80
81
82
83
84
85
86
87 TYPE_XML = '\x07'
88
89
90
91 TYPE_DATE = '\x08'
92
93
94 TYPE_ARRAY = '\x09'
95
96 TYPE_OBJECT = '\x0A'
97
98
99
100
101
102 TYPE_XMLSTRING = '\x0B'
103
104
105
106
107
108
109 TYPE_BYTEARRAY = '\x0C'
110
111
112 REFERENCE_BIT = 0x01
113
114
115 MAX_29B_INT = 0x3FFFFFFF
116
117 ENCODED_INT_CACHE = {}
118
119
121 """
122 AMF object encodings.
123 """
124
125
126
127
128 STATIC = 0x00
129
130
131
132
133
134 EXTERNAL = 0x01
135
136
137
138
139
140
141 DYNAMIC = 0x02
142
143
144 PROXY = 0x03
145
146
148 """
149 I am a C{StringIO} type object containing byte data from the AMF stream.
150 ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
151 the manipulation of raw data in the form of an Array of bytes.
152 I provide a set of methods for writing binary data with ActionScript 3.0.
153
154 This class is the I/O counterpart to the L{DataInput} class, which reads
155 binary data.
156
157 @see: U{IDataOutput on Livedocs (external)
158 <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataOutput.html>}
159 """
161 """
162 @param encoder: Encoder containing the stream.
163 @type encoder: L{amf3.Encoder<pyamf.amf3.Encoder>}
164 """
165 self.encoder = encoder
166 self.stream = encoder.stream
167
169 """
170 Writes a Boolean value.
171
172 @type value: C{bool}
173 @param value: A C{Boolean} value determining which byte is written.
174 If the parameter is C{True}, C{1} is written; if C{False}, C{0} is
175 written.
176
177 @raise ValueError: Non-boolean value found.
178 """
179 if isinstance(value, bool):
180 if value is True:
181 self.stream.write_uchar(1)
182 else:
183 self.stream.write_uchar(0)
184 else:
185 raise ValueError("Non-boolean value found")
186
188 """
189 Writes a byte.
190
191 @type value: C{int}
192 """
193 self.stream.write_char(value)
194
196 """
197 Writes an unsigned byte.
198
199 @type value: C{int}
200 @since: 0.5
201 """
202 return self.stream.write_uchar(value)
203
205 """
206 Writes an IEEE 754 double-precision (64-bit) floating
207 point number.
208
209 @type value: C{number}
210 """
211 self.stream.write_double(value)
212
214 """
215 Writes an IEEE 754 single-precision (32-bit) floating
216 point number.
217
218 @type value: C{float}
219 """
220 self.stream.write_float(value)
221
223 """
224 Writes a 32-bit signed integer.
225
226 @type value: C{int}
227 """
228 self.stream.write_long(value)
229
231 """
232 Writes a multibyte string to the datastream using the
233 specified character set.
234
235 @type value: C{str}
236 @param value: The string value to be written.
237 @type charset: C{str}
238 @param charset: The string denoting the character set to use. Possible
239 character set strings include C{shift-jis}, C{cn-gb},
240 C{iso-8859-1} and others.
241 @see: U{Supported character sets on Livedocs (external)
242 <http://livedocs.adobe.com/flex/201/langref/charset-codes.html>}
243 """
244 self.stream.write(unicode(value).encode(charset))
245
246 - def writeObject(self, value, use_references=True, use_proxies=None):
247 """
248 Writes an object to data stream in AMF serialized format.
249
250 @param value: The object to be serialized.
251 @type use_references: C{bool}
252 @param use_references:
253 """
254 self.encoder.writeElement(value, use_references, use_proxies)
255
257 """
258 Writes a 16-bit integer.
259
260 @type value: C{int}
261 @param value: A byte value as an integer.
262 """
263 self.stream.write_short(value)
264
266 """
267 Writes a 16-bit unsigned integer.
268
269 @type value: C{int}
270 @param value: A byte value as an integer.
271 @since: 0.5
272 """
273 self.stream.write_ushort(value)
274
276 """
277 Writes a 32-bit unsigned integer.
278
279 @type value: C{int}
280 @param value: A byte value as an unsigned integer.
281 """
282 self.stream.write_ulong(value)
283
285 """
286 Writes a UTF-8 string to the data stream.
287
288 The length of the UTF-8 string in bytes is written first,
289 as a 16-bit integer, followed by the bytes representing the
290 characters of the string.
291
292 @type value: C{str}
293 @param value: The string value to be written.
294 """
295 if not isinstance(value, unicode):
296 value = unicode(value, 'utf8')
297
298 buf = util.BufferedByteStream()
299 buf.write_utf8_string(value)
300 bytes = buf.getvalue()
301
302 self.stream.write_ushort(len(bytes))
303 self.stream.write(bytes)
304
306 """
307 Writes a UTF-8 string. Similar to L{writeUTF}, but does
308 not prefix the string with a 16-bit length word.
309
310 @type value: C{str}
311 @param value: The string value to be written.
312 """
313 val = None
314
315 if isinstance(value, unicode):
316 val = value
317 else:
318 val = unicode(value, 'utf8')
319
320 self.stream.write_utf8_string(val)
321
322
488
489
490 -class ByteArray(util.BufferedByteStream, DataInput, DataOutput):
491 """
492 I am a C{StringIO} type object containing byte data from the AMF stream.
493 ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
494 the manipulation of raw data in the form of an Array of bytes.
495
496 Supports C{zlib} compression.
497
498 Possible uses of the C{ByteArray} class:
499 - Creating a custom protocol to connect to a client.
500 - Writing your own AMF/Remoting packet.
501 - Optimizing the size of your data by using custom data types.
502
503 @see: U{ByteArray on Livedocs (external)
504 <http://livedocs.adobe.com/flex/201/langref/flash/utils/ByteArray.html>}
505 """
506
509
518
524
526 buf = self.getvalue()
527
528 if self.compressed:
529 buf = zlib.compress(buf)
530
531 buf = buf[0] + '\xda' + buf[2:]
532
533 return buf
534
536 """
537 Forces compression of the underlying stream.
538 """
539 self.compressed = True
540
541
543 """
544 """
545
564
566 return '<%s.ClassDefinition reference=%r encoding=%r alias=%r at 0x%x>' % (
567 self.__class__.__module__, self.reference, self.encoding, self.alias, id(self))
568
569
570 -class Context(pyamf.BaseContext):
571 """
572 I hold the AMF3 context for en/decoding streams.
573
574 @ivar strings: A list of string references.
575 @type strings: C{list}
576 @ivar classes: A list of L{ClassDefinition}.
577 @type classes: C{list}
578 @ivar legacy_xml: A list of legacy encoded XML documents.
579 @type legacy_xml: C{list}
580 """
581
582 - def __init__(self, exceptions=True):
583 self.strings = util.IndexedCollection(use_hash=True, exceptions=False)
584 self.classes = {}
585 self.class_ref = {}
586 self.legacy_xml = util.IndexedCollection(exceptions=False)
587 self.object_aliases = util.IndexedMap(exceptions=False)
588
589 self.class_idx = 0
590
591 pyamf.BaseContext.__init__(self, exceptions=exceptions)
592
594 """
595 Clears the context.
596 """
597 pyamf.BaseContext.clear(self)
598
599 self.strings.clear()
600 self.classes = {}
601 self.class_ref = {}
602 self.legacy_xml.clear()
603 self.object_aliases.clear()
604
605 self.class_idx = 0
606
607 - def setObjectAlias(self, obj, alias):
608 """
609 Maps an object to an aliased object.
610
611 @since: 0.4
612 """
613 self.object_aliases.map(obj, alias)
614
615 - def getObjectAlias(self, obj):
616 """
617 Get an alias of an object.
618
619 @since: 0.4
620 @raise pyamf.ReferenceError: Unknown object alias.
621 @raise pyamf.ReferenceError: Unknown mapped alias.
622 """
623 ref = self.object_aliases.getReferenceTo(obj)
624
625 if ref is None:
626 if self.exceptions is False:
627 return None
628
629 raise pyamf.ReferenceError('Unknown object alias for %r' % (obj,))
630
631 mapped = self.object_aliases.getMappedByReference(ref)
632
633 if mapped is None:
634 if self.exceptions is False:
635 return None
636
637 raise pyamf.ReferenceError('Unknown mapped alias for %r' % (obj,))
638
639 return mapped
640
641 - def getString(self, ref):
642 """
643 Gets a string based on a reference C{ref}.
644
645 @param ref: The reference index.
646 @type ref: C{str}
647 @raise pyamf.ReferenceError: The referenced string could not be found.
648
649 @rtype: C{str}
650 @return: The referenced string.
651 """
652 i = self.strings.getByReference(ref)
653
654 if i is None and self.exceptions:
655 raise pyamf.ReferenceError("String reference %r not found" % (ref,))
656
657 return i
658
659 - def getStringReference(self, s):
660 """
661 Return string reference.
662
663 @type s: C{str}
664 @param s: The referenced string.
665 @raise pyamf.ReferenceError: The string reference could not be found.
666 @return: The reference index to the string.
667 @rtype: C{int}
668 """
669 i = self.strings.getReferenceTo(s)
670
671 if i is None and self.exceptions:
672 raise pyamf.ReferenceError("Reference for string %r not found" % (s,))
673
674 return i
675
676 - def addString(self, s):
677 """
678 Creates a reference to C{s}. If the reference already exists, that
679 reference is returned.
680
681 @type s: C{str}
682 @param s: The string to be referenced.
683 @rtype: C{int}
684 @return: The reference index.
685
686 @raise TypeError: The parameter C{s} is not of C{basestring} type.
687 @raise pyamf.ReferenceError: Trying to store a reference to an empty string.
688 """
689 if not isinstance(s, basestring):
690 raise TypeError
691
692 if len(s) == 0:
693 if not self.exceptions:
694 return None
695
696
697 raise pyamf.ReferenceError("Cannot store a reference to an empty string")
698
699 return self.strings.append(s)
700
701 - def getClassByReference(self, ref):
702 """
703 Return class reference.
704
705 @raise pyamf.ReferenceError: The class reference could not be found.
706 @return: Class reference.
707 """
708 try:
709 return self.class_ref[ref]
710 except KeyError:
711 if not self.exceptions:
712 return None
713
714 raise pyamf.ReferenceError("Class reference %r not found" % (
715 ref,))
716
717 - def getClass(self, klass):
718 """
719 Return class reference.
720
721 @raise pyamf.ReferenceError: The class reference could not be found.
722 @return: Class reference.
723 """
724 try:
725 return self.classes[klass]
726 except KeyError:
727 if not self.exceptions:
728 return None
729
730 raise pyamf.ReferenceError("Class alias for %r not found" % (
731 klass,))
732
733 - def addClass(self, alias, klass):
734 """
735 Creates a reference to C{class_def}.
736
737 @param alias: C{ClassDefinition} instance.
738 """
739 ref = self.class_idx
740
741 self.class_ref[ref] = alias
742 cd = self.classes[klass] = alias
743
744 cd.reference = ref
745
746 self.class_idx += 1
747
748 return ref
749
750 - def getLegacyXML(self, ref):
751 """
752 Return the legacy XML reference. This is the C{flash.xml.XMLDocument}
753 class in ActionScript 3.0 and the top-level C{XML} class in
754 ActionScript 1.0 and 2.0.
755
756 @type ref: C{int}
757 @param ref: The reference index.
758 @raise pyamf.ReferenceError: The legacy XML reference could not be found.
759 @return: Instance of L{ET<util.ET>}
760 """
761 i = self.legacy_xml.getByReference(ref)
762
763 if i is None:
764 if not self.exceptions:
765 return None
766
767 raise pyamf.ReferenceError("Legacy XML reference %r not found" % (ref,))
768
769 return i
770
772 """
773 Return legacy XML reference.
774
775 @type doc: L{ET<util.ET>}
776 @param doc: The XML document to reference.
777 @raise pyamf.ReferenceError: The reference could not be found.
778 @return: The reference to C{doc}.
779 @rtype: C{int}
780 """
781 i = self.legacy_xml.getReferenceTo(doc)
782
783 if i is None:
784 if not self.exceptions:
785 return None
786
787 raise pyamf.ReferenceError("Reference for document %r not found" % (doc,))
788
789 return i
790
791 - def addLegacyXML(self, doc):
792 """
793 Creates a reference to C{doc}.
794
795 If C{doc} is already referenced that index will be returned. Otherwise
796 a new index will be created.
797
798 @type doc: L{ET<util.ET>}
799 @param doc: The XML document to reference.
800 @rtype: C{int}
801 @return: The reference to C{doc}.
802 """
803 return self.legacy_xml.append(doc)
804
805 - def __copy__(self):
806 return self.__class__(exceptions=self.exceptions)
807
808
810 """
811 Decodes an AMF3 data stream.
812 """
813 context_class = Context
814
815 type_map = {
816 TYPE_UNDEFINED: 'readUndefined',
817 TYPE_NULL: 'readNull',
818 TYPE_BOOL_FALSE: 'readBoolFalse',
819 TYPE_BOOL_TRUE: 'readBoolTrue',
820 TYPE_INTEGER: 'readSignedInteger',
821 TYPE_NUMBER: 'readNumber',
822 TYPE_STRING: 'readString',
823 TYPE_XML: 'readXML',
824 TYPE_DATE: 'readDate',
825 TYPE_ARRAY: 'readArray',
826 TYPE_OBJECT: 'readObject',
827 TYPE_XMLSTRING: 'readXMLString',
828 TYPE_BYTEARRAY: 'readByteArray',
829 }
830
835
841
843 """
844 Read null.
845
846 @return: C{None}
847 @rtype: C{None}
848 """
849 return None
850
852 """
853 Returns C{False}.
854
855 @return: C{False}
856 @rtype: C{bool}
857 """
858 return False
859
861 """
862 Returns C{True}.
863
864 @return: C{True}
865 @rtype: C{bool}
866 """
867 return True
868
870 """
871 Read number.
872 """
873 return self.stream.read_double()
874
876 """
877 Reads and returns an unsigned integer from the stream.
878 """
879 return self.readInteger(False)
880
882 """
883 Reads and returns a signed integer from the stream.
884 """
885 return self.readInteger(True)
886
888 """
889 Reads and returns an integer from the stream.
890
891 @type signed: C{bool}
892 @see: U{Parsing integers on OSFlash
893 <http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data
894 format.
895 """
896 return decode_int(self.stream, signed)
897
899 """
900 Reads and returns a string from the stream.
901
902 @type use_references: C{bool}
903 """
904 def readLength():
905 x = self.readUnsignedInteger()
906
907 return (x >> 1, x & REFERENCE_BIT == 0)
908
909 length, is_reference = readLength()
910
911 if use_references and is_reference:
912 return self.context.getString(length)
913
914 if length == 0:
915 return u''
916
917 result = self.stream.read_utf8_string(length)
918
919 if len(result) != 0 and use_references:
920 self.context.addString(result)
921
922 return result
923
944
987
1029
1031 """
1032 Reads an object from the stream.
1033
1034 @raise pyamf.EncodeError: Decoding an object in amf3 tagged as amf0
1035 only is not allowed.
1036 @raise pyamf.DecodeError: Unknown object encoding.
1037 """
1038 if use_proxies is None:
1039 use_proxies = self.use_proxies
1040
1041 def readStatic(class_def, obj):
1042 for attr in class_def.static_properties:
1043 obj[attr] = self.readElement()
1044
1045 def readDynamic(class_def, obj):
1046 attr = self.readString().encode('utf8')
1047
1048 while attr != '':
1049 obj[attr] = self.readElement()
1050 attr = self.readString().encode('utf8')
1051
1052 ref = self.readUnsignedInteger()
1053
1054 if ref & REFERENCE_BIT == 0:
1055 obj = self.context.getObject(ref >> 1)
1056
1057 if use_proxies is True:
1058 obj = self.readProxyObject(obj)
1059
1060 return obj
1061
1062 ref >>= 1
1063
1064 class_def, alias = self._getClassDefinition(ref)
1065
1066 obj = alias.createInstance(codec=self)
1067 obj_attrs = dict()
1068
1069 self.context.addObject(obj)
1070
1071 if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
1072 obj.__readamf__(DataInput(self))
1073 elif class_def.encoding == ObjectEncoding.DYNAMIC:
1074 readStatic(class_def, obj_attrs)
1075 readDynamic(class_def, obj_attrs)
1076 elif class_def.encoding == ObjectEncoding.STATIC:
1077 readStatic(class_def, obj_attrs)
1078 else:
1079 raise pyamf.DecodeError("Unknown object encoding")
1080
1081 alias.applyAttributes(obj, obj_attrs, codec=self)
1082
1083 if use_proxies is True:
1084 obj = self.readProxyObject(obj)
1085
1086 return obj
1087
1089 """
1090 Return the source object of a proxied object.
1091
1092 @since: 0.4
1093 """
1094 if isinstance(proxy, ArrayCollection):
1095 return list(proxy)
1096 elif isinstance(proxy, ObjectProxy):
1097 return proxy._amf_object
1098
1099 return proxy
1100
1102 """
1103 Reads an object from the stream.
1104
1105 @type legacy: C{bool}
1106 @param legacy: The read XML is in 'legacy' format.
1107 """
1108 ref = self.readUnsignedInteger()
1109
1110 if ref & REFERENCE_BIT == 0:
1111 return self.context.getObject(ref >> 1)
1112
1113 xmlstring = self.stream.read(ref >> 1)
1114
1115 x = util.ET.fromstring(xmlstring)
1116 self.context.addObject(x)
1117
1118 if legacy is True:
1119 self.context.addLegacyXML(x)
1120
1121 return x
1122
1124 """
1125 Reads a string from the data stream and converts it into
1126 an XML Tree.
1127
1128 @return: The XML Document.
1129 @rtype: L{ET<util.ET>}
1130 """
1131 return self._readXML()
1132
1134 """
1135 Read a legacy XML Document from the stream.
1136
1137 @return: The XML Document.
1138 @rtype: L{ET<util.ET>}
1139 """
1140 return self._readXML(True)
1141
1143 """
1144 Reads a string of data from the stream.
1145
1146 Detects if the L{ByteArray} was compressed using C{zlib}.
1147
1148 @see: L{ByteArray}
1149 @note: This is not supported in ActionScript 1.0 and 2.0.
1150 """
1151 ref = self.readUnsignedInteger()
1152
1153 if ref & REFERENCE_BIT == 0:
1154 return self.context.getObject(ref >> 1)
1155
1156 buffer = self.stream.read(ref >> 1)
1157
1158 try:
1159 buffer = zlib.decompress(buffer)
1160 compressed = True
1161 except zlib.error:
1162 compressed = False
1163
1164 obj = ByteArray(buffer, context=self.context)
1165 obj.compressed = compressed
1166
1167 self.context.addObject(obj)
1168
1169 return obj
1170
1171
1173 """
1174 Encodes an AMF3 data stream.
1175 """
1176 context_class = Context
1177
1178 type_map = [
1179 ((types.BuiltinFunctionType, types.BuiltinMethodType,
1180 types.FunctionType, types.GeneratorType, types.ModuleType,
1181 types.LambdaType, types.MethodType), "writeFunc"),
1182 ((bool,), "writeBoolean"),
1183 ((types.NoneType,), "writeNull"),
1184 ((int,long), "writeInteger"),
1185 ((float,), "writeNumber"),
1186 (types.StringTypes, "writeString"),
1187 ((ByteArray,), "writeByteArray"),
1188 ((datetime.date, datetime.datetime, datetime.time), "writeDate"),
1189 ((util.is_ET_element,), "writeXML"),
1190 ((pyamf.UndefinedType,), "writeUndefined"),
1191 ((types.ClassType, types.TypeType), "writeClass"),
1192 ((types.InstanceType, types.ObjectType,), "writeInstance"),
1193 ]
1194
1200
1201 - def writeElement(self, data, use_references=True, use_proxies=None):
1202 """
1203 Writes the data.
1204
1205 @param data: The data to be encoded to the AMF3 data stream.
1206 @type data: C{mixed}
1207 @param use_references: Default is C{True}.
1208 @type use_references: C{bool}
1209 @raise EncodeError: Cannot find encoder func for C{data}.
1210 """
1211 func = self._writeElementFunc(data)
1212
1213 if func is None:
1214 raise pyamf.EncodeError("Unknown type %r" % (data,))
1215
1216 func(data, use_references=use_references, use_proxies=use_proxies)
1217
1219 """
1220 Classes cannot be serialised.
1221 """
1222 raise pyamf.EncodeError("Class objects cannot be serialised")
1223
1225 """
1226 Writes an C{pyamf.Undefined} value to the stream.
1227 """
1228 self.stream.write(TYPE_UNDEFINED)
1229
1231 """
1232 Writes a C{null} value to the stream.
1233 """
1234 self.stream.write(TYPE_NULL)
1235
1246
1248 """
1249 AMF3 integers are encoded.
1250
1251 @param n: The integer data to be encoded to the AMF3 data stream.
1252 @type n: integer data
1253
1254 @see: U{Parsing Integers on OSFlash
1255 <http://osflash.org/documentation/amf3/parsing_integers>}
1256 for more info.
1257 """
1258 try:
1259 self.stream.write(ENCODED_INT_CACHE[n])
1260 except KeyError:
1261 ENCODED_INT_CACHE[n] = encode_int(n)
1262 self.stream.write(ENCODED_INT_CACHE[n])
1263
1265 """
1266 Writes an integer to the stream.
1267
1268 @type n: integer data
1269 @param n: The integer data to be encoded to the AMF3 data stream.
1270 @type use_references: C{bool}
1271 @kwarg use_references: Default is C{True}.
1272 """
1273 if n < 0 or n > MAX_29B_INT:
1274 self.writeNumber(float(n))
1275
1276 return
1277
1278 self.stream.write(TYPE_INTEGER)
1279 self.stream.write(encode_int(n))
1280
1282 """
1283 Writes a float to the stream.
1284
1285 @type n: C{float}
1286 """
1287 self.stream.write(TYPE_NUMBER)
1288 self.stream.write_double(n)
1289
1291 """
1292 Writes a raw string to the stream.
1293
1294 @type n: C{str} or C{unicode}
1295 @param n: The string data to be encoded to the AMF3 data stream.
1296 """
1297 if n == '':
1298 self.stream.write_uchar(REFERENCE_BIT)
1299
1300 return
1301
1302 t = type(n)
1303
1304 if t is str:
1305 bytes = n
1306 elif t is unicode:
1307 bytes = n.encode('utf8')
1308 else:
1309 bytes = unicode(n).encode('utf8')
1310 n = bytes
1311
1312 if self.string_references:
1313 ref = self.context.getStringReference(n)
1314
1315 if ref is not None:
1316 self._writeInteger(ref << 1)
1317
1318 return
1319
1320 self.context.addString(n)
1321
1322 self._writeInteger((len(bytes) << 1) | REFERENCE_BIT)
1323 self.stream.write(bytes)
1324
1326 """
1327 Writes a string to the stream. If C{n} is not a unicode string, an
1328 attempt will be made to convert it.
1329
1330 @type n: C{basestring}
1331 @param n: The string data to be encoded to the AMF3 data stream.
1332 @type use_references: C{bool}
1333 @kwarg use_references: Default is C{True}.
1334 """
1335 self.stream.write(TYPE_STRING)
1336
1337 self._writeString(n, **kwargs)
1338
1339 - def writeDate(self, n, use_references=True, **kwargs):
1340 """
1341 Writes a C{datetime} instance to the stream.
1342
1343 @type n: L{datetime}
1344 @param n: The C{Date} data to be encoded to the AMF3 data stream.
1345 @type use_references: C{bool}
1346 @param use_references: Default is C{True}.
1347 """
1348 if isinstance(n, datetime.time):
1349 raise pyamf.EncodeError('A datetime.time instance was found but '
1350 'AMF3 has no way to encode time objects. Please use '
1351 'datetime.datetime instead (got:%r)' % (n,))
1352
1353 self.stream.write(TYPE_DATE)
1354
1355 if use_references is True:
1356 ref = self.context.getObjectReference(n)
1357
1358 if ref is not None:
1359 self._writeInteger(ref << 1)
1360
1361 return
1362
1363 self.context.addObject(n)
1364
1365 self.stream.write_uchar(REFERENCE_BIT)
1366
1367 if self.timezone_offset is not None:
1368 n -= self.timezone_offset
1369
1370 ms = util.get_timestamp(n)
1371 self.stream.write_double(ms * 1000.0)
1372
1373 - def writeList(self, n, use_references=True, use_proxies=None):
1374 """
1375 Writes a C{tuple}, C{set} or C{list} to the stream.
1376
1377 @type n: One of C{__builtin__.tuple}, C{__builtin__.set}
1378 or C{__builtin__.list}
1379 @param n: The C{list} data to be encoded to the AMF3 data stream.
1380 @type use_references: C{bool}
1381 @param use_references: Default is C{True}.
1382 """
1383
1384 if use_proxies is None:
1385 use_proxies = self.use_proxies
1386
1387 if use_proxies:
1388 ref_obj = self.context.getObjectAlias(n)
1389
1390 if ref_obj is None:
1391 proxy = ArrayCollection(n)
1392 self.context.setObjectAlias(n, proxy)
1393 ref_obj = proxy
1394
1395 self.writeObject(ref_obj, use_references, use_proxies=False)
1396
1397 return
1398
1399 self.stream.write(TYPE_ARRAY)
1400
1401 if use_references:
1402 ref = self.context.getObjectReference(n)
1403
1404 if ref is not None:
1405 self._writeInteger(ref << 1)
1406
1407 return
1408
1409 self.context.addObject(n)
1410
1411 self._writeInteger((len(n) << 1) | REFERENCE_BIT)
1412 self.stream.write_uchar(0x01)
1413
1414 [self.writeElement(x) for x in n]
1415
1416 - def writeDict(self, n, use_references=True, use_proxies=None):
1417 """
1418 Writes a C{dict} to the stream.
1419
1420 @type n: C{__builtin__.dict}
1421 @param n: The C{dict} data to be encoded to the AMF3 data stream.
1422 @type use_references: C{bool}
1423 @param use_references: Default is C{True}.
1424 @raise ValueError: Non C{int}/C{str} key value found in the C{dict}
1425 @raise EncodeError: C{dict} contains empty string keys.
1426 """
1427
1428
1429
1430
1431 if '' in n:
1432 raise pyamf.EncodeError("dicts cannot contain empty string keys")
1433
1434 if use_proxies is None:
1435 use_proxies = self.use_proxies
1436
1437 if use_proxies is True:
1438 ref_obj = self.context.getObjectAlias(n)
1439
1440 if ref_obj is None:
1441 proxy = ObjectProxy(pyamf.ASObject(n))
1442 self.context.setObjectAlias(n, proxy)
1443 ref_obj = proxy
1444
1445 self.writeObject(ref_obj, use_references, use_proxies=False)
1446
1447 return
1448
1449 self.stream.write(TYPE_ARRAY)
1450
1451 if use_references:
1452 ref = self.context.getObjectReference(n)
1453
1454 if ref is not None:
1455 self._writeInteger(ref << 1)
1456
1457 return
1458
1459 self.context.addObject(n)
1460
1461
1462 keys = n.keys()
1463 int_keys = []
1464 str_keys = []
1465
1466 for x in keys:
1467 if isinstance(x, (int, long)):
1468 int_keys.append(x)
1469 elif isinstance(x, (str, unicode)):
1470 str_keys.append(x)
1471 else:
1472 raise ValueError("Non int/str key value found in dict")
1473
1474
1475 l = len(int_keys)
1476
1477 for x in int_keys:
1478 if l < x <= 0:
1479
1480 str_keys.append(x)
1481 del int_keys[int_keys.index(x)]
1482
1483 int_keys.sort()
1484
1485
1486 if len(int_keys) > 0 and int_keys[0] != 0:
1487 for x in int_keys:
1488 str_keys.append(str(x))
1489 del int_keys[int_keys.index(x)]
1490
1491 self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT)
1492
1493 for x in str_keys:
1494 self._writeString(x)
1495 self.writeElement(n[x])
1496
1497 self.stream.write_uchar(0x01)
1498
1499 for k in int_keys:
1500 self.writeElement(n[k])
1501
1503 """
1504 Read class definition.
1505
1506 @param obj: The class instance to be encoded.
1507 """
1508 kls = obj.__class__
1509
1510 if kls is pyamf.MixedArray:
1511 f = self._write_elem_func_cache[kls] = self.writeDict
1512 elif kls in (list, set, tuple):
1513 f = self._write_elem_func_cache[kls] = self.writeList
1514 else:
1515 f = self._write_elem_func_cache[kls] = self.writeObject
1516
1517 f(obj, **kwargs)
1518
1519 - def writeObject(self, obj, use_references=True, use_proxies=None):
1520 """
1521 Writes an object to the stream.
1522
1523 @param obj: The object data to be encoded to the AMF3 data stream.
1524 @type obj: object data
1525 @param use_references: Default is C{True}.
1526 @type use_references: C{bool}
1527 @raise EncodeError: Encoding an object in amf3 tagged as amf0 only.
1528 """
1529 if use_proxies is None:
1530 use_proxies = self.use_proxies
1531
1532 if use_proxies is True and obj.__class__ is dict:
1533 ref_obj = self.context.getObjectAlias(obj)
1534
1535 if ref_obj is None:
1536 proxy = ObjectProxy(obj)
1537 self.context.setObjectAlias(obj, proxy)
1538 ref_obj = proxy
1539
1540 self.writeObject(ref_obj, use_references, use_proxies=False)
1541
1542 return
1543
1544 self.stream.write(TYPE_OBJECT)
1545
1546 if use_references:
1547 ref = self.context.getObjectReference(obj)
1548
1549 if ref is not None:
1550 self._writeInteger(ref << 1)
1551
1552 return
1553
1554 self.context.addObject(obj)
1555
1556
1557 kls = obj.__class__
1558 definition = self.context.getClass(kls)
1559 alias = None
1560 class_ref = False
1561
1562 if definition:
1563 class_ref = True
1564 alias = definition.alias
1565
1566 if alias.anonymous and definition.reference is not None:
1567 class_ref = True
1568 else:
1569 try:
1570 alias = pyamf.get_class_alias(kls)
1571 except pyamf.UnknownClassAlias:
1572 alias_klass = util.get_class_alias(kls)
1573
1574 alias = alias_klass(kls, defer=True)
1575
1576 definition = ClassDefinition(alias)
1577
1578 self.context.addClass(definition, alias.klass)
1579
1580 if class_ref:
1581 self.stream.write(definition.reference)
1582
1583 if alias.anonymous:
1584 self.stream.write_uchar(0x01)
1585 else:
1586 ref = 0
1587
1588 if definition.encoding != ObjectEncoding.EXTERNAL:
1589 ref += definition.attr_len << 4
1590
1591 final_reference = encode_int(ref | definition.encoding << 2 |
1592 REFERENCE_BIT << 1 | REFERENCE_BIT)
1593
1594 self.stream.write(final_reference)
1595
1596 if alias.anonymous:
1597 self.stream.write_uchar(0x01)
1598
1599 else:
1600 self._writeString(alias.alias)
1601
1602
1603
1604
1605
1606 if alias.anonymous:
1607 definition.reference = final_reference
1608 else:
1609 definition.reference = encode_int(
1610 definition.reference << 2 | REFERENCE_BIT)
1611
1612 if alias.external:
1613 obj.__writeamf__(DataOutput(self))
1614
1615 return
1616
1617 sa, da = alias.getEncodableAttributes(obj, codec=self)
1618
1619 if sa:
1620 if not class_ref:
1621 [self._writeString(attr) for attr in alias.static_attrs]
1622
1623 [self.writeElement(sa[attr]) for attr in alias.static_attrs]
1624
1625 if definition.encoding == ObjectEncoding.STATIC:
1626 return
1627
1628 if definition.encoding == ObjectEncoding.DYNAMIC:
1629 if da:
1630 for attr, value in da.iteritems():
1631 self._writeString(attr)
1632 self.writeElement(value)
1633
1634 self.stream.write_uchar(0x01)
1635
1637 """
1638 Writes a L{ByteArray} to the data stream.
1639
1640 @param n: The L{ByteArray} data to be encoded to the AMF3 data stream.
1641 @type n: L{ByteArray}
1642 @param use_references: Default is C{True}.
1643 @type use_references: C{bool}
1644 """
1645 self.stream.write(TYPE_BYTEARRAY)
1646
1647 if use_references:
1648 ref = self.context.getObjectReference(n)
1649
1650 if ref is not None:
1651 self._writeInteger(ref << 1)
1652
1653 return
1654
1655 self.context.addObject(n)
1656
1657 buf = str(n)
1658 l = len(buf)
1659 self._writeInteger(l << 1 | REFERENCE_BIT)
1660 self.stream.write(buf)
1661
1662 - def writeXML(self, n, use_references=True, use_proxies=None):
1663 """
1664 Writes a XML string to the data stream.
1665
1666 @type n: L{ET<util.ET>}
1667 @param n: The XML Document to be encoded to the AMF3 data stream.
1668 @type use_references: C{bool}
1669 @param use_references: Default is C{True}.
1670 """
1671 i = self.context.getLegacyXMLReference(n)
1672
1673 if i is None:
1674 is_legacy = True
1675 else:
1676 is_legacy = False
1677
1678 if is_legacy is True:
1679 self.stream.write(TYPE_XMLSTRING)
1680 else:
1681 self.stream.write(TYPE_XML)
1682
1683 if use_references:
1684 ref = self.context.getObjectReference(n)
1685
1686 if ref is not None:
1687 self._writeInteger(ref << 1)
1688
1689 return
1690
1691 self.context.addObject(n)
1692
1693 self._writeString(util.ET.tostring(n, 'utf-8'))
1694
1695
1696 -def decode(stream, context=None, strict=False):
1697 """
1698 A helper function to decode an AMF3 datastream.
1699
1700 @type stream: L{BufferedByteStream<util.BufferedByteStream>}
1701 @param stream: AMF3 data.
1702 @type context: L{Context}
1703 @param context: Context.
1704 """
1705 decoder = Decoder(stream, context, strict)
1706
1707 while 1:
1708 try:
1709 yield decoder.readElement()
1710 except pyamf.EOStream:
1711 break
1712
1713
1715 """
1716 A helper function to encode an element into AMF3 format.
1717
1718 @type args: List of args to encode.
1719 @keyword context: Any initial context to use.
1720 @type context: L{Context}
1721 @return: C{StringIO} type object containing the encoded AMF3 data.
1722 @rtype: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
1723 """
1724 context = kwargs.get('context', None)
1725 buf = util.BufferedByteStream()
1726 encoder = Encoder(buf, context)
1727
1728 for element in args:
1729 encoder.writeElement(element)
1730
1731 return buf
1732
1733
1735 """
1736 Encodes an int as a variable length unsigned 29-bit integer as defined by
1737 the spec.
1738
1739 @param n: The integer to be encoded
1740 @return: The encoded string
1741 @rtype: C{str}
1742 @raise OverflowError: Out of range.
1743 """
1744 if n < 0 or n > MAX_29B_INT:
1745 raise OverflowError("Out of range")
1746
1747 bytes = ''
1748 real_value = None
1749
1750 if n > 0x1fffff:
1751 real_value = n
1752 n >>= 1
1753 bytes += chr(0x80 | ((n >> 21) & 0xff))
1754
1755 if n > 0x3fff:
1756 bytes += chr(0x80 | ((n >> 14) & 0xff))
1757
1758 if n > 0x7f:
1759 bytes += chr(0x80 | ((n >> 7) & 0xff))
1760
1761 if real_value is not None:
1762 n = real_value
1763
1764 if n > 0x1fffff:
1765 bytes += chr(n & 0xff)
1766 else:
1767 bytes += chr(n & 0x7f)
1768
1769 return bytes
1770
1771
1773 """
1774 Decode C{int}.
1775 """
1776 n = result = 0
1777 b = stream.read_uchar()
1778
1779 while b & 0x80 != 0 and n < 3:
1780 result <<= 7
1781 result |= b & 0x7f
1782 b = stream.read_uchar()
1783 n += 1
1784
1785 if n < 3:
1786 result <<= 7
1787 result |= b
1788 else:
1789 result <<= 8
1790 result |= b
1791
1792 if result & 0x10000000 != 0:
1793 if signed:
1794 result -= 0x20000000
1795 else:
1796 result <<= 1
1797 result += 1
1798
1799 return result
1800
1801 try:
1802 from cpyamf.amf3 import encode_int, decode_int
1803 except ImportError:
1804 pass
1805
1806
1807 pyamf.register_class(ByteArray)
1808
1809 for x in range(0, 20):
1810 ENCODED_INT_CACHE[x] = encode_int(x)
1811 del x
1812