~launchpad-pqm/launchpad/devel

8687.15.18 by Karl Fogel
Add the copyright header block to files under lib/canonical/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
3
4
__metaclass__ = type
5
6
__all__ = [
7
    'ExportedFolder',
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
8
    'ExportedImageFolder',
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
9
    ]
10
11
import errno
12
import os
13
import re
5369.1.14 by Mark Shuttleworth
Remove unnecessary imports
14
import time
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
15
11666.2.1 by William Grant
Replace deprecated zope.app.publisher imports with the new locations, and only include z.a.p.xmlrpc's ZCML. This prevents the regstration of a 'manage' view for every object.
16
from zope.browserresource.file import setCacheControl
6061.2.5 by Maris Fogels
Fixed many Zope DeprecationWarnings.
17
from zope.contenttype import guess_content_type
18
from zope.datetime import rfc1123_date
11666.2.1 by William Grant
Replace deprecated zope.app.publisher imports with the new locations, and only include z.a.p.xmlrpc's ZCML. This prevents the regstration of a 'manage' view for every object.
19
from zope.interface import implements
14612.2.1 by William Grant
format-imports on lib/. So many imports.
20
from zope.publisher.interfaces import NotFound
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
21
from zope.publisher.interfaces.browser import IBrowserPublisher
22
23
24
class File:
11666.2.1 by William Grant
Replace deprecated zope.app.publisher imports with the new locations, and only include z.a.p.xmlrpc's ZCML. This prevents the regstration of a 'manage' view for every object.
25
    # Copied from zope.browserresource.file, which
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
26
    # unbelievably throws away the file data, and isn't
27
    # useful extensible.
28
    #
29
    def __init__(self, path, name):
30
        self.path = path
31
32
        f = open(path, 'rb')
33
        self.data = f.read()
34
        f.close()
35
        self.content_type, enc = guess_content_type(path, self.data)
36
        self.__name__ = name
5369.1.14 by Mark Shuttleworth
Remove unnecessary imports
37
        self.lmt = float(os.path.getmtime(path)) or time.time()
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
38
        self.lmh = rfc1123_date(self.lmt)
39
40
41
class ExportedFolder:
42
    """View that gives access to the files in a folder.
43
44
    The URL to the folder can start with an optional path step like
45
    /revNNN/ where NNN is one or more digits.  This path step will
46
    be ignored.  It is useful for having a different path for
47
    all resources being served, to ensure that we don't use cached
48
    files in browsers.
6753.5.1 by Diogo Matsubara
merge the twozero tour again
49
50
    By default, subdirectories are not exported. Set export_subdirectories
51
    to True to change this.
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
52
    """
53
54
    implements(IBrowserPublisher)
55
56
    rev_part_re = re.compile('rev\d+$')
57
6753.5.1 by Diogo Matsubara
merge the twozero tour again
58
    export_subdirectories = False
59
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
60
    def __init__(self, context, request):
61
        """Initialize with context and request."""
62
        self.context = context
63
        self.request = request
64
        self.names = []
65
66
    def __call__(self):
67
        names = list(self.names)
68
        if names and self.rev_part_re.match(names[0]):
69
            # We have a /revNNN/ path step, so remove it.
70
            names = names[1:]
71
72
        if not names:
7146.1.8 by Maris Fogels
Fixed the broken folder traversal.
73
            # Just the root directory, so make this a 404.
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
74
            raise NotFound(self, '')
6753.5.1 by Diogo Matsubara
merge the twozero tour again
75
        elif len(names) > 1 and not self.export_subdirectories:
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
76
            # Too many path elements, so make this a 404.
77
            raise NotFound(self, self.names[-1])
78
        else:
79
            # Actually serve up the resource.
7146.1.9 by Maris Fogels
Added a small docstring, to appease the paranoid.
80
            # Don't worry about serving  up stuff like ../../../etc/passwd,
7146.1.10 by Maris Fogels
Clarified some comment text.
81
            # because the Zope name traversal will sanitize './' and '../'
82
            # before setting the value of self.names.
6753.5.1 by Diogo Matsubara
merge the twozero tour again
83
            return self.prepareDataForServing(
84
                os.path.join(self.folder, *names))
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
85
6753.5.1 by Diogo Matsubara
merge the twozero tour again
86
    def prepareDataForServing(self, filename):
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
87
        """Set the response headers and return the data for this resource."""
6753.5.1 by Diogo Matsubara
merge the twozero tour again
88
        name = os.path.basename(filename)
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
89
        try:
90
            fileobj = File(filename, name)
91
        except IOError, ioerror:
7146.1.8 by Maris Fogels
Fixed the broken folder traversal.
92
            expected = (errno.ENOENT, errno.EISDIR, errno.ENOTDIR)
93
            if ioerror.errno in expected:
6574.1.9 by Francis J. Lacoste
Review comments.
94
                # No such file or is a directory.
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
95
                raise NotFound(self, name)
96
            else:
97
                # Some other IOError that we're not expecting.
98
                raise
99
100
        # TODO: Set an appropriate charset too.  There may be zope code we
101
        #       can reuse for this.
102
        response = self.request.response
103
        response.setHeader('Content-Type', fileobj.content_type)
104
        response.setHeader('Last-Modified', fileobj.lmh)
105
        setCacheControl(response)
106
        return fileobj.data
107
108
    # The following two zope methods publishTraverse and browserDefault
109
    # allow this view class to take control of traversal from this point
110
    # onwards.  Traversed names just end up in self.names.
111
112
    def publishTraverse(self, request, name):
113
        """Traverse to the given name."""
6753.5.1 by Diogo Matsubara
merge the twozero tour again
114
        # The two following constraints are enforced by the publisher.
115
        assert os.path.sep not in name, (
116
            'traversed name contains os.path.sep: %s' % name)
117
        assert name != '..', 'traversing to ..'
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
118
        self.names.append(name)
119
        return self
120
121
    def browserDefault(self, request):
122
        return self, ()
123
124
    @property
125
    def folder(self):
126
        raise (
127
            NotImplementedError,
128
            'Your subclass of ExportedFolder should have its own folder.')
129
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
130
131
class ExportedImageFolder(ExportedFolder):
6574.1.7 by Francis J. Lacoste
Improved docstring.
132
    """ExportedFolder subclass for directory of images.
133
134
    It supports serving image files without their extension (e.g. "image1.gif"
135
    can be served as "image1".
136
    """
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
137
138
139
    # The extensions we consider.
6574.1.9 by Francis J. Lacoste
Review comments.
140
    image_extensions = ('.png', '.gif')
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
141
6753.5.1 by Diogo Matsubara
merge the twozero tour again
142
    def prepareDataForServing(self, filename):
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
143
        """Serve files without their extension.
144
145
        If the requested name doesn't exist but a file exists which has
146
        the same base name and has an image extension, it will be served.
147
        """
6753.5.1 by Diogo Matsubara
merge the twozero tour again
148
        root, ext = os.path.splitext(filename)
149
        if ext == '' and not os.path.exists(root):
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
150
            for image_ext in self.image_extensions:
6753.5.1 by Diogo Matsubara
merge the twozero tour again
151
                if os.path.exists(root + image_ext):
152
                    filename = filename + image_ext
6574.1.4 by Francis J. Lacoste
Add doctest for ExportedFolder. Added an ExportedImageFolder.
153
                    break
6753.5.1 by Diogo Matsubara
merge the twozero tour again
154
        return super(
155
            ExportedImageFolder, self).prepareDataForServing(filename)