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

Source Code for Module pyamf.amf0

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2007-2008 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  @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   
31 -class ASTypes:
32 """ 33 The AMF/RTMP data encoding format constants. 34 35 @see: U{Data types on OSFlash (external) 36 <http://osflash.org/documentation/amf/astypes>} 37 """ 38 #: Represented as 9 bytes: 1 byte for C{0×00} and 8 bytes a double 39 #: representing the value of the number. 40 NUMBER = 0x00 41 #: Represented as 2 bytes: 1 byte for C{0×01} and a second, C{0×00} 42 #: for C{False}, C{0×01} for C{True}. 43 BOOL = 0x01 44 #: Represented as 3 bytes + len(String): 1 byte C{0×02}, then a UTF8 string, 45 #: including the top two bytes representing string length as a C{int}. 46 STRING = 0x02 47 #: Represented as 1 byte, C{0×03}, then pairs of UTF8 string, the key, and 48 #: an AMF element, ended by three bytes, C{0×00} C{0×00} C{0×09}. 49 OBJECT = 0x03 50 #: MovieClip does not seem to be supported by Remoting. 51 #: It may be used by other AMF clients such as SharedObjects. 52 MOVIECLIP = 0x04 53 #: 1 single byte, C{0×05} indicates null. 54 NULL = 0x05 55 #: 1 single byte, C{0×06} indicates null. 56 UNDEFINED = 0x06 57 #: When an ActionScript object refers to itself, such C{this.self = this}, 58 #: or when objects are repeated within the same scope (for example, as the 59 #: two parameters of the same function called), a code of C{0×07} and an 60 #: C{int}, the reference number, are written. 61 REFERENCE = 0x07 62 #: A MixedArray is indicated by code C{0×08}, then a Long representing the 63 #: highest numeric index in the array, or 0 if there are none or they are 64 #: all negative. After that follow the elements in key : value pairs. 65 MIXEDARRAY = 0x08 66 #: @see: L{OBJECT} 67 OBJECTTERM = 0x09 68 #: An array is indicated by C{0x0A}, then a Long for array length, then the 69 #: array elements themselves. Arrays are always sparse; values for 70 #: inexistant keys are set to null (C{0×06}) to maintain sparsity. 71 ARRAY = 0x0a 72 #: Date is represented as C{00x0B}, then a double, then an C{int}. The double 73 #: represents the number of milliseconds since 01/01/1970. The C{int} represents 74 #: the timezone offset in minutes between GMT. Note for the latter than values 75 #: greater than 720 (12 hours) are represented as M{2^16} - the value. Thus GMT+1 76 #: is 60 while GMT-5 is 65236. 77 DATE = 0x0b 78 #: LongString is reserved for strings larger then M{2^16} characters long. It 79 #: is represented as C{00x0C} then a LongUTF. 80 LONGSTRING = 0x0c 81 #: Trying to send values which don’t make sense, such as prototypes, functions, 82 #: built-in objects, etc. will be indicated by a single C{00x0D} byte. 83 UNSUPPORTED = 0x0d 84 #: Remoting Server -> Client only. 85 #: @see: L{RecordSet} 86 #: @see: U{RecordSet structure on OSFlash (external) 87 #: <http://osflash.org/documentation/amf/recordset>} 88 RECORDSET = 0x0e 89 #: The XML element is indicated by C{00x0F} and followed by a LongUTF containing 90 #: the string representation of the XML object. The receiving gateway may which 91 #: to wrap this string inside a language-specific standard XML object, or simply 92 #: pass as a string. 93 XML = 0x0f 94 #: A typed object is indicated by C{0×10}, then a UTF string indicating class 95 #: name, and then the same structure as a normal C{0×03} Object. The receiving 96 #: gateway may use a mapping scheme, or send back as a vanilla object or 97 #: associative array. 98 TYPEDOBJECT = 0x10 99 #: An AMF message sent from an AVM+ client such as the Flash Player 9 may break 100 #: out into L{AMF3<pyamf.amf3>} mode. In this case the next byte will be the 101 #: AMF3 type code and the data will be in AMF3 format until the decoded object 102 #: reaches it’s logical conclusion (for example, an object has no more keys). 103 AMF3 = 0x11
104 105 #: List of available ActionScript types in AMF0. 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 """
119 - def clear(self):
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
144 - def getAMF3ObjectReference(self, obj):
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
171 -class Decoder(pyamf.BaseDecoder):
172 """ 173 Decodes an AMF0 stream. 174 """ 175 context_class = Context 176 177 # XXX nick: Do we need to support ASTypes.MOVIECLIP here? 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 # TODO: do we need a special value here? 191 ASTypes.UNSUPPORTED:'readNull', 192 ASTypes.XML: 'readXML', 193 ASTypes.TYPEDOBJECT:'readTypedObject', 194 ASTypes.AMF3: 'readAMF3' 195 } 196
197 - def readType(self):
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
212 - def readNumber(self):
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
223 - def readBoolean(self):
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
232 - def readNull(self):
233 """ 234 Reads a ActionScript C{null} value. 235 236 @return: C{None} 237 @rtype: C{None} 238 """ 239 return None
240
241 - def readUndefined(self):
242 """ 243 Reads an ActionScript C{undefined} value. 244 245 @return: L{Undefined<pyamf.Undefined>} 246 """ 247 return pyamf.Undefined
248
249 - def readMixedArray(self):
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 # XXX: do we want to ignore this? 270 pass 271 272 ikeys.sort() 273 274 return obj
275
276 - def readList(self):
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
292 - def readTypedObject(self):
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
308 - def readAMF3(self):
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
327 - def readString(self):
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
337 - def _readObject(self, obj, alias=None):
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 # discard the end marker (ASTypes.OBJECTTERM) 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
377 - def readObject(self):
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
390 - def readReference(self):
391 """ 392 Reads a reference from the data stream. 393 """ 394 idx = self.stream.read_ushort() 395 396 return self.context.getObject(idx)
397
398 - def readDate(self):
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 # Timezones are ignored 414 d = datetime.datetime.utcfromtimestamp(ms) 415 self.context.addObject(d) 416 417 return d
418
419 - def readLongString(self):
420 """ 421 Read UTF8 string. 422 """ 423 len = self.stream.read_ulong() 424 425 return self.stream.read_utf8_string(len)
426
427 - def readXML(self):
428 """ 429 Read XML. 430 """ 431 data = self.readLongString() 432 xml = util.ET.fromstring(data) 433 self.context.addObject(xml) 434 435 return xml
436
437 -class Encoder(pyamf.BaseEncoder):
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
459 - def writeType(self, type):
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
474