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 @author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>}
20 @author: U{Thijs Triemstra<mailto:info@collab.nl>}
21 @author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
22
23 @since: 0.1.0
24 """
25
26 import datetime, types
27
28 import pyamf
29 from pyamf import util
30
104
105
106 ACTIONSCRIPT_TYPES = []
107
108 for x in ASTypes.__dict__:
109 if not x.startswith('_'):
110 ACTIONSCRIPT_TYPES.append(ASTypes.__dict__[x])
111 del x
112
113 -class Context(pyamf.BaseContext):
114 """
115 I hold the AMF0 context for en/decoding streams.
116
117 AMF0 object references start at index 1.
118 """
120 """
121 Resets the context.
122
123 The C{amf3_objs} var keeps a list of objects that were encoded
124 in L{AMF3<pyamf.amf3>}.
125 """
126 pyamf.BaseContext.clear(self)
127
128 self.amf3_objs = []
129 self.rev_amf3_objs = {}
130
131 if hasattr(self, 'amf3_context'):
132 self.amf3_context.clear()
133
134 - def _getObject(self, ref):
135 return self.objects[ref + 1]
136
137 - def __copy__(self):
138 copy = self.__class__()
139 copy.amf3_objs = self.amf3_objs
140 copy.rev_amf3_objs = self.rev_amf3_objs
141
142 return copy
143
145 """
146 Gets a reference for an object.
147
148 @raise ReferenceError: Object reference could not be found.
149 """
150 try:
151 return self.rev_amf3_objs[id(obj)]
152 except KeyError:
153 raise ReferenceError
154
155 - def addAMF3Object(self, obj):
156 """
157 Adds an AMF3 reference to C{obj}.
158
159 @type obj: C{mixed}
160 @param obj: The object to add to the context.
161
162 @rtype: C{int}
163 @return: Reference to C{obj}.
164 """
165 self.amf3_objs.append(obj)
166 idx = len(self.amf3_objs) - 1
167 self.rev_amf3_objs[id(obj)] = idx
168
169 return idx
170
172 """
173 Decodes an AMF0 stream.
174 """
175 context_class = Context
176
177
178 type_map = {
179 ASTypes.NUMBER: 'readNumber',
180 ASTypes.BOOL: 'readBoolean',
181 ASTypes.STRING: 'readString',
182 ASTypes.OBJECT: 'readObject',
183 ASTypes.NULL: 'readNull',
184 ASTypes.UNDEFINED: 'readUndefined',
185 ASTypes.REFERENCE: 'readReference',
186 ASTypes.MIXEDARRAY: 'readMixedArray',
187 ASTypes.ARRAY: 'readList',
188 ASTypes.DATE: 'readDate',
189 ASTypes.LONGSTRING: 'readLongString',
190
191 ASTypes.UNSUPPORTED:'readNull',
192 ASTypes.XML: 'readXML',
193 ASTypes.TYPEDOBJECT:'readTypedObject',
194 ASTypes.AMF3: 'readAMF3'
195 }
196
198 """
199 Read and returns the next byte in the stream and determine its type.
200
201 @raise DecodeError: AMF0 type not recognized.
202 @return: AMF0 type.
203 """
204 type = self.stream.read_uchar()
205
206 if type not in ACTIONSCRIPT_TYPES:
207 raise pyamf.DecodeError("Unknown AMF0 type 0x%02x at %d" % (
208 type, self.stream.tell() - 1))
209
210 return type
211
213 """
214 Reads a ActionScript C{Number} value.
215
216 In ActionScript 1 and 2 the C{NumberASTypes} type represents all numbers,
217 both floats and integers.
218
219 @rtype: C{int} or C{float}
220 """
221 return _check_for_int(self.stream.read_double())
222
224 """
225 Reads a ActionScript C{Boolean} value.
226
227 @rtype: C{bool}
228 @return: Boolean.
229 """
230 return bool(self.stream.read_uchar())
231
233 """
234 Reads a ActionScript C{null} value.
235
236 @return: C{None}
237 @rtype: C{None}
238 """
239 return None
240
242 """
243 Reads an ActionScript C{undefined} value.
244
245 @return: L{Undefined<pyamf.Undefined>}
246 """
247 return pyamf.Undefined
248
250 """
251 Read mixed array.
252
253 @rtype: C{dict}
254 @return: C{dict} read from the stream
255 """
256 len = self.stream.read_ulong()
257 obj = pyamf.MixedArray()
258 self.context.addObject(obj)
259 self._readObject(obj)
260 ikeys = []
261
262 for key in obj.keys():
263 try:
264 ikey = int(key)
265 ikeys.append((key, ikey))
266 obj[ikey] = obj[key]
267 del obj[key]
268 except ValueError:
269
270 pass
271
272 ikeys.sort()
273
274 return obj
275
277 """
278 Read a C{list} from the data stream.
279
280 @rtype: C{list}
281 @return: C{list}
282 """
283 obj = []
284 self.context.addObject(obj)
285 len = self.stream.read_ulong()
286
287 for i in xrange(len):
288 obj.append(self.readElement())
289
290 return obj
291
293 """
294 Reads an ActionScript object from the stream and attempts to
295 'cast' it.
296
297 @see: L{load_class<pyamf.load_class>}
298 """
299 classname = self.readString()
300 alias = pyamf.load_class(classname)
301
302 ret = alias()
303 self.context.addObject(ret)
304 self._readObject(ret, alias)
305
306 return ret
307
309 """
310 Read AMF3 elements from the data stream.
311
312 @rtype: C{mixed}
313 @return: The AMF3 element read from the stream
314 """
315 if not hasattr(self.context, 'amf3_context'):
316 from pyamf import amf3
317
318 self.context.amf3_context = amf3.Context()
319
320 decoder = pyamf._get_decoder_class(pyamf.AMF3)(self.stream, self.context.amf3_context)
321
322 element = decoder.readElement()
323 self.context.addAMF3Object(element)
324
325 return element
326
328 """
329 Reads a string from the data stream.
330
331 @rtype: C{str}
332 @return: string
333 """
334 len = self.stream.read_ushort()
335 return self.stream.read_utf8_string(len)
336
338 attrs = []
339
340 if alias is not None:
341 attrs = alias.getAttrs(obj)
342
343 key = self.readString()
344
345 ot = chr(ASTypes.OBJECTTERM)
346 obj_attrs = dict()
347
348 while self.stream.peek() != ot:
349 obj_attrs[key] = self.readElement()
350 key = self.readString()
351
352
353 self.stream.read(len(ot))
354
355 if attrs is None:
356 attrs = obj_attrs.keys()
357
358 if alias:
359 if hasattr(obj, '__setstate__'):
360 obj.__setstate__(obj_attrs)
361
362 return
363
364 for key in filter(lambda x: x in attrs, obj_attrs.keys()):
365 setattr(obj, key, obj_attrs[key])
366 else:
367 f = obj.__setattr__
368
369 if isinstance(obj, (list, dict)):
370 f = obj.__setitem__
371
372 for key, value in obj_attrs.iteritems():
373 f(key, value)
374
375 return
376
378 """
379 Reads an object from the data stream.
380
381 @rtype: L{ASObject<pyamf.ASObject>}
382 """
383 obj = pyamf.ASObject()
384 self.context.addObject(obj)
385
386 self._readObject(obj)
387
388 return obj
389
391 """
392 Reads a reference from the data stream.
393 """
394 idx = self.stream.read_ushort()
395
396 return self.context.getObject(idx)
397
399 """
400 Reads a UTC date from the data stream. Client and servers are
401 responsible for applying their own timezones.
402
403 Date: C{0x0B T7 T6} .. C{T0 Z1 Z2 T7} to C{T0} form a 64 bit
404 Big Endian number that specifies the number of nanoseconds
405 that have passed since 1/1/1970 0:00 to the specified time.
406 This format is UTC 1970. C{Z1} and C{Z0} for a 16 bit Big
407 Endian number indicating the indicated time's timezone in
408 minutes.
409 """
410 ms = self.stream.read_double() / 1000.0
411 tz = self.stream.read_short()
412
413
414 d = datetime.datetime.utcfromtimestamp(ms)
415 self.context.addObject(d)
416
417 return d
418
426
436
438 """
439 Encodes an AMF0 stream.
440 """
441 context_class = Context
442
443 type_map = [
444 ((types.BuiltinFunctionType, types.BuiltinMethodType,),
445 "writeUnsupported"),
446 ((types.NoneType,), "writeNull"),
447 ((bool,), "writeBoolean"),
448 ((int,long,float), "writeNumber"),
449 ((types.StringTypes,), "writeString"),
450 ((pyamf.has_alias,pyamf.ASObject), "writeObject"),
451 ((pyamf.MixedArray,), "writeMixedArray"),
452 ((types.ListType, types.TupleType,), "writeArray"),
453 ((datetime.date, datetime.datetime), "writeDate"),
454 ((util.ET.iselement,), "writeXML"),
455 ((lambda x: x is pyamf.Undefined,), "writeUndefined"),
456 ((types.InstanceType,types.ObjectType,), "writeObject"),
457 ]
458
460 """
461 Writes the type to the stream.
462
463 @type type: C{int}
464 @param type: ActionScript type.
465
466 @raise pyamf.EncodeError: AMF0 type is not recognized.
467 """
468 if type not in ACTIONSCRIPT_TYPES:
469 raise pyamf.EncodeError("Unknown AMF0 type 0x%02x at %d" % (
470 type, self.stream.tell() - 1))
471
472 self.stream.write_uchar(type)
473
479
485
502
504 """
505 Writes the data.
506
507 @type data: C{mixed}
508 @param data: The data to be encoded to the AMF0 data stream.
509
510 @raise EncodeError: Unable to encode the data.
511 """
512 func = self._writeElementFunc(data)
513
514 if func is None:
515
516 self.writeUnsupported(data)
517 else:
518 try:
519 func(data)
520 except (KeyboardInterrupt, SystemExit):
521 raise
522 except pyamf.EncodeError:
523 raise
524 except:
525 raise
526 raise pyamf.EncodeError, "Unable to encode '%r'" % data
527
529 """
530 Write null type to data stream.
531
532 @type n: C{None}
533 @param n: Is ignored.
534 """
535 self.writeType(ASTypes.NULL)
536
555
557 """
558 Write number to the data stream.
559
560 @type n: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
561 @param n: AMF data.
562 """
563 self.writeType(ASTypes.NUMBER)
564 self.stream.write_double(float(n))
565
567 """
568 Write boolean to the data stream.
569
570 @type b: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
571 @param b: AMF data.
572 """
573 self.writeType(ASTypes.BOOL)
574
575 if b:
576 self.stream.write_uchar(1)
577 else:
578 self.stream.write_uchar(0)
579
581 if not isinstance(s, basestring):
582 s = unicode(s).encode('utf8')
583
584 if len(s) > 0xffff:
585 self.stream.write_ulong(len(s))
586 else:
587 self.stream.write_ushort(len(s))
588
589 self.stream.write(s)
590
592 """
593 Write string to the data stream.
594
595 @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
596 @param s: AMF data.
597 @type writeType: C{bool}
598 @param writeType: Write data type.
599 """
600 if isinstance(s, unicode):
601 s = s.encode('utf8')
602 elif not isinstance(s, basestring):
603 s = unicode(s).encode('utf8')
604
605 if len(s) > 0xffff:
606 if writeType:
607 self.writeType(ASTypes.LONGSTRING)
608 else:
609 if writeType:
610 self.stream.write_uchar(ASTypes.STRING)
611
612 self._writeString(s)
613
625
627 """
628 Write C{dict} to the data stream.
629
630 @type o: C{iterable}
631 @param o: AMF data.
632 """
633 for key, val in o.iteritems():
634 self.writeString(key, False)
635 self.writeElement(val)
636
638 """
639 Write mixed array to the data stream.
640
641 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
642 @param o: AMF data.
643 """
644 try:
645 self.writeReference(o)
646 return
647 except pyamf.ReferenceError:
648 self.context.addObject(o)
649
650 self.writeType(ASTypes.MIXEDARRAY)
651
652
653
654 try:
655
656 max_index = max([y[0] for y in o.items()
657 if isinstance(y[0], (int, long))])
658
659 if max_index < 0:
660 max_index = 0
661 except ValueError, e:
662 max_index = 0
663
664 self.stream.write_ulong(max_index)
665
666 self._writeDict(o)
667 self._writeEndObject()
668
674
676 obj_attrs = None
677
678 if alias is not None:
679 attrs = alias.getAttrs(o)
680
681 if attrs is not None:
682 obj_attrs = {}
683
684 for at in attrs:
685 obj_attrs[at] = getattr(o, at)
686
687 if obj_attrs is None:
688 obj_attrs = util.get_attrs(o)
689
690 if obj_attrs is None:
691 raise pyamf.EncodeError('Unable to determine object attributes')
692
693 return obj_attrs
694
731
733 """
734 Writes a date to the data stream.
735
736 @type d: Instance of C{datetime.datetime}
737 @param d: The date to be written.
738 """
739
740
741 secs = util.get_timestamp(d)
742 tz = 0
743
744 self.writeType(ASTypes.DATE)
745 self.stream.write_double(secs * 1000.0)
746 self.stream.write_short(tz)
747
749 """
750 Write XML to the data stream.
751
752 @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
753 @param e: AMF data.
754 """
755 data = util.ET.tostring(e, 'utf-8')
756
757 self.writeType(ASTypes.XML)
758 self.stream.write_ulong(len(data))
759 self.stream.write(data)
760
778
779 -def decode(stream, context=None):
780 """
781 A helper function to decode an AMF0 datastream.
782
783 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
784 @param stream: AMF0 datastream.
785 @type context: L{Context<pyamf.amf0.Context>}
786 @param context: AMF0 Context.
787 """
788 decoder = Decoder(stream, context)
789
790 while 1:
791 try:
792 yield decoder.readElement()
793 except pyamf.EOStream:
794 break
795
797 """
798 A helper function to encode an element into the AMF0 format.
799
800 @type element: C{mixed}
801 @param element: The element to encode
802 @type context: L{Context<pyamf.amf0.Context>}
803 @param context: AMF0 C{Context} to use for the encoding. This holds
804 previously referenced objects etc.
805 @rtype: C{StringIO}
806 @return: The encoded stream.
807 """
808 context = kwargs.get('context', None)
809 buf = util.BufferedByteStream()
810 encoder = Encoder(buf, context)
811
812 for element in args:
813 encoder.writeElement(element)
814
815 return buf
816
818 """
819 I represent the RecordSet class used in Flash Remoting to hold (amongst
820 other things) SQL records.
821
822 @ivar columns: The columns to send.
823 @type columns: List of strings.
824 @ivar items: The recordset data.
825 @type items: List of lists, the order of the data corresponds to the order
826 of the columns.
827 @ivar service: Service linked to the recordset.
828 @type service:
829 @ivar id: The id of the recordset.
830 @type id: C{str}
831
832 @see: U{RecordSet on OSFlash (external)
833 <http://osflash.org/documentation/amf/recordset>}
834 """
835
836 - def __init__(self, columns=[], items=[], service=None, id=None):
837 self.columns = columns
838 self.items = items
839 self.service = service
840 self.id = id
841
843 ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1,
844 initialData=self.items, columnNames=self.columns)
845
846 if self.service is not None:
847 ret.update({'serviceName': str(self.service['name'])})
848
849 if self.id is not None:
850 ret.update({'id':str(self.id)})
851
852 return ret
853
855 self.columns = val['columnNames']
856 self.items = val['initialData']
857
858 try:
859
860 self.service = dict(name=val['serviceName'])
861 except KeyError:
862 self.service = None
863
864 try:
865 self.id = val['id']
866 except KeyError:
867 self.id = None
868
869 serverInfo = property(_get_server_info, _set_server_info)
870
872 ret = '<%s.%s object' % (self.__module__, self.__class__.__name__)
873
874 if self.id is not None:
875 ret += ' id=%s' % self.id
876
877 if self.service is not None:
878 ret += ' service=%s' % self.service
879
880 ret += ' at 0x%x>' % id(self)
881
882 return ret
883
884 pyamf.register_class(RecordSet, 'RecordSet', attrs=['serverInfo'], metadata=['amf0'])
885
887 """
888 This is a compatibility function that takes a C{float} and converts it to an
889 C{int} if the values are equal.
890 """
891 try:
892 y = int(x)
893 except OverflowError:
894 pass
895 else:
896
897 if x == x and y == x:
898 return y
899
900 return x
901
902
903 try:
904 float('nan')
905 except ValueError:
906 pass
907 else:
908 if float('nan') == 0:
910 def f2(x):
911 if str(x).lower().find('nan') >= 0:
912 return x
913
914 return f2.func(x)
915 f2.func = func
916
917 return f2
918
919 _check_for_int = check_nan(_check_for_int)
920