~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web/static.py

  • Committer: Bazaar Package Importer
  • Author(s): Moshe Zadka
  • Date: 2002-03-08 07:14:16 UTC
  • Revision ID: james.westby@ubuntu.com-20020308071416-oxvuw76tpcpi5v1q
Tags: upstream-0.15.5
ImportĀ upstreamĀ versionĀ 0.15.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
# Twisted, the Framework of Your Internet
 
3
# Copyright (C) 2001 Matthew W. Lefkowitz
 
4
#
 
5
# This library is free software; you can redistribute it and/or
 
6
# modify it under the terms of version 2.1 of the GNU Lesser General Public
 
7
# License as published by the Free Software Foundation.
 
8
#
 
9
# This library is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
# Lesser General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU Lesser General Public
 
15
# License along with this library; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
"""I deal with static resources.
 
19
"""
 
20
 
 
21
 
 
22
# System Imports
 
23
import os, string, stat
 
24
import cStringIO
 
25
import traceback
 
26
import types
 
27
StringIO = cStringIO
 
28
del cStringIO
 
29
import urllib
 
30
 
 
31
# Sibling Imports
 
32
import server
 
33
import error
 
34
import resource
 
35
import html
 
36
import widgets
 
37
 
 
38
# Twisted Imports
 
39
from twisted.protocols import http
 
40
from twisted.python import threadable, log
 
41
from twisted.internet import abstract
 
42
from twisted.spread import pb
 
43
from twisted.manhole import coil
 
44
from twisted.persisted import styles
 
45
 
 
46
class Data(resource.Resource):
 
47
    """
 
48
    This is a static, in-memory resource.
 
49
    """
 
50
 
 
51
    def __init__(self, data, type):
 
52
        resource.Resource.__init__(self)
 
53
        self.data = data
 
54
        self.type = type
 
55
 
 
56
    def render(self, request):
 
57
        request.setHeader("content-type", self.type)
 
58
        if request.method == "HEAD":
 
59
            request.setHeader("content-length", len(self.data))
 
60
            return ''
 
61
        return self.data
 
62
 
 
63
class DirectoryListing(widgets.StreamWidget):
 
64
    def __init__(self, pathname):
 
65
        self.path = pathname
 
66
 
 
67
    def getTitle(self, request):
 
68
        return "Directory Listing For %s" % request.path
 
69
 
 
70
    def stream(self, write, request):
 
71
        write("<UL>\n")
 
72
        directory = os.listdir(self.path)
 
73
        directory.sort()
 
74
        for path in directory:
 
75
            write('<LI><A HREF="%s">%s</a>' % (urllib.quote(path, "/:"), path))
 
76
        write("</UL>\n")
 
77
 
 
78
class File(resource.Resource, coil.Configurable, styles.Versioned):
 
79
    """
 
80
    File is a resource that represents a plain non-interpreted file.
 
81
    It's constructor takes a file path.
 
82
    """
 
83
    contentTypes = {
 
84
        ".css": "text/css",
 
85
        ".exe": "application/x-executable",
 
86
        ".gif": "image/gif",
 
87
        ".gtar": "application/x-gtar",
 
88
        ".java": "text/plain",
 
89
        ".jpeg": "image/jpeg",
 
90
        ".jpg": "image/jpeg",
 
91
        ".lisp": "text/x-lisp",
 
92
        ".mp3":  "audio/mpeg",
 
93
        ".oz": "text/x-oz",
 
94
        ".pdf": "application/x-pdf",
 
95
        ".png": "image/png",
 
96
        ".py": "text/x-python",
 
97
        ".swf": "appplication/x-shockwave-flash",
 
98
        ".tar": "application/x-tar",
 
99
        ".tgz": "application/x-gtar",
 
100
        ".tif": "image/tiff",
 
101
        ".tiff": "image/tiff",
 
102
        ".txt": "text/plain",
 
103
        ".zip": "application/x-zip",
 
104
        }
 
