diff --git a/browser/fileresource.py b/browser/fileresource.py new file mode 100644 index 0000000..31888f7 --- /dev/null +++ b/browser/fileresource.py @@ -0,0 +1,140 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""File-based browser resources. + +$Id$ +""" + +import time +from zope.security.proxy import Proxy +from zope.interface import implements +from zope.datetime import time as timeFromDateTimeString +from zope.publisher.interfaces import NotFound +from zope.publisher.interfaces.browser import IBrowserPublisher + +from zope.app.publisher.browser import BrowserView +from zope.app.publisher.fileresource import File, Image +from zope.app.publisher.browser.resource import Resource + +class FileResource(BrowserView, Resource): + + implements(IBrowserPublisher) + + def publishTraverse(self, request, name): + '''See interface IBrowserPublisher''' + raise NotFound(None, name) + + def browserDefault(self, request): + '''See interface IBrowserPublisher''' + return getattr(self, request.method), () + + # + ############################################################ + + # for unit tests + def _testData(self): + f = open(self.context.path, 'rb') + data = f.read() + f.close() + return data + + + def chooseContext(self): + """Choose the appropriate context""" + return self.context + + + def GET(self): + """Default document""" + + file = self.chooseContext() + request = self.request + response = request.response + + # HTTP If-Modified-Since header handling. This is duplicated + # from OFS.Image.Image - it really should be consolidated + # somewhere... + header = request.getHeader('If-Modified-Since', None) + if header is not None: + header = header.split(';')[0] + # Some proxies seem to send invalid date strings for this + # header. If the date string is not valid, we ignore it + # rather than raise an error to be generally consistent + # with common servers such as Apache (which can usually + # understand the screwy date string as a lucky side effect + # of the way they parse it). + try: mod_since=long(timeFromDateTimeString(header)) + except: mod_since=None + if mod_since is not None: + if getattr(file, 'lmt', None): + last_mod = long(file.lmt) + else: + last_mod = long(0) + if last_mod > 0 and last_mod <= mod_since: + response.setStatus(304) + return '' + + response.setHeader('Content-Type', file.content_type) + response.setHeader('Last-Modified', file.lmh) + + setCacheControl(response) + f = open(file.path,'rb') + data = f.read() + f.close() + + return data + + def HEAD(self): + file = self.chooseContext() + response = self.request.response + response.setHeader('Content-Type', file.content_type) + response.setHeader('Last-Modified', file.lmh) + setCacheControl(response) + return '' + + +def setCacheControl(response, secs=86400): + # Cache for one day by default + response.setHeader('Cache-Control', 'public,max-age=%s' % secs) + t = time.time() + secs + response.setHeader('Expires', + time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(t))) + + +class FileResourceFactory(object): + + def __init__(self, path, checker, name): + self.__file = File(path, name) + self.__checker = checker + self.__name = name + + def __call__(self, request): + resource = FileResource(self.__file, request) + resource.__Security_checker__ = self.__checker + resource.__name__ = self.__name + return resource + +class ImageResourceFactory(object): + + def __init__(self, path, checker, name): + self.__file = Image(path, name) + self.__checker = checker + self.__name = name + + def __call__(self, request): + resource = FileResource(self.__file, request) + resource.__Security_checker__ = self.__checker + resource.__name__ = self.__name + return resource diff --git a/fieldconverters.py b/fieldconverters.py new file mode 100644 index 0000000..7c9a9a1 --- /dev/null +++ b/fieldconverters.py @@ -0,0 +1,61 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Zope-specific request field converters. + +$Id$ +""" +from datetime import datetime + +from zope.publisher.browser import registerTypeConverter +from zope.datetime import parse as parseDateTime + +def field2date_via_datetimeutils(v): + """Converter for request fields marshalled as ':date'. + + o TODO: Uses the non-localized and non-tzinfo-aware 'parseDateTime' + utility from zope.datetime; a better alternative + would be more I18N / L10N aware, perhaps even adapting to + the expressed preferences of the user. + """ + if hasattr(v,'read'): + v = v.read() + else: + v = str(v) + + # *Don't* force a timezone if not passed explicitly; leave it as + # "naive" datetime. + year, month, day, hour, minute, second, tzname = parseDateTime(v, local=0) + + # TODO: look up a real tzinfo object using 'tzname' + # + # Option 1: Use 'timezones' module as global registry:: + # + # from zope.app.timezones import getTimezoneInfo + # tzinfo = getTimezoneInfo(tzname) + # + # Option 2: Use a utility (or perhaps a view, for L10N). + # + # tz_lookup = getUtility(ITimezoneLookup) + # tzinfo = tz_lookup(tzname) + # + return datetime(year, month, day, hour, minute, second, + # tzinfo=tzinfo + ) + +ZOPE_CONVERTERS = [('date', field2date_via_datetimeutils)] + +def registerZopeConverters(): + + for field_type, converter in ZOPE_CONVERTERS: + registerTypeConverter(field_type, converter) diff --git a/fileresource.py b/fileresource.py new file mode 100644 index 0000000..62cbf24 --- /dev/null +++ b/fileresource.py @@ -0,0 +1,49 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Browser File Resource + +$Id$ +""" +import os +import posixpath + +from time import time + +from zope.app.contenttypes import guess_content_type +from zope.datetime import rfc1123_date + + +class File(object): + + def __init__(self, path, name): + self.path = path + + f = open(path, 'rb') + data = f.read() + f.close() + self.content_type, enc = guess_content_type(path, data) + self.__name__ = name + self.lmt = float(os.path.getmtime(path)) or time() + self.lmh = rfc1123_date(self.lmt) + + +class Image(File): + """Image objects stored in external files.""" + + def __init__(self, path, name): + super(Image, self).__init__(path, name) + if self.content_type in (None, 'application/octet-stream'): + ext = os.path.splitext(self.path)[1] + if ext: + self.content_type = 'image/%s' % ext[1:]