1
2
3
4
5
6 """
7 AMF0 implementation.
8
9 C{AMF0} supports the basic data types used for the NetConnection, NetStream,
10 LocalConnection, SharedObjects and other classes in the Flash Player.
11
12 @see: U{Official AMF0 Specification in English (external)
13 <http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf>}
14 @see: U{Official AMF0 Specification in Japanese (external)
15 <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf0_spec_121207.pdf>}
16 @see: U{AMF documentation on OSFlash (external)
17 <http://osflash.org/documentation/amf>}
18
19 @since: 0.1.0
20 """
21
22 import datetime, types
23 import copy
24
25 import pyamf
26 from pyamf import util
27
101
102
103 ACTIONSCRIPT_TYPES = []
104
105 for x in ASTypes.__dict__:
106 if not x.startswith('_'):
107 ACTIONSCRIPT_TYPES.append(ASTypes.__dict__[x])
108 del x
109
110 -class Context(pyamf.BaseContext):
111 """
112 I hold the AMF0 context for en/decoding streams.
113
114 AMF0 object references start at index 1.
115
116 @ivar amf3_objs: A list of objects that have been decoded in
117 L{AMF3<pyamf.amf3>}.
118 @type amf3_objs: L{util.IndexedCollection}
119 """
120 - def __init__(self):
124
126 """
127 Clears the context.
128 """
129 pyamf.BaseContext.clear(self)
130
131 self.amf3_objs.clear()
132
133 if hasattr(self, 'amf3_context'):
134 self.amf3_context.clear()
135
137 """
138 Resets the context.
139
140 @see: L{pyamf.BaseContext.reset}
141 """
142 pyamf.BaseContext.reset(self)
143
144 if hasattr(self, 'amf3_context'):
145 self.amf3_context.reset()
146
148 """
149 Gets a reference for an object.
150
151 @raise ReferenceError: Object reference could not be found.
152 """
153 try:
154 return self.amf3_objs.getReferenceTo(obj)
155 except KeyError:
156 raise ReferenceError
157
158 - def addAMF3Object(self, obj):
159 """
160 Adds an AMF3 reference to C{obj}.
161
162 @type obj: C{mixed}
163 @param obj: The object to add to the context.
164 @rtype: C{int}
165 @return: Reference to C{obj}.
166 """
167 return self.amf3_objs.append(obj)
168
169 - def __copy__(self):
170 cpy = self.__class__()
171 cpy.amf3_objs = copy.copy(self.amf3_objs)
172
173 return cpy
174
176 """
177 Decodes an AMF0 stream.
178 """
179
180 context_class = Context
181
182
183 type_map = {
184 ASTypes.NUMBER: 'readNumber',
185 ASTypes.BOOL: 'readBoolean',
186 ASTypes.STRING: 'readString',
187 ASTypes.OBJECT: 'readObject',
188 ASTypes.NULL: 'readNull',
189 ASTypes.UNDEFINED: 'readUndefined',
190 ASTypes.REFERENCE: 'readReference',
191 ASTypes.MIXEDARRAY: 'readMixedArray',
192 ASTypes.ARRAY: 'readList',
193 ASTypes.DATE: 'readDate',
194 ASTypes.LONGSTRING: 'readLongString',
195
196 ASTypes.UNSUPPORTED:'readNull',
197 ASTypes.XML: 'readXML',
198 ASTypes.TYPEDOBJECT:'readTypedObject',
199 ASTypes.AMF3: 'readAMF3'
200 }
201
203 """
204 Read and returns the next byte in the stream and determine its type.
205
206 @raise DecodeError: AMF0 type not recognized.
207 @return: AMF0 type.
208 """
209 type = self.stream.read_uchar()
210
211 if type not in ACTIONSCRIPT_TYPES:
212 raise pyamf.DecodeError("Unknown AMF0 type 0x%02x at %d" % (
213 type, self.stream.tell() - 1))
214
215 return type
216
218 """
219 Reads a ActionScript C{Number} value.
220
221 In ActionScript 1 and 2 the C{NumberASTypes} type represents all numbers,
222 both floats and integers.
223
224 @rtype: C{int} or C{float}
225 """
226 return _check_for_int(self.stream.read_double())
227
229 """
230 Reads a ActionScript C{Boolean} value.
231
232 @rtype: C{bool}
233 @return: Boolean.
234 """
235 return bool(self.stream.read_uchar())
236
238 """
239 Reads a ActionScript C{null} value.
240
241 @return: C{None}
242 @rtype: C{None}
243 """
244 return None
245
247 """
248 Reads an ActionScript C{undefined} value.
249
250 @return: L{Undefined<pyamf.Undefined>}
251 """
252 return pyamf.Undefined
253
255 """
256 Read mixed array.
257
258 @rtype: C{dict}
259 @return: C{dict} read from the stream
260 """
261 len = self.stream.read_ulong()
262 obj = pyamf.MixedArray()
263 self.context.addObject(obj)
264 self._readObject(obj)
265 ikeys = []
266
267 for key in obj.keys():
268 try:
269 ikey = int(key)
270 ikeys.append((key, ikey))
271 obj[ikey] = obj[key]
272 del obj[key]
273 except ValueError:
274
275 pass
276
277 ikeys.sort()
278
279 return obj
280
282 """
283 Read a C{list} from the data stream.
284
285 @rtype: C{list}
286 @return: C{list}
287 """
288 obj = []
289 self.context.addObject(obj)
290 len = self.stream.read_ulong()
291
292 for i in xrange(len):
293 obj.append(self.readElement())
294
295 return obj
296
321
323 """
324 Read AMF3 elements from the data stream.
325
326 @rtype: C{mixed}
327 @return: The AMF3 element read from the stream
328 """
329 if not hasattr(self.context, 'amf3_context'):
330 from pyamf import amf3
331
332 self.context.amf3_context = amf3.Context()
333
334 decoder = pyamf._get_decoder_class(pyamf.AMF3)(self.stream, self.context.amf3_context, strict=self.strict)
335
336 element = decoder.readElement()
337 self.context.addAMF3Object(element)
338
339 return element
340
342 """
343 Reads a string from the data stream.
344
345 @rtype: C{str}
346 @return: string
347 """
348 len = self.stream.read_ushort()
349 return self.stream.read_utf8_string(len)
350
368
370 """
371 Reads an object from the data stream.
372
373 @rtype: L{ASObject<pyamf.ASObject>}
374 """
375 obj = pyamf.ASObject()
376 self.context.addObject(obj)
377
378 self._readObject(obj)
379
380 return obj
381
383 """
384 Reads a reference from the data stream.
385 """
386 idx = self.stream.read_ushort()
387
388 return self.context.getObject(idx)
389
391 """
392 Reads a UTC date from the data stream. Client and servers are
393 responsible for applying their own timezones.
394
395 Date: C{0x0B T7 T6} .. C{T0 Z1 Z2 T7} to C{T0} form a 64 bit
396 Big Endian number that specifies the number of nanoseconds
397 that have passed since 1/1/1970 0:00 to the specified time.
398 This format is UTC 1970. C{Z1} and C{Z0} for a 16 bit Big
399 Endian number indicating the indicated time's timezone in
400 minutes.
401 """
402 ms = self.stream.read_double() / 1000.0
403 tz = self.stream.read_short()
404
405
406 d = util.get_datetime(ms)
407 self.context.addObject(d)
408
409 return d
410
418
428
430 """
431 Encodes an AMF0 stream.
432
433 @ivar use_amf3: A flag to determine whether this encoder knows about AMF3.
434 @type use_amf3: C{bool}
435 """
436
437 context_class = Context
438
439 type_map = [
440 ((types.BuiltinFunctionType, types.BuiltinMethodType,
441 types.FunctionType, types.GeneratorType, types.ModuleType,
442 types.LambdaType, types.MethodType), "writeFunc"),
443 ((types.NoneType,), "writeNull"),
444 ((bool,), "writeBoolean"),
445 ((int,long,float), "writeNumber"),
446 ((types.StringTypes,), "writeString"),
447 ((pyamf.ASObject,), "writeObject"),
448 ((pyamf.MixedArray,), "writeMixedArray"),
449 ((types.ListType, types.TupleType,), "writeArray"),
450 ((datetime.date, datetime.datetime), "writeDate"),
451 ((util.is_ET_element,), "writeXML"),
452 ((lambda x: x is pyamf.Undefined,), "writeUndefined"),
453 ((types.InstanceType,types.ObjectType,), "writeObject"),
454 ]
455
460
462 """
463 Writes the type to the stream.
464
465 @type type: C{int}
466 @param type: ActionScript type.
467
468 @raise pyamf.EncodeError: AMF0 type is not recognized.
469 """
470 if type not in ACTIONSCRIPT_TYPES:
471 raise pyamf.EncodeError("Unknown AMF0 type 0x%02x at %d" % (
472 type, self.stream.tell() - 1))
473
474 self.stream.write_uchar(type)
475
477 """
478 Writes the L{undefined<ASTypes.UNDEFINED>} data type to the stream.
479
480 @param data: The C{undefined} data to be encoded to the AMF0 data
481 stream.
482 @type data: C{undefined} data
483 """
484 self.writeType(ASTypes.UNDEFINED)
485
487 """
488 Functions cannot be serialised.
489 """
490 raise pyamf.EncodeError("Callables cannot be serialised")
491
493 """
494 Writes L{unsupported<ASTypes.UNSUPPORTED>} data type to the
495 stream.
496
497 @param data: The C{unsupported} data to be encoded to the AMF0
498 data stream.
499 @type data: C{unsupported} data
500 """
501 self.writeType(ASTypes.UNSUPPORTED)
502
519
521 """
522 Writes the data.
523
524 @type data: C{mixed}
525 @param data: The data to be encoded to the AMF0 data stream.
526
527 @raise EncodeError: Cannot find encoder func.
528 @raise EncodeError: Unable to encode the data.
529 """
530 func = self._writeElementFunc(data)
531
532 if func is None:
533 raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,))
534 else:
535 try:
536 func(data)
537 except (KeyboardInterrupt, SystemExit):
538 raise
539 except pyamf.EncodeError:
540 raise
541 except:
542
543 raise
544
546 """
547 Write null type to data stream.
548
549 @type n: C{None}
550 @param n: Is ignored.
551 """
552 self.writeType(ASTypes.NULL)
553
581
583 """
584 Write number to the data stream.
585
586 @type n: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
587 @param n: The number data to be encoded to the AMF0 data stream.
588 """
589 self.writeType(ASTypes.NUMBER)
590 self.stream.write_double(float(n))
591
593 """
594 Write boolean to the data stream.
595
596 @type b: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
597 @param b: The boolean data to be encoded to the AMF0 data stream.
598 """
599 self.writeType(ASTypes.BOOL)
600
601 if b:
602 self.stream.write_uchar(1)
603 else:
604 self.stream.write_uchar(0)
605
607 if not isinstance(s, basestring):
608 s = unicode(s).encode('utf8')
609
610 if len(s) > 0xffff:
611 self.stream.write_ulong(len(s))
612 else:
613 self.stream.write_ushort(len(s))
614
615 self.stream.write(s)
616
618 """
619 Write string to the data stream.
620
621 @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
622 @param s: The string data to be encoded to the AMF0 data stream.
623 @type writeType: C{bool}
624 @param writeType: Write data type.
625 """
626 if isinstance(s, unicode):
627 s = s.encode('utf8')
628 elif not isinstance(s, basestring):
629 s = unicode(s).encode('utf8')
630
631 if len(s) > 0xffff:
632 if writeType:
633 self.writeType(ASTypes.LONGSTRING)
634 else:
635 if writeType:
636 self.stream.write_uchar(ASTypes.STRING)
637
638 self._writeString(s)
639
641 """
642 Write reference to the data stream.
643
644 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
645 @param o: The reference data to be encoded to the AMF0 data
646 stream.
647 """
648 idx = self.context.getObjectReference(o)
649
650 self.writeType(ASTypes.REFERENCE)
651 self.stream.write_ushort(idx)
652
654 """
655 Write C{dict} to the data stream.
656
657 @type o: C{iterable}
658 @param o: The C{dict} data to be encoded to the AMF0 data
659 stream.
660 """
661 for key, val in o.iteritems():
662 self.writeString(key, False)
663 self.writeElement(val)
664
666 """
667 Write mixed array to the data stream.
668
669 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
670 @param o: The mixed array data to be encoded to the AMF0
671 data stream.
672 """
673 try:
674 self.writeReference(o)
675 return
676 except pyamf.ReferenceError:
677 self.context.addObject(o)
678
679 self.writeType(ASTypes.MIXEDARRAY)
680
681
682
683 try:
684
685 max_index = max([y[0] for y in o.items()
686 if isinstance(y[0], (int, long))])
687
688 if max_index < 0:
689 max_index = 0
690 except ValueError:
691 max_index = 0
692
693 self.stream.write_ulong(max_index)
694
695 self._writeDict(o)
696 self._writeEndObject()
697
703
705 """
706 @raise pyamf.EncodeError: Unable to determine object attributes.
707 """
708 obj_attrs = None
709
710 if alias is not None:
711 obj_attrs = {}
712
713 for attrs in alias.getAttributes(o):
714 obj_attrs.update(attrs)
715
716 if obj_attrs is None:
717 obj_attrs = util.get_attrs(o)
718
719 if obj_attrs is None:
720 raise pyamf.EncodeError('Unable to determine object attributes')
721
722 return obj_attrs
723
760
762 """
763 Writes a date to the data stream.
764
765 @type d: Instance of C{datetime.datetime}
766 @param d: The date to be encoded to the AMF0 data stream.
767 """
768
769
770 secs = util.get_timestamp(d)
771 tz = 0
772
773 self.writeType(ASTypes.DATE)
774 self.stream.write_double(secs * 1000.0)
775 self.stream.write_short(tz)
776
778 """
779 Write XML to the data stream.
780
781 @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
782 @param e: The XML data to be encoded to the AMF0 data stream.
783 """
784 if self.use_amf3 is True:
785 self.writeAMF3(e)
786
787 return
788
789 self.writeType(ASTypes.XML)
790
791 data = util.ET.tostring(e, 'utf-8')
792 self.stream.write_ulong(len(data))
793 self.stream.write(data)
794
812
813 -def decode(stream, context=None, strict=False):
814 """
815 A helper function to decode an AMF0 datastream.
816
817 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
818 @param stream: AMF0 datastream.
819 @type context: L{Context<pyamf.amf0.Context>}
820 @param context: AMF0 Context.
821 """
822 decoder = Decoder(stream, context, strict=strict)
823
824 while 1:
825 try:
826 yield decoder.readElement()
827 except pyamf.EOStream:
828 break
829
831 """
832 A helper function to encode an element into the AMF0 format.
833
834 @type element: C{mixed}
835 @keyword element: The element to encode
836 @type context: L{Context<pyamf.amf0.Context>}
837 @keyword context: AMF0 C{Context} to use for the encoding. This holds
838 previously referenced objects etc.
839 @rtype: C{StringIO}
840 @return: The encoded stream.
841 """
842 context = kwargs.get('context', None)
843 buf = util.BufferedByteStream()
844 encoder = Encoder(buf, context)
845
846 for element in args:
847 encoder.writeElement(element)
848
849 return buf
850
852 """
853 I represent the C{RecordSet} class used in Flash Remoting to hold
854 (amongst other things) SQL records.
855
856 @ivar columns: The columns to send.
857 @type columns: List of strings.
858 @ivar items: The C{RecordSet} data.
859 @type items: List of lists, the order of the data corresponds to the order
860 of the columns.
861 @ivar service: Service linked to the C{RecordSet}.
862 @type service:
863 @ivar id: The id of the C{RecordSet}.
864 @type id: C{str}
865
866 @see: U{RecordSet on OSFlash (external)
867 <http://osflash.org/documentation/amf/recordset>}
868 """
869
870 - def __init__(self, columns=[], items=[], service=None, id=None):
871 self.columns = columns
872 self.items = items
873 self.service = service
874 self.id = id
875
877 ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1,
878 initialData=self.items, columnNames=self.columns)
879
880 if self.service is not None:
881 ret.update({'serviceName': str(self.service['name'])})
882
883 if self.id is not None:
884 ret.update({'id':str(self.id)})
885
886 return ret
887
889 self.columns = val['columnNames']
890 self.items = val['initialData']
891
892 try:
893
894 self.service = dict(name=val['serviceName'])
895 except KeyError:
896 self.service = None
897
898 try:
899 self.id = val['id']
900 except KeyError:
901 self.id = None
902
903 serverInfo = property(_get_server_info, _set_server_info)
904
906 ret = '<%s.%s object' % (self.__module__, self.__class__.__name__)
907
908 if self.id is not None:
909 ret += ' id=%s' % self.id
910
911 if self.service is not None:
912 ret += ' service=%s' % self.service
913
914 ret += ' at 0x%x>' % id(self)
915
916 return ret
917
918 pyamf.register_class(RecordSet, 'RecordSet', attrs=['serverInfo'], metadata=['amf0'])
919
921 """
922 This is a compatibility function that takes a C{float} and converts it to an
923 C{int} if the values are equal.
924 """
925 try:
926 y = int(x)
927 except (OverflowError, ValueError):
928 pass
929 else:
930
931 if x == x and y == x:
932 return y
933
934 return x
935
936
937 try:
938 float('nan')
939 except ValueError:
940 pass
941 else:
942 if float('nan') == 0:
944 def f2(x):
945 if str(x).lower().find('nan') >= 0:
946 return x
947
948 return f2.func(x)
949 f2.func = func
950
951 return f2
952
953 _check_for_int = check_nan(_check_for_int)
954