import mimetypes from zope.interface import implements from zope.interface import Interface from AccessControl import ClassSecurityInfo try: from App.class_init import InitializeClass except ImportError: from Globals import InitializeClass from ZPublisher.Iterators import filestream_iterator from webdav.Resource import Resource try: from zopyx.txng3.core.interfaces import IIndexableContent except ImportError: try: from textindexng.interfaces.indexable import IIndexableContent except ImportError: class IIndexableContent(Interface): pass from Products.CMFCore.DynamicType import DynamicType from Products.CMFCore.permissions import View from Products.CMFCore.utils import getToolByName from Products.Reflecto.interfaces import IReflectoFile from Products.Reflecto.content.proxy import BaseProxy, BaseMove from Products.Reflecto.config import HAS_CACHESETUP from Products.Reflecto.permissions import AddFilesystemObject from Products.Reflecto import chardet from ZServer import LARGE_FILE_THRESHOLD from App.Common import rfc1123_date from stat import ST_MTIME import os import os.path import tempfile class ReflectoFile(BaseMove, Resource, BaseProxy, DynamicType): """A filesystem reflected file.""" implements(IReflectoFile) meta_type = "ReflectoFile" portal_type = "ReflectoFile" security = ClassSecurityInfo() security.declareProtected(View, "get_data") def get_data(self): # Just to be more compatible with a standard file type return self.getFileContent() security.declareProtected(View, "getFileContent") def getFileContent(self): return open(self.getFilesystemPath(), "rb").read() security.declarePrivate(View, "setCacheHeaders") def setCacheHeaders(self): if not HAS_CACHESETUP: return from Products.CacheSetup.config import CACHE_TOOL_ID from Products.CacheSetup.cmf_utils import _setCacheHeaders # The CacheSetup API is not just weird. It's insane. pcs=getToolByName(self, CACHE_TOOL_ID, None) if pcs is None or not pcs.getEnabled(): return request=getattr(self, "REQUEST", None) if request is None: return rule=self.getReflector().getCacheRule() if not rule: return rule=getattr(pcs.getRules(), rule, None) if rule is None: return # We have to pretend that the default view is being accessed, # otherwise the rule will refuse to find a header set. member=pcs.getMember() header_set=rule.getHeaderSet(request, self, "reflecto_file_view", member) if header_set is None: return expr_context=rule._getExpressionContext(request, self, "reflecto_file_view", member, keywords={}) _setCacheHeaders(self, {}, rule, header_set, expr_context) def __call__(self): """Download the file""" self.setCacheHeaders() RESPONSE=self.REQUEST['RESPONSE'] iterator = filestream_iterator(self.getFilesystemPath(), 'rb') RESPONSE.setHeader('Last-Modified', rfc1123_date(self.getStatus()[ST_MTIME])) RESPONSE.setHeader('Content-Type', self.Format()) RESPONSE.setHeader('Content-Length', len(iterator)) return iterator security.declareProtected(View, "index_html") def index_html(self): """Download the file""" return self() def Format(self): extension=os.path.splitext(self.getId().lower())[1] type=None mtr=getToolByName(self, "mimetypes_registry", None) if mtr is not None: mimetype=mtr.lookupExtension(extension[1:]) if mimetype is None: mimetype=mtr.classify(self.getFileContent()) if mimetype is not None: return mimetype.normalized() try: return mimetypes.types_map[extension] except KeyError: return "application/octet-stream" get_content_type = Format getContentType = Format security.declareProtected(View, "SearchableText") def SearchableText(self): """Return textual content of the file for the search index. This is usually only used If TextIndexNG3 is not installed. But if collective.solr is used then this is used even with TextIndexNG3. """ result = BaseProxy.SearchableText(self) indexable = IIndexableContent(self, None) if indexable is not None: # We might get here if TextIndexNG3 is installed but # the content is being indexed by collective.solr. # In this case, use TextIndexNG3 to obtain the text for # binary files. icc = indexable.indexableContent(['SearchableText']) result = ' '.join(info['content'].encode('utf8') for info in icc.getFieldData('SearchableText')) elif self.Format().startswith("text/"): data = self.get_data() encoding = chardet.detect(data)["encoding"] result += ' ' + data.decode(encoding, 'ignore').encode('utf8') return result security.declareProtected(AddFilesystemObject, 'PUT') def PUT(self, REQUEST, RESPONSE): """Handle HTTP PUT requests""" self.dav__init(REQUEST, RESPONSE) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) file=REQUEST['BODYFILE'] path = self.getFilesystemPath() if isinstance(file, tempfile._TemporaryFileWrapper): # Zope >= 2.11 # If we only ran on unix we would use os.link here. Instead, rename # the file and manually close the NamedTemporaryFile, bypassing the # call to os.unlink. os.rename(file.name, path) os.chmod(path, 0644) file.file.close() file.close_called = True else: # Zope < 2.11 try: # For OSes which support it (Windows) we need to use the # O_BINARY flag to prevent cr/lf rewriting. # os.O_EXCL not used so uploads can overwrite existing files flags=os.O_WRONLY|os.O_CREAT|os.O_TRUNC|os.O_BINARY except AttributeError: flags=os.O_WRONLY|os.O_CREAT|os.O_TRUNC fd=os.open(self.getFilesystemPath(), flags, 0644) data = file.read(LARGE_FILE_THRESHOLD) # 512k chunks, might be optimal... while data: os.write(fd, data) data = file.read(LARGE_FILE_THRESHOLD) os.close(fd) self.indexObject() InitializeClass(ReflectoFile)