105
 
 
106
    contentEncodings = {
 
107
        ".gz" : "application/x-gzip",
 
108
        ".bz2": "appliation/x-bzip2"
 
109
        }
 
110
 
 
111
    processors = {}
 
112
 
 
113
    indexNames = ["index", "index.html"]
 
114
 
 
115
    ### Versioning
 
116
 
 
117
    persistenceVersion = 1
 
118
 
 
119
    def upgradeToVersion1(self):
 
120
        if hasattr(self, 'indexName'):
 
121
            self.indexNames = [self.indexName]
 
122
            del self.indexName
 
123
 
 
124
    ### Configuration
 
125
 
 
126
    configTypes = {'path': types.StringType,
 
127
                   'execCGI': 'boolean',
 
128
                   'execEPY': 'boolean'}
 
129
 
 
130
    configName = 'Web Filesystem Access'
 
131
 
 
132
    def config_path(self, path):
 
133
        self.path = path
 
134
 
 
135
    def config_execCGI(self, allowed):
 
136
        if allowed:
 
137
            import twcgi
 
138
            self.processors['.cgi'] = twcgi.CGIScript
 
139
        else:
 
140
            if self.processors.has_key('.cgi'):
 
141
                del self.processors['.cgi']
 
142
 
 
143
    def config_execEPY(self, allowed):
 
144
        if allowed:
 
145
            import script
 
146
            self.processors['.epy'] = script.PythonScript
 
147
        else:
 
148
            if self.processors.has_key('.epy'):
 
149
                del self.processors['.epy']
 
150
 
 
151
 
 
152
    def getConfiguration(self):
 
153
        return {'path': self.path,
 
154
                'execCGI': self.processors.has_key('.cgi'),
 
155
                'execEPY': self.processors.has_key('.epy')}
 
156
 
 
157
    def configInit(self, container, name):
 
158
        self.__init__("nowhere/nohow")
 
159
 
 
160
    ### End Configuration
 
161
 
 
162
    def __init__(self, path):
 
163
        """Create a file with the given path.
 
164
        """
 
165
        resource.Resource.__init__(self)
 
166
        self.path = path
 
167
        # Remove the dots from the path to split
 
168
        p = os.path.abspath(path)
 
169
        p, ext = os.path.splitext(p)
 
170
        self.encoding = self.contentEncodings.get(string.lower(ext))
 
171
        # if there was an encoding, get the next suffix
 
172
        if self.encoding is not None:
 
173
            p, ext = os.path.splitext(p)
 
174
        self.type = self.contentTypes.get(string.lower(ext))
 
175
        self.processors = {}
 
176
 
 
177
 
 
178
    def getChild(self, path, request):
 
179
        """See twisted.web.Resource.getChild.
 
180
        """
 
181
        if path == '..':
 
182
            return error.NoResource()
 
183
        if path == '':
 
184
            for path in self.indexNames:
 
185
                # This next step is so urls like
 
186
                #     /foo/bar/baz/
 
187
                # will be represented (internally) as
 
188
                #     ['foo','bar','baz','index.qux']
 
189
                # So that request.childLink() will work correctly.
 
190
                if os.path.exists(os.path.join(self.path, path)):
 
191
                    request.prepath[-1] = path
 
192
                    break
 
193
            else:
 
194
                return widgets.WidgetPage(DirectoryListing(self.path))
 
195
        newpath = os.path.join(self.path, path)
 
196
        # forgive me, oh lord, for I know not what I do
 
197
        p, ext = os.path.splitext(newpath)
 
198
        processor = self.processors.get(ext)
 
199
        if processor:
 
200
            return processor(newpath)
 
201
        f = File(newpath)
 
202
        f.processors = self.processors
 
203
        f.indexNames = self.indexNames[:]
 
204
        return f
 
205
 
 
206
 
 
207
    def render(self, request):
 
208
        "You know what you doing."
 
209
        try:
 
