blob: 5031c0eeb5eab0eeb662a88d44d587643b06aff9 [file]
# cython: boundscheck=False
# Copyright (c) The PyAMF Project.
# See LICENSE.txt for details.
"""
C-extension for L{pyamf.amf3} Python module in L{PyAMF<pyamf>}.
@since: 0.4
"""
from cpython cimport *
cdef extern from "datetime.h":
void PyDateTime_IMPORT()
int PyDateTime_CheckExact(object)
int PyDate_CheckExact(object)
int PyTime_CheckExact(object)
cdef extern from "Python.h":
bint PyClass_Check(object)
from cpyamf.util cimport cBufferedByteStream, BufferedByteStream
import types
import pyamf
from pyamf import util, xml
import datetime
cdef object MixedArray = pyamf.MixedArray
cdef object Undefined = pyamf.Undefined
cdef object BuiltinFunctionType = types.BuiltinFunctionType
cdef object GeneratorType = types.GeneratorType
PyDateTime_IMPORT
cdef class IndexedCollection(object):
"""
Provides reference functionality for amf contexts.
@see: L{pyamf.codec.IndexedCollection} for complete documentation
"""
def __cinit__(self, bint use_hash=0):
self.use_hash = use_hash
self.data = NULL
self.refs = {}
self.size = -1
self.length = -1
def __init__(self, use_hash=False):
self.use_hash = use_hash
self.clear()
cdef void _clear(self):
cdef Py_ssize_t i
if self.data != NULL:
for i from 0 <= i < self.length:
Py_DECREF(<object>self.data[i])
PyMem_Free(self.data)
self.data = NULL
def __dealloc__(self):
self._clear()
cdef int _actually_increase_size(self) except -1:
cdef Py_ssize_t new_len = self.length
cdef Py_ssize_t current_size = self.size
cdef PyObject **cpy
while new_len >= current_size:
current_size *= 2
if current_size != self.size:
self.size = current_size
cpy = <PyObject **>PyMem_Realloc(self.data, sizeof(PyObject *) * self.size)
if cpy == NULL:
self._clear()
PyErr_NoMemory()
self.data = cpy
return 0
cdef inline int _increase_size(self) except -1:
if self.length < self.size:
return 0
return self._actually_increase_size()
cpdef int clear(self) except -1:
self._clear()
self.length = 0
self.size = 64
self.data = <PyObject **>PyMem_Malloc(sizeof(PyObject *) * self.size)
if self.data == NULL:
PyErr_NoMemory()
self.refs = {}
return 0
cpdef object getByReference(self, Py_ssize_t ref):
if ref < 0 or ref >= self.length:
return None
return <object>self.data[ref]
cdef inline object _ref(self, object obj):
if self.use_hash:
return hash(obj)
return PyLong_FromVoidPtr(<void *>obj)
cpdef Py_ssize_t getReferenceTo(self, object obj) except -2:
cdef object p = self.refs.get(self._ref(obj), None)
if p is None:
return -1
return <Py_ssize_t>PyInt_AS_LONG(<object>p)
cpdef Py_ssize_t append(self, object obj) except -1:
self._increase_size()
cdef object h = self._ref(obj)
self.refs[h] = <object>self.length
self.data[self.length] = <PyObject *>obj
Py_INCREF(obj)
self.length += 1
return self.length - 1
def __iter__(self):
cdef list x = []
cdef Py_ssize_t idx
for idx from 0 <= idx < self.length:
x.append(<object>self.data[idx])
return iter(x)
def __len__(self):
return self.length
def __richcmp__(self, object other, int op):
cdef int equal
cdef Py_ssize_t i
cdef IndexedCollection s = self # this is necessary because cython does not see the c-space vars of the class for this func
if PyDict_Check(other) == 1:
equal = s.refs == other
elif PyList_Check(other) != 1:
equal = 0
else:
equal = 0
if PyList_GET_SIZE(other) == s.length:
equal = 1
for i from 0 <= i < s.length:
if <object>PyList_GET_ITEM(other, i) != <object>s.data[i]:
equal = 0
break
if op == 2: # ==
return equal
elif op == 3: # !=
return not equal
else:
raise NotImplementedError
def __getitem__(self, idx):
return self.getByReference(idx)
def __copy__(self):
cdef IndexedCollection n = IndexedCollection(self.use_hash)
return n
cdef class Context(object):
"""
I hold the AMF context for en/decoding streams.
@ivar objects: An indexed collection of referencable objects encountered
during en/decoding.
@type objects: L{util.IndexedCollection}
@ivar class_aliases: A L{dict} of C{class} to L{ClassAlias}
"""
def __cinit__(self):
self.objects = IndexedCollection()
self.clear()
def __init__(self):
self.clear()
cpdef int clear(self) except -1:
self.objects.clear()
self.class_aliases = {}
self.unicodes = {}
self._strings = {}
self.extra = {}
return 0
cpdef inline object getObject(self, Py_ssize_t ref):
return self.objects.getByReference(ref)
cpdef inline Py_ssize_t getObjectReference(self, object obj) except -2:
return self.objects.getReferenceTo(obj)
cpdef inline Py_ssize_t addObject(self, object obj) except -1:
return self.objects.append(obj)
cpdef object getClassAlias(self, object klass):
"""
Gets a class alias based on the supplied C{klass}.
@param klass: The class object.
@return: The L{ClassAlias} that is linked to C{klass}
"""
try:
return self.class_aliases[klass]
except KeyError:
pass
try:
alias = pyamf.get_class_alias(klass)
except pyamf.UnknownClassAlias:
if isinstance(klass, basestring):
raise
# no alias has been found yet .. check subclasses
alias = util.get_class_alias(klass) or pyamf.ClassAlias
meta = util.get_class_meta(klass)
alias = alias(klass, defer=True, **meta)
self.class_aliases[klass] = alias
return alias
cpdef unicode getStringForBytes(self, object s):
"""
Returns the corresponding unicode object for a given string. If there
is no unicode object, one is created.
@since: 0.6
"""
cdef object ret = self.unicodes.get(s, None)
if ret is not None:
return ret
cdef unicode u = s.decode('utf-8')
self.unicodes[s] = u
self._strings[u] = s
return u
cpdef str getBytesForString(self, object u):
"""
Returns the corresponding utf-8 encoded string for a given unicode
object. If there is no string, one is encoded.
@since: 0.6
"""
cdef object ret = self._strings.get(u, None)
if ret is not None:
return ret
cdef str s = u.encode('utf-8')
self.unicodes[s] = u
self._strings[u] = s
return s
cdef class Codec(object):
"""
Base class for Encoder/Decoder classes. Provides base functionality for
managing codecs.
"""
property stream:
def __get__(self):
return <BufferedByteStream>self.stream
def __set__(self, value):
if not isinstance(value, BufferedByteStream):
value = BufferedByteStream(value)
self.stream = <cBufferedByteStream>value
def __cinit__(self):
self.stream = None
self.strict = 0
self.timezone_offset = None
def __init__(self, stream=None, strict=False, timezone_offset=None):
if not isinstance(stream, BufferedByteStream):
stream = BufferedByteStream(stream)
self.stream = <cBufferedByteStream>stream
self.strict = strict
self.timezone_offset = timezone_offset
cdef class Decoder(Codec):
"""
Base AMF decoder.
"""
cdef object readDate(self):
raise NotImplementedError
cpdef object readString(self):
raise NotImplementedError
cdef object readObject(self):
raise NotImplementedError
cdef object readNumber(self):
raise NotImplementedError
cdef inline object readNull(self):
return None
cdef inline object readUndefined(self):
return Undefined
cdef object readList(self):
raise NotImplementedError
cdef object readXML(self):
raise NotImplementedError
cpdef object readElement(self):
"""
Reads an element from the data stream.
"""
cdef Py_ssize_t pos = self.stream.tell()
cdef char t
if self.stream.at_eof():
raise pyamf.EOStream
t = self.stream.read_char()
try:
return self.readConcreteElement(t)
except IOError:
self.stream.seek(pos)
raise
cdef object readConcreteElement(self, char t):
"""
The workhorse function. Overridden in subclasses
"""
raise NotImplementedError
cpdef int send(self, data) except -1:
"""
Add data for the decoder to work on.
"""
return self.stream.append(data)
def __next__(self):
"""
Part of the iterator protocol.
"""
try:
return self.readElement()
except pyamf.EOStream:
# all data was successfully decoded from the stream
raise StopIteration
def __iter__(self):
return self
cdef class Encoder(Codec):
"""
Base AMF encoder.
"""
def __cinit__(self):
self.func_cache = {}
self.use_write_object = []
self.bucket = []
cpdef int serialiseString(self, u) except -1:
raise NotImplementedError
cdef inline int writeType(self, char type) except -1:
return self.stream.write(<char *>&type, 1)
cdef int writeNull(self, object o) except -1:
raise NotImplementedError
cdef int writeUndefined(self, object o) except -1:
raise NotImplementedError
cdef int writeString(self, object o) except -1:
raise NotImplementedError
cdef int writeBytes(self, object o) except -1:
raise NotImplementedError
cdef int writeBoolean(self, object o) except -1:
raise NotImplementedError
cdef int writeInt(self, object o) except -1:
raise NotImplementedError
cdef int writeLong(self, object o) except -1:
raise NotImplementedError
cdef int writeNumber(self, object o) except -1:
raise NotImplementedError
cdef int writeDateTime(self, object o) except -1:
raise NotImplementedError
cdef int writeDate(self, object o) except -1:
o = datetime.datetime.combine(o, datetime.time(0, 0, 0, 0))
self.writeDateTime(o)
cdef int writeXML(self, object o) except -1:
raise NotImplementedError
cpdef int writeList(self, object o, bint is_proxy=0) except -1:
raise NotImplementedError
cdef int writeTuple(self, object o) except -1:
raise NotImplementedError
cdef int writeDict(self, dict o) except -1:
raise NotImplementedError
cdef int writeGenerator(self, object o) except -1:
cdef object n = getattr(o, 'next')
while True:
try:
self.writeElement(n())
except StopIteration:
return 0
cdef int writeSequence(self, object iterable) except -1:
"""
Encodes an iterable. The default is to write If the iterable has an al
"""
try:
alias = self.context.getClassAlias(iterable.__class__)
except (AttributeError, pyamf.UnknownClassAlias):
return self.writeList(iterable)
if alias.external:
# a is a subclassed list with a registered alias - push to the
# correct method
self.use_write_object.append(type(iterable))
return self.writeObject(iterable)
return self.writeList(iterable)
cpdef int writeObject(self, object o, bint is_proxy=0) except -1:
raise NotImplementedError
cdef int writeMixedArray(self, object o) except -1:
raise NotImplementedError
cdef inline int handleBasicTypes(self, object element, object py_type) except -1:
"""
@return: 0 = handled, -1 = error, 1 = not handled
"""
cdef int ret = 1
if PyString_Check(element):
ret = self.writeBytes(element)
elif PyUnicode_Check(element):
ret = self.writeString(element)
elif element is None:
ret = self.writeNull(element)
elif PyBool_Check(element):
ret = self.writeBoolean(element)
elif PyInt_CheckExact(element):
ret = self.writeInt(element)
elif PyLong_CheckExact(element):
ret = self.writeLong(element)
elif PyFloat_CheckExact(element):
ret = self.writeNumber(element)
elif PyList_CheckExact(element):
ret = self.writeList(element)
elif PyTuple_CheckExact(element):
ret = self.writeTuple(element)
elif element is Undefined:
ret = self.writeUndefined(element)
elif PyDict_CheckExact(element):
ret = self.writeDict(element)
elif PyDateTime_CheckExact(element):
ret = self.writeDateTime(element)
elif PyDate_CheckExact(element):
ret = self.writeDate(element)
elif py_type is MixedArray:
ret = self.writeMixedArray(element)
elif py_type is GeneratorType:
ret = self.writeGenerator(element)
elif PySequence_Contains(self.use_write_object, py_type):
ret = self.writeObject(element)
elif isinstance(element, (list, tuple)):
ret = self.writeSequence(element)
elif xml.is_xml(element):
ret = self.writeXML(element)
return ret
cdef int checkBadTypes(self, object element, object py_type) except -1:
if PyModule_CheckExact(element):
raise pyamf.EncodeError("Cannot encode modules")
elif PyMethod_Check(element):
raise pyamf.EncodeError("Cannot encode methods")
elif PyFunction_Check(element) or py_type is BuiltinFunctionType:
raise pyamf.EncodeError("Cannot encode functions")
elif PyClass_Check(element) or PyType_CheckExact(element):
raise pyamf.EncodeError("Cannot encode class objects")
elif PyTime_CheckExact(element):
raise pyamf.EncodeError('A datetime.time instance was found but '
'AMF has no way to encode time objects. Please use '
'datetime.datetime instead')
return 0
cpdef int writeElement(self, object element) except -1:
cdef int ret = 0
cdef object py_type = type(element)
cdef object func = None
cdef int use_proxy
ret = self.handleBasicTypes(element, py_type)
if ret == 1:
# encoding was not handled by basic types
func = self.func_cache.get(py_type, None)
if func is None:
func = get_custom_type_func(self, element)
if func is None:
self.checkBadTypes(element, py_type)
self.use_write_object.append(py_type)
return self.writeObject(element)
self.func_cache[py_type] = func
func(element)
return ret
cpdef int send(self, data) except -1:
"""
Add data for the decoder to work on.
"""
self.bucket.append(data)
def __next__(self):
"""
Part of the iterator protocol.
"""
cdef Py_ssize_t start_pos, end_pos
cdef char *buf
try:
element = self.bucket.pop(0)
except IndexError:
raise StopIteration
start_pos = self.stream.tell()
self.writeElement(element)
end_pos = self.stream.tell()
self.stream.seek(start_pos)
self.stream.read(&buf, end_pos - start_pos)
return PyString_FromStringAndSize(buf, end_pos - start_pos)
def __iter__(self):
return self
cdef class _CustomTypeFunc(object):
"""
Support for custom type mappings when encoding.
"""
cdef Encoder encoder
cdef object func
def __cinit__(self, Encoder encoder, func):
self.encoder = encoder
self.func = func
def __call__(self, data, **kwargs):
ret = self.func(data, encoder=self.encoder)
if ret is not None:
self.encoder.writeElement(ret)
cdef object get_custom_type_func(object encoder, object data):
cdef _CustomTypeFunc ret
for type_, func in pyamf.TYPE_MAP.iteritems():
try:
if isinstance(data, type_):
return _CustomTypeFunc(encoder, func)
except TypeError:
if callable(type_) and type_(data):
return _CustomTypeFunc(encoder, func)
return None