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 = 0x0FFFFFFF
116
117
118 MIN_29B_INT = -0x10000000
119
120 ENCODED_INT_CACHE = {}
121
122
124 """
125 AMF object encodings.
126 """
127
128
129
130
131 STATIC = 0x00
132
133
134
135
136
137 EXTERNAL = 0x01
138
139
140
141
142
143
144 DYNAMIC = 0x02
145
146
147 PROXY = 0x03
148
149
151 """
152 I am a C{StringIO} type object containing byte data from the AMF stream.
153 ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
154 the manipulation of raw data in the form of an Array of bytes.
155 I provide a set of methods for writing binary data with ActionScript 3.0.
156
157 This class is the I/O counterpart to the L{DataInput} class, which reads
158 binary data.
159
160 @see: U{IDataOutput on Livedocs (external)
161 <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataOutput.html>}
162 """
164 """
165 @param encoder: Encoder containing the stream.
166 @type encoder: L{amf3.Encoder<pyamf.amf3.Encoder>}
167 """
168 self.encoder = encoder
169 self.stream = encoder.stream
170
172 """
173 Writes a Boolean value.
174
175 @type value: C{bool}
176 @param value: A C{Boolean} value determining which byte is written.
177 If the parameter is C{True}, C{1} is written; if C{False}, C{0} is
178 written.
179
180 @raise ValueError: Non-boolean value found.
181 """
182 if isinstance(value, bool):
183 if value is True:
184 self.stream.write_uchar(1)
185 else:
186 self.stream.write_uchar(0)
187 else:
188 raise ValueError("Non-boolean value found")
189
191 """
192 Writes a byte.
193
194 @type value: C{int}
195 """
196 self.stream.write_char(value)
197
199 """
200 Writes an unsigned byte.
201
202 @type value: C{int}
203 @since: 0.5
204 """
205 return self.stream.write_uchar(value)
206
208 """
209 Writes an IEEE 754 double-precision (64-bit) floating
210 point number.
211
212 @type value: C{number}
213 """
214 self.stream.write_double(value)
215
217 """
218 Writes an IEEE 754 single-precision (32-bit) floating
219 point number.
220
221 @type value: C{float}
222 """
223 self.stream.write_float(value)
224
226 """
227 Writes a 32-bit signed integer.
228
229 @type value: C{int}
230 """
231 self.stream.write_long(value)
232
234 """
235 Writes a multibyte string to the datastream using the
236 specified character set.
237
238 @type value: C{str}
239 @param value: The string value to be written.
240 @type charset: C{str}
241 @param charset: The string denoting the character set to use. Possible
242 character set strings include C{shift-jis}, C{cn-gb},
243 C{iso-8859-1} and others.
244 @see: U{Supported character sets on Livedocs (external)
245 <http://livedocs.adobe.com/flex/201/langref/charset-codes.html>}
246 """
247 self.stream.write(unicode(value).encode(charset))
248
249 - def writeObject(self, value, use_references=True, use_proxies=None):
250 """
251 Writes an object to data stream in AMF serialized format.
252
253 @param value: The object to be serialized.
254 @type use_references: C{bool}
255 @param use_references:
256 """
257 self.encoder.writeElement(value, use_references, use_proxies)
258
260 """
261 Writes a 16-bit integer.
262
263 @type value: C{int}
264 @param value: A byte value as an integer.
265 """
266 self.stream.write_short(value)
267
269 """
270 Writes a 16-bit unsigned integer.
271
272 @type value: C{int}
273 @param value: A byte value as an integer.
274 @since: 0.5
275 """
276 self.stream.write_ushort(value)
277
279 """
280 Writes a 32-bit unsigned integer.
281
282 @type value: C{int}
283 @param value: A byte value as an unsigned integer.
284 """
285 self.stream.write_ulong(value)
286
288 """
289 Writes a UTF-8 string to the data stream.
290
291 The length of the UTF-8 string in bytes is written first,
292 as a 16-bit integer, followed by the bytes representing the
293 characters of the string.
294
295 @type value: C{str}
296 @param value: The string value to be written.
297 """
298 if not isinstance(value, unicode):
299 value = unicode(value, 'utf8')
300
301 buf = util.BufferedByteStream()
302 buf.write_utf8_string(value)
303 bytes = buf.getvalue()
304
305 self.stream.write_ushort(len(bytes))
306 self.stream.write(bytes)
307
309 """
310 Writes a UTF-8 string. Similar to L{writeUTF}, but does
311 not prefix the string with a 16-bit length word.
312
313 @type value: C{str}
314 @param value: The string value to be written.
315 """
316 val = None
317
318 if isinstance(value, unicode):
319 val = value
320 else:
321 val = unicode(value, 'utf8')
322
323 self.stream.write_utf8_string(val)
324
325
491
492
493 -class ByteArray(util.BufferedByteStream, DataInput, DataOutput):
494 """
495 I am a C{StringIO} type object containing byte data from the AMF stream.
496 ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
497 the manipulation of raw data in the form of an Array of bytes.
498
499 Supports C{zlib} compression.
500
501 Possible uses of the C{ByteArray} class:
502 - Creating a custom protocol to connect to a client.
503 - Writing your own AMF/Remoting packet.
504 - Optimizing the size of your data by using custom data types.
505
506 @see: U{ByteArray on Livedocs (external)
507 <http://livedocs.adobe.com/flex/201/langref/flash/utils/ByteArray.html>}
508 """
509
512
521
527
529 buf = self.getvalue()
530
531 if self.compressed:
532 buf = zlib.compress(buf)
533
534 buf = buf[0] + '\xda' + buf[2:]
535
536 return buf
537
539 """
540 Forces compression of the underlying stream.
541 """
542 self.compressed = True
543
544
546 """
547 """
548
567
569 return '<%s.ClassDefinition reference=%r encoding=%r alias=%r at 0x%x>' % (
570 self.__class__.__module__, self.reference, self.encoding, self.alias, id(self))
571
572
573 -class Context(pyamf.BaseContext):
574 """
575 I hold the AMF3 context for en/decoding streams.
576
577 @ivar strings: A list of string references.
578 @type strings: C{list}
579 @ivar classes: A list of L{ClassDefinition}.
580 @type classes: C{list}
581 @ivar legacy_xml: A list of legacy encoded XML documents.
582 @type legacy_xml: C{list}
583 """
584
585 - def __init__(self, exceptions=True):
586 self.strings = util.IndexedCollection(use_hash=True, exceptions=False)
587 self.classes = {}
588 self.class_ref = {}
589 self.legacy_xml = util.IndexedCollection(exceptions=False)
590 self.object_aliases = util.IndexedMap(exceptions=False)
591
592 self.class_idx = 0
593
594 pyamf.BaseContext.__init__(self, exceptions=exceptions)
595
597 """
598 Clears the context.
599 """
600 pyamf.BaseContext.clear(self)
601
602 self.strings.clear()
603 self.classes = {}
604 self.class_ref = {}
605 self.legacy_xml.clear()
606 self.object_aliases.clear()
607
608 self.class_idx = 0
609
610 - def setObjectAlias(self, obj, alias):
611 """
612 Maps an object to an aliased object.
613
614 @since: 0.4
615 """
616 self.object_aliases.map(obj, alias)
617
618 - def getObjectAlias(self, obj):
619 """
620 Get an alias of an object.
621
622 @since: 0.4
623 @raise pyamf.ReferenceError: Unknown object alias.
624 @raise pyamf.ReferenceError: Unknown mapped alias.
625 """
626 ref = self.object_aliases.getReferenceTo(obj)
627
628 if ref is None:
629 if self.exceptions is False:
630 return None
631
632 raise pyamf.ReferenceError('Unknown object alias for %r' % (obj,))
633
634 mapped = self.object_aliases.getMappedByReference(ref)
635
636 if mapped is None:
637 if self.exceptions is False:
638 return None
639
640 raise pyamf.ReferenceError('Unknown mapped alias for %r' % (obj,))
641
642 return mapped
643
644 - def getString(self, ref):
645 """
646 Gets a string based on a reference C{ref}.
647
648 @param ref: The reference index.
649 @type ref: C{str}
650 @raise pyamf.ReferenceError: The referenced string could not be found.
651
652 @rtype: C{str}
653 @return: The referenced string.
654 """
655 i = self.strings.getByReference(ref)
656
657 if i is None and self.exceptions:
658 raise pyamf.ReferenceError("String reference %r not found" % (ref,))
659
660 return i
661
662 - def getStringReference(self, s):
663 """
664 Return string reference.
665
666 @type s: C{str}
667 @param s: The referenced string.
668 @raise pyamf.ReferenceError: The string reference could not be found.
669 @return: The reference index to the string.
670 @rtype: C{int}
671 """
672 i = self.strings.getReferenceTo(s)
673
674 if i is None and self.exceptions:
675 raise pyamf.ReferenceError("Reference for string %r not found" % (s,))
676
677 return i
678
679 - def addString(self, s):
680 """
681 Creates a reference to C{s}. If the reference already exists, that
682 reference is returned.
683
684 @type s: C{str}
685 @param s: The string to be referenced.
686 @rtype: C{int}
687 @return: The reference index.
688
689 @raise TypeError: The parameter C{s} is not of C{basestring} type.
690 @raise pyamf.ReferenceError: Trying to store a reference to an empty string.
691 """
692 if not isinstance(s, basestring):
693 raise TypeError
694
695 if len(s) == 0:
696 if not self.exceptions:
697 return None
698
699
700 raise pyamf.ReferenceError("Cannot store a reference to an empty string")
701
702 return self.strings.append(s)
703
704 - def getClassByReference(self, ref):
705 """
706 Return class reference.
707
708 @raise pyamf.ReferenceError: The class reference could not be found.
709 @return: Class reference.
710 """
711 try:
712 return self.class_ref[ref]
713 except KeyError:
714 if not self.exceptions:
715 return None
716
717 raise pyamf.ReferenceError("Class reference %r not found" % (
718 ref,))
719
720 - def getClass(self, klass):
721 """
722 Return class reference.
723
724 @raise pyamf.ReferenceError: The class reference could not be found.
725 @return: Class reference.
726 """
727 try:
728 return self.classes[klass]
729 except KeyError:
730 if not self.exceptions:
731 return None
732
733 raise pyamf.ReferenceError("Class alias for %r not found" % (
734 klass,))
735
736 - def addClass(self, alias, klass):
737 """
738 Creates a reference to C{class_def}.
739
740 @param alias: C{ClassDefinition} instance.
741 """
742 ref = self.class_idx
743
744 self.class_ref[ref] = alias
745 cd = self.classes[klass] = alias
746
747 cd.reference = ref
748
749 self.class_idx += 1
750
751 return ref
752
753 - def getLegacyXML(self, ref):
754 """
755 Return the legacy XML reference. This is the C{flash.xml.XMLDocument}
756 class in ActionScript 3.0 and the top-level C{XML} class in
757 ActionScript 1.0 and 2.0.
758
759 @type ref: C{int}
760 @param ref: The reference index.
761 @raise pyamf.ReferenceError: The legacy XML reference could not be found.
762 @return: Instance of L{ET<util.ET>}
763 """
764 i = self.legacy_xml.getByReference(ref)
765
766 if i is None:
767 if not self.exceptions:
768 return None
769
770 raise pyamf.ReferenceError("Legacy XML reference %r not found" % (ref,))
771
772 return i
773
775 """
776 Return legacy XML reference.
777
778 @type doc: L{ET<util.ET>}
779 @param doc: The XML document to reference.
780 @raise pyamf.ReferenceError: The reference could not be found.
781 @return: The reference to C{doc}.
782 @rtype: C{int}
783 """
784 i = self.legacy_xml.getReferenceTo(doc)
785
786 if i is None:
787 if not self.exceptions:
788 return None
789
790 raise pyamf.ReferenceError("Reference for document %r not found" % (doc,))
791
792 return i
793
794 - def addLegacyXML(self, doc):
795 """
796 Creates a reference to C{doc}.
797
798 If C{doc} is already referenced that index will be returned. Otherwise
799 a new index will be created.
800
801 @type doc: L{ET<util.ET>}
802 @param doc: The XML document to reference.
803 @rtype: C{int}
804 @return: The reference to C{doc}.
805 """
806 return self.legacy_xml.append(doc)
807
808 - def __copy__(self):
809 return self.__class__(exceptions=self.exceptions)
810
811
813 """
814 Decodes an AMF3 data stream.
815 """
816 context_class = Context
817
818 type_map = {
819 TYPE_UNDEFINED: 'readUndefined',
820 TYPE_NULL: 'readNull',
821 TYPE_BOOL_FALSE: 'readBoolFalse',
822 TYPE_BOOL_TRUE: 'readBoolTrue',
823 TYPE_INTEGER: 'readSignedInteger',
824 TYPE_NUMBER: 'readNumber',
825 TYPE_STRING: 'readString',
826 TYPE_XML: 'readXML',
827 TYPE_DATE: 'readDate',
828 TYPE_ARRAY: 'readArray',
829 TYPE_OBJECT: 'readObject',
830 TYPE_XMLSTRING: 'readXMLString',
831 TYPE_BYTEARRAY: 'readByteArray',
832 }
833
838
844
846 """
847 Read null.
848
849 @return: C{None}
850 @rtype: C{None}
851 """
852 return None
853
855 """
856 Returns C{False}.
857
858 @return: C{False}
859 @rtype: C{bool}
860 """
861 return False
862
864 """
865 Returns C{True}.
866
867 @return: C{True}
868 @rtype: C{bool}
869 """
870 return True
871
873 """
874 Read number.
875 """
876 return self.stream.read_double()
877
879 """
880 Reads and returns an unsigned integer from the stream.
881 """
882 return self.readInteger(False)
883
885 """
886 Reads and returns a signed integer from the stream.
887 """
888 return self.readInteger(True)
889
891 """
892 Reads and returns an integer from the stream.
893
894 @type signed: C{bool}
895 @see: U{Parsing integers on OSFlash
896 <http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data
897 format.
898 """
899 return decode_int(self.stream, signed)
900
902 """
903 Reads and returns a string from the stream.
904
905 @type use_references: C{bool}
906 """
907 def readLength():
908 x = self.readUnsignedInteger()
909
910 return (x >> 1, x & REFERENCE_BIT == 0)
911
912 length, is_reference = readLength()
913
914 if use_references and is_reference:
915 return self.context.getString(length)
916
917 if length == 0:
918 return u''
919
920 result = self.stream.read_utf8_string(length)
921
922 if len(result) != 0 and use_references:
923 self.context.addString(result)
924
925 return result
926
947
990
1032
1034 """
1035 Reads an object from the stream.
1036
1037 @raise pyamf.EncodeError: Decoding an object in amf3 tagged as amf0
1038 only is not allowed.
1039 @raise pyamf.DecodeError: Unknown object encoding.
1040 """
1041 if use_proxies is None:
1042 use_proxies = self.use_proxies
1043
1044 def readStatic(class_def, obj):
1045 for attr in class_def.static_properties:
1046 obj[attr] = self.readElement()
1047
1048 def readDynamic(class_def, obj):
1049 attr = self.readString().encode('utf8')
1050
1051 while attr != '':
1052 obj[attr] = self.readElement()
1053 attr = self.readString().encode('utf8')
1054
1055 ref = self.readUnsignedInteger()
1056
1057 if ref & REFERENCE_BIT == 0:
1058 obj = self.context.getObject(ref >> 1)
1059
1060 if use_proxies is True:
1061 obj = self.readProxyObject(obj)
1062
1063 return obj
1064
1065 ref >>= 1
1066
1067 class_def, alias = self._getClassDefinition(ref)
1068
1069 obj = alias.createInstance(codec=self)
1070 obj_attrs = dict()
1071
1072 self.context.addObject(obj)
1073
1074 if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
1075 obj.__readamf__(DataInput(self))
1076 elif class_def.encoding == ObjectEncoding.DYNAMIC:
1077 readStatic(class_def, obj_attrs)
1078 readDynamic(class_def, obj_attrs)
1079 elif class_def.encoding == ObjectEncoding.STATIC:
1080 readStatic(class_def, obj_attrs)
1081 else:
1082 raise pyamf.DecodeError("Unknown object encoding")
1083
1084 alias.applyAttributes(obj, obj_attrs, codec=self)
1085
1086 if use_proxies is True:
1087 obj = self.readProxyObject(obj)
1088
1089 return obj
1090
1092 """
1093 Return the source object of a proxied object.
1094
1095 @since: 0.4
1096 """
1097 if isinstance(proxy, ArrayCollection):
1098 return list(proxy)
1099 elif isinstance(proxy, ObjectProxy):
1100 return proxy._amf_object
1101
1102 return proxy
1103
1105 """
1106 Reads an object from the stream.
1107
1108 @type legacy: C{bool}
1109 @param legacy: The read XML is in 'legacy' format.
1110 """
1111 ref = self.readUnsignedInteger()
1112
1113 if ref & REFERENCE_BIT == 0:
1114 return self.context.getObject(ref >> 1)
1115
1116 xmlstring = self.stream.read(ref >> 1)
1117
1118 x = util.ET.fromstring(xmlstring)
1119 self.context.addObject(x)
1120
1121 if legacy is True:
1122 self.context.addLegacyXML(x)
1123
1124 return x
1125
1127 """
1128 Reads a string from the data stream and converts it into
1129 an XML Tree.
1130
1131 @return: The XML Document.
1132 @rtype: L{ET<util.ET>}
1133 """
1134 return self._readXML()
1135
1137 """
1138 Read a legacy XML Document from the stream.
1139
1140 @return: The XML Document.
1141 @rtype: L{ET<util.ET>}
1142 """
1143 return self._readXML(True)
1144
1146 """
1147 Reads a string of data from the stream.
1148
1149 Detects if the L{ByteArray} was compressed using C{zlib}.
1150
1151 @see: L{ByteArray}
1152 @note: This is not supported in ActionScript 1.0 and 2.0.
1153 """
1154 ref = self.readUnsignedInteger()
1155
1156 if ref & REFERENCE_BIT == 0:
1157 return self.context.getObject(ref >> 1)
1158
1159 buffer = self.stream.read(ref >> 1)
1160
1161 try:
1162 buffer = zlib.decompress(buffer)
1163 compressed = True
1164 except zlib.error:
1165 compressed = False
1166
1167 obj = ByteArray(buffer, context=self.context)
1168 obj.compressed = compressed
1169
1170 self.context.addObject(obj)
1171
1172 return obj
1173
1174
1176 """
1177 Encodes an AMF3 data stream.
1178 """
1179 context_class = Context
1180
1181 type_map = [
1182 ((types.BuiltinFunctionType, types.BuiltinMethodType,
1183 types.FunctionType, types.GeneratorType, types.ModuleType,
1184 types.LambdaType, types.MethodType), "writeFunc"),
1185 ((bool,), "writeBoolean"),
1186 ((types.NoneType,), "writeNull"),
1187 ((int,long), "writeInteger"),
1188 ((float,), "writeNumber"),
1189 (types.StringTypes, "writeString"),
1190 ((ByteArray,), "writeByteArray"),
1191 ((datetime.date, datetime.datetime, datetime.time), "writeDate"),
1192 ((util.is_ET_element,), "writeXML"),
1193 ((pyamf.UndefinedType,), "writeUndefined"),
1194 ((types.ClassType, types.TypeType), "writeClass"),
1195 ((types.InstanceType, types.ObjectType,), "writeInstance"),
1196 ]
1197
1203
1204 - def writeElement(self, data, use_references=True, use_proxies=None):
1205 """
1206 Writes the data.
1207
1208 @param data: The data to be encoded to the AMF3 data stream.
1209 @type data: C{mixed}
1210 @param use_references: Default is C{True}.
1211 @type use_references: C{bool}
1212 @raise EncodeError: Cannot find encoder func for C{data}.
1213 """
1214 func = self._writeElementFunc(data)
1215
1216 if func is None:
1217 raise pyamf.EncodeError("Unknown type %r" % (data,))
1218
1219 func(data, use_references=use_references, use_proxies=use_proxies)
1220
1222 """
1223 Classes cannot be serialised.
1224 """
1225 raise pyamf.EncodeError("Class objects cannot be serialised")
1226
1228 """
1229 Writes an C{pyamf.Undefined} value to the stream.
1230 """
1231 self.stream.write(TYPE_UNDEFINED)
1232
1234 """
1235 Writes a C{null} value to the stream.
1236 """
1237 self.stream.write(TYPE_NULL)
1238
1249
1251 """
1252 AMF3 integers are encoded.
1253
1254 @param n: The integer data to be encoded to the AMF3 data stream.
1255 @type n: integer data
1256
1257 @see: U{Parsing Integers on OSFlash
1258 <http://osflash.org/documentation/amf3/parsing_integers>}
1259 for more info.
1260 """
1261 try:
1262 self.stream.write(ENCODED_INT_CACHE[n])
1263 except KeyError:
1264 ENCODED_INT_CACHE[n] = encode_int(n)
1265 self.stream.write(ENCODED_INT_CACHE[n])
1266
1268 """
1269 Writes an integer to the stream.
1270
1271 @type n: integer data
1272 @param n: The integer data to be encoded to the AMF3 data stream.
1273 @type use_references: C{bool}
1274 @kwarg use_references: Default is C{True}.
1275 """
1276 if n < MIN_29B_INT or n > MAX_29B_INT:
1277 self.writeNumber(float(n))
1278
1279 return
1280
1281 self.stream.write(TYPE_INTEGER)
1282 self.stream.write(encode_int(n))
1283
1285 """
1286 Writes a float to the stream.
1287
1288 @type n: C{float}
1289 """
1290 self.stream.write(TYPE_NUMBER)
1291 self.stream.write_double(n)
1292
1294 """
1295 Writes a raw string to the stream.
1296
1297 @type n: C{str} or C{unicode}
1298 @param n: The string data to be encoded to the AMF3 data stream.
1299 """
1300 if n == '':
1301 self.stream.write_uchar(REFERENCE_BIT)
1302
1303 return
1304
1305 t = type(n)
1306
1307 if t is str:
1308 bytes = n
1309 elif t is unicode:
1310 bytes = n.encode('utf8')
1311 else:
1312 bytes = unicode(n).encode('utf8')
1313 n = bytes
1314
1315 if self.string_references:
1316 ref = self.context.getStringReference(n)
1317
1318 if ref is not None:
1319 self._writeInteger(ref << 1)
1320
1321 return
1322
1323 self.context.addString(n)
1324
1325 self._writeInteger((len(bytes) << 1) | REFERENCE_BIT)
1326 self.stream.write(bytes)
1327
1329 """
1330 Writes a string to the stream. If C{n} is not a unicode string, an
1331 attempt will be made to convert it.
1332
1333 @type n: C{basestring}
1334 @param n: The string data to be encoded to the AMF3 data stream.
1335 """
1336 if writeType:
1337 self.stream.write(TYPE_STRING)
1338
1339 self._writeString(n, **kwargs)
1340
1341 - def writeDate(self, n, use_references=True, **kwargs):
1342 """
1343 Writes a C{datetime} instance to the stream.
1344
1345 @type n: L{datetime}
1346 @param n: The C{Date} data to be encoded to the AMF3 data stream.
1347 @type use_references: C{bool}
1348 @param use_references: Default is C{True}.
1349 """
1350 if isinstance(n, datetime.time):
1351 raise pyamf.EncodeError('A datetime.time instance was found but '
1352 'AMF3 has no way to encode time objects. Please use '
1353 'datetime.datetime instead (got:%r)' % (n,))
1354
1355 self.stream.write(TYPE_DATE)
1356
1357 if use_references is True:
1358 ref = self.context.getObjectReference(n)
1359
1360 if ref is not None:
1361 self._writeInteger(ref << 1)
1362
1363 return
1364
1365 self.context.addObject(n)
1366
1367 self.stream.write_uchar(REFERENCE_BIT)
1368
1369 if self.timezone_offset is not None:
1370 n -= self.timezone_offset
1371
1372 ms = util.get_timestamp(n)
1373 self.stream.write_double(ms * 1000.0)
1374
1375 - def writeList(self, n, use_references=True, use_proxies=None):
1376 """
1377 Writes a C{tuple}, C{set} or C{list} to the stream.
1378
1379 @type n: One of C{__builtin__.tuple}, C{__builtin__.set}
1380 or C{__builtin__.list}
1381 @param n: The C{list} data to be encoded to the AMF3 data stream.
1382 @type use_references: C{bool}
1383 @param use_references: Default is C{True}.
1384 """
1385
1386 if use_proxies is None:
1387 use_proxies = self.use_proxies
1388
1389 if use_proxies:
1390 ref_obj = self.context.getObjectAlias(n)
1391
1392 if ref_obj is None:
1393 proxy = ArrayCollection(n)
1394 self.context.setObjectAlias(n, proxy)
1395 ref_obj = proxy
1396
1397 self.writeObject(ref_obj, use_references, use_proxies=False)
1398
1399 return
1400
1401 self.stream.write(TYPE_ARRAY)
1402
1403 if use_references:
1404 ref = self.context.getObjectReference(n)
1405
1406 if ref is not None:
1407 self._writeInteger(ref << 1)
1408
1409 return
1410
1411 self.context.addObject(n)
1412
1413 self._writeInteger((len(n) << 1) | REFERENCE_BIT)
1414 self.stream.write_uchar(0x01)
1415
1416 [self.writeElement(x) for x in n]
1417
1418 - def writeDict(self, n, use_references=True, use_proxies=None):
1419 """
1420 Writes a C{dict} to the stream.
1421
1422 @type n: C{__builtin__.dict}
1423 @param n: The C{dict} data to be encoded to the AMF3 data stream.
1424 @type use_references: C{bool}
1425 @param use_references: Default is C{True}.
1426 @raise ValueError: Non C{int}/C{str} key value found in the C{dict}
1427 @raise EncodeError: C{dict} contains empty string keys.
1428 """
1429
1430
1431
1432
1433 if '' in n:
1434 raise pyamf.EncodeError("dicts cannot contain empty string keys")
1435
1436 if use_proxies is None:
1437 use_proxies = self.use_proxies
1438
1439 if use_proxies is True:
1440 ref_obj = self.context.getObjectAlias(n)
1441
1442 if ref_obj is None:
1443 proxy = ObjectProxy(pyamf.ASObject(n))
1444 self.context.setObjectAlias(n, proxy)
1445 ref_obj = proxy
1446
1447 self.writeObject(ref_obj, use_references, use_proxies=False)
1448
1449 return
1450
1451 self.stream.write(TYPE_ARRAY)
1452
1453 if use_references:
1454 ref = self.context.getObjectReference(n)
1455
1456 if ref is not None:
1457 self._writeInteger(ref << 1)
1458
1459 return
1460
1461 self.context.addObject(n)
1462
1463
1464 keys = n.keys()
1465 int_keys = []
1466 str_keys = []
1467
1468 for x in keys:
1469 if isinstance(x, (int, long)):
1470 int_keys.append(x)
1471 elif isinstance(x, (str, unicode)):
1472 str_keys.append(x)
1473 else:
1474 raise ValueError("Non int/str key value found in dict")
1475
1476
1477 l = len(int_keys)
1478
1479 for x in int_keys:
1480 if l < x <= 0:
1481
1482 str_keys.append(x)
1483 del int_keys[int_keys.index(x)]
1484
1485 int_keys.sort()
1486
1487
1488 if len(int_keys) > 0 and int_keys[0] != 0:
1489 for x in int_keys:
1490 str_keys.append(str(x))
1491 del int_keys[int_keys.index(x)]
1492
1493 self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT)
1494
1495 for x in str_keys:
1496 self._writeString(x)
1497 self.writeElement(n[x])
1498
1499 self.stream.write_uchar(0x01)
1500
1501 for k in int_keys:
1502 self.writeElement(n[k])
1503
1505 """
1506 Read class definition.
1507
1508 @param obj: The class instance to be encoded.
1509 """
1510 kls = obj.__class__
1511
1512 if kls is pyamf.MixedArray:
1513 f = self._write_elem_func_cache[kls] = self.writeDict
1514 elif kls in (list, set, tuple):
1515 f = self._write_elem_func_cache[kls] = self.writeList
1516 else:
1517 f = self._write_elem_func_cache[kls] = self.writeObject
1518
1519 f(obj, **kwargs)
1520
1521 - def writeObject(self, obj, use_references=True, use_proxies=None):
1632
1634 """
1635 Writes a L{ByteArray} to the data stream.
1636
1637 @param n: The L{ByteArray} data to be encoded to the AMF3 data stream.
1638 @type n: L{ByteArray}
1639 @param use_references: Default is C{True}.
1640 @type use_references: C{bool}
1641 """
1642 self.stream.write(TYPE_BYTEARRAY)
1643
1644 if use_references:
1645 ref = self.context.getObjectReference(n)
1646
1647 if ref is not None:
1648 self._writeInteger(ref << 1)
1649
1650 return
1651
1652 self.context.addObject(n)
1653
1654 buf = str(n)
1655 l = len(buf)
1656 self._writeInteger(l << 1 | REFERENCE_BIT)
1657 self.stream.write(buf)
1658
1659 - def writeXML(self, n, use_references=True, use_proxies=None):
1660 """
1661 Writes a XML string to the data stream.
1662
1663 @type n: L{ET<util.ET>}
1664 @param n: The XML Document to be encoded to the AMF3 data stream.
1665 @type use_references: C{bool}
1666 @param use_references: Default is C{True}.
1667 """
1668 i = self.context.getLegacyXMLReference(n)
1669
1670 if i is None:
1671 is_legacy = True
1672 else:
1673 is_legacy = False
1674
1675 if is_legacy is True:
1676 self.stream.write(TYPE_XMLSTRING)
1677 else:
1678 self.stream.write(TYPE_XML)
1679
1680 if use_references:
1681 ref = self.context.getObjectReference(n)
1682
1683 if ref is not None:
1684 self._writeInteger(ref << 1)
1685
1686 return
1687
1688 self.context.addObject(n)
1689
1690 self._writeString(util.ET.tostring(n, 'utf-8'))
1691
1692
1693 -def decode(stream, context=None, strict=False):
1694 """
1695 A helper function to decode an AMF3 datastream.
1696
1697 @type stream: L{BufferedByteStream<util.BufferedByteStream>}
1698 @param stream: AMF3 data.
1699 @type context: L{Context}
1700 @param context: Context.
1701 """
1702 decoder = Decoder(stream, context, strict)
1703
1704 while 1:
1705 try:
1706 yield decoder.readElement()
1707 except pyamf.EOStream:
1708 break
1709
1710
1712 """
1713 A helper function to encode an element into AMF3 format.
1714
1715 @type args: List of args to encode.
1716 @keyword context: Any initial context to use.
1717 @type context: L{Context}
1718 @return: C{StringIO} type object containing the encoded AMF3 data.
1719 @rtype: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
1720 """
1721 context = kwargs.get('context', None)
1722 buf = util.BufferedByteStream()
1723 encoder = Encoder(buf, context)
1724
1725 for element in args:
1726 encoder.writeElement(element)
1727
1728 return buf
1729
1730
1732 """
1733 Encodes an int as a variable length signed 29-bit integer as defined by
1734 the spec.
1735
1736 @param n: The integer to be encoded
1737 @return: The encoded string
1738 @rtype: C{str}
1739 @raise OverflowError: Out of range.
1740 """
1741 if n < MIN_29B_INT or n > MAX_29B_INT:
1742 raise OverflowError("Out of range")
1743
1744 if n < 0:
1745 n += 0x20000000
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