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(codec=self) 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().encode('utf8') 356 357 while self.stream.peek() != ot: 358 obj_attrs[key] = self.readElement() 359 key = self.readString().encode('utf8') 360 361 # discard the end marker (ASTypes.OBJECTTERM) 362 self.stream.read(len(ot)) 363 364 if alias: 365 alias.applyAttributes(obj, obj_attrs, codec=self) 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 @raise EncodeError: Cannot find encoder func. 527 """ 528 func = self._writeElementFunc(data) 529 530 if func is None: 531 raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,)) 532 else: 533 try: 534 func(data) 535 except (KeyboardInterrupt, SystemExit): 536 raise 537 except pyamf.EncodeError: 538 raise 539 except: 540 #raise pyamf.EncodeError("Unable to encode '%r'" % (data,)) 541 raise
542
543 - def writeNull(self, n):
544 """ 545 Write null type to data stream. 546 547 @type n: C{None} 548 @param n: Is ignored. 549 """ 550 self.writeType(ASTypes.NULL)
551
552 - def writeArray(self, a):
553 """ 554 Write array to the stream. 555 556 @type a: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 557 @param a: The array data to be encoded to the AMF0 data stream. 558 """ 559 alias = self.context.getClassAlias(a.__class__) 560 561 if alias is not None and 'external' in alias.metadata: 562 # a is a subclassed list with a registered alias - push to the 563 # correct method 564 self.writeObject(a) 565 566 return 567 568 try: 569 self.writeReference(a) 570 return 571 except pyamf.ReferenceError: 572 self.context.addObject(a) 573 574 self.writeType(ASTypes.ARRAY) 575 self.stream.write_ulong(len(a)) 576 577 for data in a: 578 self.writeElement(data)
579
580 - def writeNumber(self, n):
581 """ 582 Write number to the data stream. 583 584 @type n: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 585 @param n: The number data to be encoded to the AMF0 data stream. 586 """ 587 self.writeType(ASTypes.NUMBER) 588 self.stream.write_double(float(n))
589
590 - def writeBoolean(self, b):
591 """ 592 Write boolean to the data stream. 593 594 @type b: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 595 @param b: The boolean data to be encoded to the AMF0 data stream. 596 """ 597 self.writeType(ASTypes.BOOL) 598 599 if b: 600 self.stream.write_uchar(1) 601 else: 602 self.stream.write_uchar(0)
603
604 - def _writeString(self, s):
605 if not isinstance(s, basestring): 606 s = unicode(s).encode('utf8') 607 608 if len(s) > 0xffff: 609 self.stream.write_ulong(len(s)) 610 else: 611 self.stream.write_ushort(len(s)) 612 613 self.stream.write(s)
614
615 - def writeString(self, s, writeType=True):
616 """ 617 Write string to the data stream. 618 619 @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 620 @param s: The string data to be encoded to the AMF0 data stream. 621 @type writeType: C{bool} 622 @param writeType: Write data type. 623 """ 624 if isinstance(s, unicode): 625 s = s.encode('utf8') 626 elif not isinstance(s, basestring): 627 s = unicode(s).encode('utf8') 628 629 if len(s) > 0xffff: 630 if writeType: 631 self.writeType(ASTypes.LONGSTRING) 632 else: 633 if writeType: 634 self.stream.write_uchar(ASTypes.STRING) 635 636 self._writeString(s)
637
638 - def writeReference(self, o):
639 """ 640 Write reference to the data stream. 641 642 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 643 @param o: The reference data to be encoded to the AMF0 data 644 stream. 645 """ 646 idx = self.context.getObjectReference(o) 647 648 self.writeType(ASTypes.REFERENCE) 649 self.stream.write_ushort(idx)
650
651 - def _writeDict(self, o):
652 """ 653 Write C{dict} to the data stream. 654 655 @type o: C{iterable} 656 @param o: The C{dict} data to be encoded to the AMF0 data 657 stream. 658 """ 659 for key, val in o.iteritems(): 660 self.writeString(key, False) 661 self.writeElement(val)
662
663 - def writeMixedArray(self, o):
664 """ 665 Write mixed array to the data stream. 666 667 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 668 @param o: The mixed array data to be encoded to the AMF0 669 data stream. 670 """ 671 try: 672 self.writeReference(o) 673 return 674 except pyamf.ReferenceError: 675 self.context.addObject(o) 676 677 self.writeType(ASTypes.MIXEDARRAY) 678 679 # TODO: optimise this 680 # work out the highest integer index 681 try: 682 # list comprehensions to save the day 683 max_index = max([y[0] for y in o.items() 684 if isinstance(y[0], (int, long))]) 685 686 if max_index < 0: 687 max_index = 0 688 except ValueError: 689 max_index = 0 690 691 self.stream.write_ulong(max_index) 692 693 self._writeDict(o) 694 self._writeEndObject()
695
696 - def _writeEndObject(self):
697 # Write a null string, this is an optimisation so that we don't 698 # have to waste precious cycles by encoding the string etc. 699 self.stream.write('\x00\x00') 700 self.writeType(ASTypes.OBJECTTERM)
701
702 - def _getObjectAttrs(self, o, alias):
703 """ 704 @raise pyamf.EncodeError: Unable to determine object attributes. 705 """ 706 obj_attrs = None 707 708 if alias is not None: 709 obj_attrs = {} 710 711 for attrs in alias.getAttributes(o, codec=self): 712 obj_attrs.update(attrs) 713 714 if obj_attrs is None: 715 obj_attrs = util.get_attrs(o) 716 717 if obj_attrs is None: 718 raise pyamf.EncodeError('Unable to determine object attributes') 719 720 return obj_attrs
721
722 - def writeObject(self, o):
723 """ 724 Write object to the stream. 725 726 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 727 @param o: The object data to be encoded to the AMF0 data stream. 728 """ 729 if self.use_amf3 is True: 730 self.writeAMF3(o) 731 732 return 733 734 try: 735 self.writeReference(o) 736 return 737 except pyamf.ReferenceError: 738 self.context.addObject(o) 739 740 alias = self.context.getClassAlias(o.__class__) 741 742 if alias is None: 743 self.writeType(ASTypes.OBJECT) 744 else: 745 if 'amf3' in alias.metadata: 746 self.writeAMF3(o) 747 748 return 749 750 if 'anonymous' in alias.metadata: 751 self.writeType(ASTypes.OBJECT) 752 else: 753 self.writeType(ASTypes.TYPEDOBJECT) 754 self.writeString(alias.alias, False) 755 756 obj_attrs = self._getObjectAttrs(o, alias) 757 758 for key, value in obj_attrs.iteritems(): 759 self.writeString(key, False) 760 self.writeElement(value) 761 762 self._writeEndObject()
763
764 - def writeDate(self, d):
765 """ 766 Writes a date to the data stream. 767 768 @type d: Instance of C{datetime.datetime} 769 @param d: The date to be encoded to the AMF0 data stream. 770 """ 771 # According to the Red5 implementation of AMF0, dates references are 772 # created, but not used. 773 secs = util.get_timestamp(d) 774 tz = 0 775 776 self.writeType(ASTypes.DATE) 777 self.stream.write_double(secs * 1000.0) 778 self.stream.write_short(tz)
779
780 - def writeXML(self, e):
781 """ 782 Write XML to the data stream. 783 784 @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 785 @param e: The XML data to be encoded to the AMF0 data stream. 786 """ 787 if self.use_amf3 is True: 788 self.writeAMF3(e) 789 790 return 791 792 self.writeType(ASTypes.XML) 793 794 data = util.ET.tostring(e, 'utf-8') 795 self.stream.write_ulong(len(data)) 796 self.stream.write(data)
797
798 - def writeAMF3(self, data):
799 """ 800 Writes an element to the datastream in L{AMF3<pyamf.amf3>} format. 801 802 @type data: C{mixed} 803 @param data: The data to be encoded to the AMF0 data stream. 804 """ 805 if not hasattr(self.context, 'amf3_context'): 806 from pyamf import amf3 807 808 self.context.amf3_context = amf3.Context() 809 810 self.context.addAMF3Object(data) 811 encoder = pyamf._get_encoder_class(pyamf.AMF3)(self.stream, self.context.amf3_context) 812 813 self.writeType(ASTypes.AMF3) 814 encoder.writeElement(data)
815
816 -def decode(stream, context=None, strict=False):
817 """ 818 A helper function to decode an AMF0 datastream. 819 820 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 821 @param stream: AMF0 datastream. 822 @type context: L{Context<pyamf.amf0.Context>} 823 @param context: AMF0 Context. 824 """ 825 decoder = Decoder(stream, context, strict=strict) 826 827 while 1: 828 try: 829 yield decoder.readElement() 830 except pyamf.EOStream: 831 break
832
833 -def encode(*args, **kwargs):
834 """ 835 A helper function to encode an element into the AMF0 format. 836 837 @type element: C{mixed} 838 @keyword element: The element to encode 839 @type context: L{Context<pyamf.amf0.Context>} 840 @keyword context: AMF0 C{Context} to use for the encoding. This holds 841 previously referenced objects etc. 842 @rtype: C{StringIO} 843 @return: The encoded stream. 844 """ 845 context = kwargs.get('context', None) 846 buf = util.BufferedByteStream() 847 encoder = Encoder(buf, context) 848 849 for element in args: 850 encoder.writeElement(element) 851 852 return buf
853
854 -class RecordSet(object):
855 """ 856 I represent the C{RecordSet} class used in Flash Remoting to hold 857 (amongst other things) SQL records. 858 859 @ivar columns: The columns to send. 860 @type columns: List of strings. 861 @ivar items: The C{RecordSet} data. 862 @type items: List of lists, the order of the data corresponds to the order 863 of the columns. 864 @ivar service: Service linked to the C{RecordSet}. 865 @type service: 866 @ivar id: The id of the C{RecordSet}. 867 @type id: C{str} 868 869 @see: U{RecordSet on OSFlash (external) 870 <http://osflash.org/documentation/amf/recordset>} 871 """ 872
873 - def __init__(self, columns=[], items=[], service=None, id=None):
874 self.columns = columns 875 self.items = items 876 self.service = service 877 self.id = id
878
879 - def _get_server_info(self):
880 ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1, 881 initialData=self.items, columnNames=self.columns) 882 883 if self.service is not None: 884 ret.update({'serviceName': str(self.service['name'])}) 885 886 if self.id is not None: 887 ret.update({'id':str(self.id)}) 888 889 return ret
890
891 - def _set_server_info(self, val):
892 self.columns = val['columnNames'] 893 self.items = val['initialData'] 894 895 try: 896 # TODO nick: find relevant service and link in here. 897 self.service = dict(name=val['serviceName']) 898 except KeyError: 899 self.service = None 900 901 try: 902 self.id = val['id'] 903 except KeyError: 904 self.id = None
905 906 serverInfo = property(_get_server_info, _set_server_info) 907
908 - def __repr__(self):
909 ret = '<%s.%s object' % (self.__module__, self.__class__.__name__) 910 911 if self.id is not None: 912 ret += ' id=%s' % self.id 913 914 if self.service is not None: 915 ret += ' service=%s' % self.service 916 917 ret += ' at 0x%x>' % id(self) 918 919 return ret
920 921 pyamf.register_class(RecordSet, 'RecordSet', attrs=['serverInfo'], metadata=['amf0']) 922
923 -def _check_for_int(x):
924 """ 925 This is a compatibility function that takes a C{float} and converts it to an 926 C{int} if the values are equal. 927 """ 928 try: 929 y = int(x) 930 except (OverflowError, ValueError): 931 pass 932 else: 933 # There is no way in AMF0 to distinguish between integers and floats 934 if x == x and y == x: 935 return y 936 937 return x
938 939 # check for some Python 2.3 problems with floats 940 try: 941 float('nan') 942 except ValueError: 943 pass 944 else: 945 if float('nan') == 0:
946 - def check_nan(func):
947 def f2(x): 948 if str(x).lower().find('nan') >= 0: 949 return x 950 951 return f2.func(x)
952 f2.func = func 953 954 return f2 955 956 _check_for_int = check_nan(_check_for_int) 957