210
            mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime =\
 
211
                  os.stat(self.path)
 
212
        except OSError:
 
213
            return error.NoResource().render(request)
 
214
        if stat.S_ISDIR(mode):
 
215
            # tack a '/' on to the response if it's a directory.
 
216
            request.setHeader("location","http://%s%s/" % (
 
217
                request.getHeader("host"),
 
218
                (string.split(request.uri,'?')[0])))
 
219
            request.setResponseCode(http.MOVED_PERMANENTLY)
 
220
            return " "
 
221
        request.setHeader('accept-ranges','bytes')
 
222
        request.setHeader('last-modified', server.date_time_string(mtime))
 
223
        if self.type:
 
224
            request.setHeader('content-type', self.type)
 
225
        if self.encoding:
 
226
            request.setHeader('content-encoding', self.encoding)
 
227
 
 
228
        # caching headers support
 
229
        modified_since = request.getHeader('if-modified-since')
 
230
        if modified_since is not None:
 
231
            # check if file has been modified and if not don't return the file
 
232
            modified_since = server.string_date_time(modified_since)
 
233
            if modified_since >= mtime:
 
234
                request.setResponseCode(http.NOT_MODIFIED)
 
235
                return ''
 
236
 
 
237
        f = open(self.path,'rb')
 
238
        try:
 
239
            range = request.getHeader('range')
 
240
            if range is not None:
 
241
                # This is a request for partial data...
 
242
                bytesrange = string.split(range, '=')
 
243
                assert bytesrange[0] == 'bytes',\
 
244
                       "Syntactically invalid http range header!"
 
245
                start, end = string.split(bytesrange[1],'-')
 
246
                if start:
 
247
                    f.seek(int(start))
 
248
                if end:
 
249
                    end = int(end)
 
250
                    size = end
 
251
                else:
 
252
                    end = size
 
253
                request.setResponseCode(http.PARTIAL_CONTENT)
 
254
                request.setHeader('content-range',"bytes %s-%s/%s " % (
 
255
                    str(start), str(end), str(size)))
 
256
            else:
 
257
                request.setHeader('content-length',size)
 
258
        except:
 
259
            traceback.print_exc(file=log.logfile)
 
260
 
 
261
        if request.method == 'HEAD':
 
262
            return ''
 
263
 
 
264
        # return data
 
265
        FileTransfer(f, size, request)
 
266
        # and make sure the connection doesn't get closed
 
267
        return server.NOT_DONE_YET
 
268
 
 
269
coil.registerClass(File)
 
270
 
 
271
class FileTransfer(pb.Viewable):
 
272
    """
 
273
    A class to represent the transfer of a file over the network.
 
274
    """
 
275
    request = None
 
276
    def __init__(self, file, size, request):
 
277
        self.file = file
 
278
        self.size = size
 
279
        self.request = request
 
280
        request.registerProducer(self, 0)
 
281
 
 
282
    def resumeProducing(self):
 
283
        if not self.request:
 
284
            return
 
285
        self.request.write(self.file.read(abstract.FileDescriptor.bufferSize))
 
286
        if self.file.tell() == self.size:
 
287
            self.request.finish()
 
288
            self.request = None
 
289
 
 
290
    def pauseProducing(self):
 
291
        pass
 
292
 
 
293
    def stopProducing(self):
 
294
        self.file.close()
 
295
        self.request = None
 
296
 
 
297
    # Remotely relay producer interface.
 
298
 
 
299
    def view_resumeProducing(self, issuer):
 
300
        self.resumeProducing()
 
301
 
 
302
    def view_pauseProducing(self, issuer):
 
303
        self.pauseProducing()
 
304
 
 
305
    def view_stopProducing(self, issuer):
 
306
        self.stopProducing()
 
307
 
 
308
 
 
309
    synchronized = ['resumeProducing', 'stopProducing']
 
310
 
 
311
threadable.synchronize(FileTransfer)