import tempfile import posixpath from zope import event from zExceptions import MethodNotAllowed from ZPublisher.Iterators import IStreamIterator from Products.CMFCore.utils import getToolByName from Products.Archetypes.event import WebDAVObjectInitializedEvent from Products.Archetypes.event import WebDAVObjectEditedEvent from Products.Archetypes.utils import shasattr, mapply from zope.interface import implements, Interface class PdataStreamIterator(object): implements(IStreamIterator) def __init__(self, data, size, streamsize=1 << 16): # Consume the whole data into a TemporaryFile when # constructing, otherwise we might end up loading the whole # file in memory or worse, loading objects after the # connection is closed. f = tempfile.TemporaryFile(mode='w+b') while data is not None: f.write(data.data) data = data.next assert size == f.tell(), 'Informed length does not match real length' f.seek(0) self.file = f self.size = size self.streamsize = streamsize def __iter__(self): return self def next(self): data = self.file.read(self.streamsize) if not data: self.file.close() raise StopIteration return data def __len__(self): return self.size _marker = [] def collection_check(self): if not shasattr(self, '__dav_marshall__'): # Backwards-compatible, if property not set ignore. return if not self.__dav_marshall__: # If property is set to a false value, do not allow # marshalling. raise MethodNotAllowed, 'Method not supported.' def PUT(self, REQUEST=None, RESPONSE=None): """ HTTP PUT handler with marshalling support """ if not REQUEST: REQUEST = self.REQUEST if not RESPONSE: RESPONSE = REQUEST.RESPONSE if not self.Schema().hasLayer('marshall'): RESPONSE.setStatus(501) # Not implemented return RESPONSE self.dav__init(REQUEST, RESPONSE) collection_check(self) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) is_new_object = self.checkCreationFlag() file = REQUEST.get('BODYFILE', _marker) if file is _marker: data = REQUEST.get('BODY', _marker) if data is _marker: raise AttributeError, 'REQUEST neither has a BODY nor a BODYFILE' else: data = '' file.seek(0) mimetype = REQUEST.get_header('content-type', None) # mimetype, if coming from request can be like: # text/plain; charset='utf-8' # # XXX we should really parse the extra params and pass them on as # keyword arguments. if mimetype is not None: mimetype = str(mimetype).split(';')[0].strip() filename = posixpath.basename(REQUEST.get('PATH_INFO', self.getId())) # XXX remove after we are using global services # use the request to find an object in the traversal hierachy that is # able to acquire a mimetypes_registry instance # This is a hack to avoid the acquisition problem on FTP/WebDAV object # creation parents = (self,) + tuple(REQUEST.get('PARENTS', ())) context = None for parent in parents: if getToolByName(parent, 'mimetypes_registry', None) is not None: context = parent break # Marshall the data marshaller = self.Schema().getLayerImpl('marshall') args = [self, data] kwargs = {'file': file, 'context': context, 'mimetype': mimetype, 'filename': filename, 'REQUEST': REQUEST, 'RESPONSE': RESPONSE} ddata = mapply(marshaller.demarshall, *args, **kwargs) if (shasattr(self, 'demarshall_hook') and self.demarshall_hook): self.demarshall_hook(ddata) self.manage_afterPUT(data, marshall_data=ddata, **kwargs) self.reindexObject() self.unmarkCreationFlag() if is_new_object: event.notify(WebDAVObjectInitializedEvent(self)) else: event.notify(WebDAVObjectEditedEvent(self)) RESPONSE.setStatus(204) return RESPONSE def manage_FTPget(self, REQUEST=None, RESPONSE=None): """Get the raw content for this object (also used for the WebDAV source) """ if REQUEST is None: REQUEST = self.REQUEST if RESPONSE is None: RESPONSE = REQUEST.RESPONSE if not self.Schema().hasLayer('marshall'): RESPONSE.setStatus(501) # Not implemented return RESPONSE self.dav__init(REQUEST, RESPONSE) collection_check(self) marshaller = self.Schema().getLayerImpl('marshall') ddata = marshaller.marshall(self, REQUEST=REQUEST, RESPONSE=RESPONSE) if (shasattr(self, 'marshall_hook') and self.marshall_hook): ddata = self.marshall_hook(ddata) content_type, length, data = ddata RESPONSE.setHeader('Content-Type', content_type) # Only set Content-Length header length is not None. If we want to # support 'chunked' Transfer-Encoding we shouldn't set # this. However ExternalEditor *expects* it to be set if we return # a StreamIterator, so until that gets fixed we must set it. if length is not None: RESPONSE.setHeader('Content-Length', length) if type(data) is type(''): return data # We assume 'data' is a 'Pdata chain' as used by OFS.File and # return a StreamIterator. assert length is not None, 'Could not figure out length of Pdata chain' if (issubclass(IStreamIterator, Interface) and IStreamIterator.providedBy(data) or not issubclass(IStreamIterator, Interface) and IStreamIterator.IsImplementedBy(data)): return data return PdataStreamIterator(data, length) def manage_afterPUT(self, data, marshall_data, file, context, mimetype, filename, REQUEST, RESPONSE): """After webdav/ftp PUT method """ pass