Package pyamf :: Module amf0
[hide private]
[frames] | no frames]

Source Code for Module pyamf.amf0

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2007-2009 The PyAMF Project. 
  4  # See LICENSE for details. 
  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   
28 -class ASTypes:
29 """ 30 The AMF/RTMP data encoding format constants. 31 32 @see: U{Data types on OSFlash (external) 33 <http://osflash.org/documentation/amf/astypes>} 34 """ 35 #: Represented as 9 bytes: 1 byte for C{0×00} and 8 bytes a double 36 #: representing the value of the number. 37 NUMBER = 0x00 38 #: Represented as 2 bytes: 1 byte for C{0×01} and a second, C{0×00} 39 #: for C{False}, C{0×01} for C{True}. 40 BOOL = 0x01 41 #: Represented as 3 bytes + len(String): 1 byte C{0×02}, then a UTF8 string, 42 #: including the top two bytes representing string length as a C{int}. 43 STRING = 0x02 44 #: Represented as 1 byte, C{0×03}, then pairs of UTF8 string, the key, and 45 #: an AMF element, ended by three bytes, C{0×00} C{0×00} C{0×09}. 46 OBJECT = 0x03 47 #: MovieClip does not seem to be supported by Remoting. 48 #: It may be used by other AMF clients such as SharedObjects. 49 MOVIECLIP = 0x04 50 #: 1 single byte, C{0×05} indicates null. 51 NULL = 0x05 52 #: 1 single byte, C{0×06} indicates null. 53 UNDEFINED = 0x06 54 #: When an ActionScript object refers to itself, such C{this.self = this}, 55 #: or when objects are repeated within the same scope (for example, as the 56 #: two parameters of the same function called), a code of C{0×07} and an 57 #: C{int}, the reference number, are written. 58 REFERENCE = 0x07 59 #: A MixedArray is indicated by code C{0×08}, then a Long representing the 60 #: highest numeric index in the array, or 0 if there are none or they are 61 #: all negative. After that follow the elements in key : value pairs. 62 MIXEDARRAY = 0x08 63 #: @see: L{OBJECT} 64 OBJECTTERM = 0x09 65 #: An array is indicated by C{0x0A}, then a Long for array length, then the 66 #: array elements themselves. Arrays are always sparse; values for 67 #: inexistant keys are set to null (C{0×06}) to maintain sparsity. 68 ARRAY = 0x0a 69 #: Date is represented as C{00x0B}, then a double, then an C{int}. The double 70 #: represents the number of milliseconds since 01/01/1970. The C{int} represents 71 #: the timezone offset in minutes between GMT. Note for the latter than values 72 #: greater than 720 (12 hours) are represented as M{2^16} - the value. Thus GMT+1 73 #: is 60 while GMT-5 is 65236. 74 DATE = 0x0b 75 #: LongString is reserved for strings larger then M{2^16} characters long. It 76 #: is represented as C{00x0C} then a LongUTF. 77 LONGSTRING = 0x0c 78 #: Trying to send values which don’t make sense, such as prototypes, functions, 79 #: built-in objects, etc. will be indicated by a single C{00x0D} byte. 80 UNSUPPORTED = 0x0d 81 #: Remoting Server -> Client only. 82 #: @see: L{RecordSet} 83 #: @see: U{RecordSet structure on OSFlash (external) 84 #: <http://osflash.org/documentation/amf/recordset>} 85 RECORDSET = 0x0e 86 #: The XML element is indicated by C{00x0F} and followed by a LongUTF containing 87 #: the string representation of the XML object. The receiving gateway may which 88 #: to wrap this string inside a language-specific standard XML object, or simply 89 #: pass as a string. 90 XML = 0x0f 91 #: A typed object is indicated by C{0×10}, then a UTF string indicating class 92 #: name, and then the same structure as a normal C{0×03} Object. The receiving 93 #: gateway may use a mapping scheme, or send back as a vanilla object or 94 #: associative array. 95 TYPEDOBJECT = 0x10 96 #: An AMF message sent from an AVM+ client such as the Flash Player 9 may break 97 #: out into L{AMF3<pyamf.amf3>} mode. In this case the next byte will be the 98 #: AMF3 type code and the data will be in AMF3 format until the decoded object 99 #: reaches it’s logical conclusion (for example, an object has no more keys). 100 AMF3 = 0x11
101 102 #: List of available ActionScript types in AMF0. 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):
121 self.amf3_objs = util.IndexedCollection() 122 123 pyamf.BaseContext.__init__(self)
124
125 - def clear(self):
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
136 - def reset(self):
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
147 - def getAMF3ObjectReference(self, obj):
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
175 -class Decoder(pyamf.BaseDecoder):
176 """ 177 Decodes an AMF0 stream. 178 """ 179 180 context_class = Context 181 182 # XXX nick: Do we need to support ASTypes.MOVIECLIP here? 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 # TODO: do we need a special value here? 196 ASTypes.UNSUPPORTED:'readNull', 197 ASTypes.XML: 'readXML', 198 ASTypes.TYPEDOBJECT:'readTypedObject', 199 ASTypes.AMF3: 'readAMF3' 200 } 201
202 - def readType(self):
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
217 - def readNumber(self):
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
228 - def readBoolean(self):
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
237 - def readNull(self):
238 """ 239 Reads a ActionScript C{null} value. 240 241 @return: C{None} 242 @rtype: C{None} 243 """ 244 return None
245
246 - def readUndefined(self):
247 """ 248 Reads an ActionScript C{undefined} value. 249 250 @return: L{Undefined<pyamf.Undefined>} 251 """ 252 return pyamf.Undefined
253
254 - def readMixedArray(self):
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 # XXX: do we want to ignore this? 275 pass 276 277 ikeys.sort() 278 279 return obj
280
281 - def readList(self):
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
297 - def readTypedObject(self):
298 """ 299 Reads an ActionScript object from the stream and attempts to 300 'cast' it. 301 302 @see: L{load_class<pyamf.load_class>} 303 """ 304 classname = self.readString() 305 alias = None 306 307 try: 308 alias = pyamf.load_class(classname) 309 310 ret = alias.createInstance() 311 except pyamf.UnknownClassAlias: 312 if self.strict: 313 raise 314 315 ret = pyamf.TypedObject(classname) 316 317 self.context.addObject(ret) 318 self._readObject(ret, alias) 319 320 return ret
321
322 - def readAMF3(self):
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
341 - def readString(self):
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
351 - def _readObject(self, obj, alias=None):
352 ot = chr(ASTypes.OBJECTTERM) 353 obj_attrs = dict() 354 355 key = self.readString() 356 357 while self.stream.peek() != ot: 358 obj_attrs[key] = self.readElement() 359 key = self.readString() 360 361 # discard the end marker (ASTypes.OBJECTTERM) 362 self.stream.read(len(ot)) 363 364 if alias: 365 alias.applyAttributes(obj, obj_attrs) 366 else: 367 util.set_attrs(obj, obj_attrs)
368
369 - def readObject(self):
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
382 - def readReference(self):
383 """ 384 Reads a reference from the data stream. 385 """ 386 idx = self.stream.read_ushort() 387 388 return self.context.getObject(idx)
389
390 - def readDate(self):
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 # Timezones are ignored 406 d = util.get_datetime(ms) 407 self.context.addObject(d) 408 409 return d
410
411 - def readLongString(self):
412 """ 413 Read UTF8 string. 414 """ 415 len = self.stream.read_ulong() 416 417 return self.stream.read_utf8_string(len)
418
419 - def readXML(self):
420 """ 421 Read XML. 422 """ 423 data = self.readLongString() 424 xml = util.ET.fromstring(data) 425 self.context.addObject(xml) 426 427 return xml
428
429 -class Encoder(pyamf.BaseEncoder):
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
456 - def __init__(self, *args, **kwargs):
457 self.use_amf3 = kwargs.pop('use_amf3', False) 458 459 pyamf.BaseEncoder.__init__(self, *args, **kwargs)
460
461 - def writeType(self, type):
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
476 - def writeUndefined(self, data):
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
486 - def writeFunc(self, *args, **kwargs):
487 """ 488 Functions cannot be serialised. 489 """ 490 raise pyamf.EncodeError("Callables cannot be serialised")
491
492 - def writeUnsupported(self, data):
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
503 - def _writeElementFunc(self, data):
504 """ 505 Gets a function based on the type of data. 506 507 @see: L{pyamf.BaseEncoder._writeElementFunc} 508 """ 509 # There is a very specific use case that we must check for. 510 # In the context there is an array of amf3_objs that contain 511 # references to objects that are to be encoded in amf3. 512 try: 513 self.context.getAMF3ObjectReference(data) 514 return self.writeAMF3 515 except pyamf.ReferenceError: 516 pass 517 518 return pyamf.BaseEncoder._writeElementFunc(self, data)
519
520 - def writeElement(self, data):
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 #raise pyamf.EncodeError("Unable to encode '%r'" % (data,)) 543 raise
544
545 - def writeNull(self, n):
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
554 - def writeArray(self, a):
555 """ 556 Write array to the stream. 557 558 @type a: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 559 @param a: The array data to be encoded to the AMF0 data stream. 560 """ 561 alias = self.context.getClassAlias(a.__class__) 562 563 if alias is not None and 'external' in alias.metadata: 564 # a is a subclassed list with a registered alias - push to the 565 # correct method 566 self.writeObject(a) 567 568 return 569 570 try: 571 self.writeReference(a) 572 return 573 except pyamf.ReferenceError: 574 self.context.addObject(a) 575 576 self.writeType(ASTypes.ARRAY) 577 self.stream.write_ulong(len(a)) 578 579 for data in a: 580 self.writeElement(data)
581
582 - def writeNumber(self, n):
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
592 - def writeBoolean(self, b):
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
606 - def _writeString(self, s):
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
617 - def writeString(self, s, writeType=True):
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
640 - def writeReference(self, o):
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
653 - def _writeDict(self, o):
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
665 - def writeMixedArray(self, o):
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 # TODO: optimise this 682 # work out the highest integer index 683 try: 684 # list comprehensions to save the day 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
698 - def _writeEndObject(self):
699 # Write a null string, this is an optimisation so that we don't 700 # have to waste precious cycles by encoding the string etc. 701 self.stream.write('\x00\x00') 702 self.writeType(ASTypes.OBJECTTERM)
703
704 - def _getObjectAttrs(self, o, alias):
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
724 - def writeObject(self, o):
725 """ 726 Write object to the stream. 727 728 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 729 @param o: The object data to be encoded to the AMF0 data stream. 730 """ 731 try: 732 self.writeReference(o) 733 return 734 except pyamf.ReferenceError: 735 self.context.addObject(o) 736 737 alias = self.context.getClassAlias(o.__class__) 738 739 if alias is None: 740 self.writeType(ASTypes.OBJECT) 741 else: 742 if 'amf3' in alias.metadata: 743 self.writeAMF3(o) 744 745 return 746 747 if 'anonymous' in alias.metadata: 748 self.writeType(ASTypes.OBJECT) 749 else: 750 self.writeType(ASTypes.TYPEDOBJECT) 751 self.writeString(alias.alias, False) 752 753 obj_attrs = self._getObjectAttrs(o, alias) 754 755 for key, value in obj_attrs.iteritems(): 756 self.writeString(key, False) 757 self.writeElement(value) 758 759 self._writeEndObject()
760
761 - def writeDate(self, d):
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 # According to the Red5 implementation of AMF0, dates references are 769 # created, but not used. 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
777 - def writeXML(self, e):
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
795 - def writeAMF3(self, data):
796 """ 797 Writes an element to the datastream in L{AMF3<pyamf.amf3>} format. 798 799 @type data: C{mixed} 800 @param data: The data to be encoded to the AMF0 data stream. 801 """ 802 if not hasattr(self.context, 'amf3_context'): 803 from pyamf import amf3 804 805 self.context.amf3_context = amf3.Context() 806 807 self.context.addAMF3Object(data) 808 encoder = pyamf._get_encoder_class(pyamf.AMF3)(self.stream, self.context.amf3_context) 809 810 self.writeType(ASTypes.AMF3) 811 encoder.writeElement(data)
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
830 -def encode(*args, **kwargs):
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
851 -class RecordSet(object):
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
876 - def _get_server_info(self):
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
888 - def _set_server_info(self, val):
889 self.columns = val['columnNames'] 890 self.items = val['initialData'] 891 892 try: 893 # TODO nick: find relevant service and link in here. 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
905 - def __repr__(self):
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
920 -def _check_for_int(x):
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 # There is no way in AMF0 to distinguish between integers and floats 931 if x == x and y == x: 932 return y 933 934 return x
935 936 # check for some Python 2.3 problems with floats 937 try: 938 float('nan') 939 except ValueError: 940 pass 941 else: 942 if float('nan') == 0:
943 - def check_nan(func):
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