2
# Twisted, the Framework of Your Internet
3
# Copyright (C) 2001 Matthew W. Lefkowitz
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.
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.
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
18
"""I deal with static resources.
23
import os, string, stat
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
46
class Data(resource.Resource):
48
This is a static, in-memory resource.
51
def __init__(self, data, type):
52
resource.Resource.__init__(self)
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))
63
class DirectoryListing(widgets.StreamWidget):
64
def __init__(self, pathname):
67
def getTitle(self, request):
68
return "Directory Listing For %s" % request.path
70
def stream(self, write, request):
72
directory = os.listdir(self.path)
74
for path in directory:
75
write('<LI><A HREF="%s">%s</a>' % (urllib.quote(path, "/:"), path))
78
class File(resource.Resource, coil.Configurable, styles.Versioned):
80
File is a resource that represents a plain non-interpreted file.
81
It's constructor takes a file path.
85
".exe": "application/x-executable",
87
".gtar": "application/x-gtar",
88
".java": "text/plain",
89
".jpeg": "image/jpeg",
91
".lisp": "text/x-lisp",
94
".pdf": "application/x-pdf",
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",
107
".gz" : "application/x-gzip",
108
".bz2": "appliation/x-bzip2"
113
indexNames = ["index", "index.html"]
117
persistenceVersion = 1
119
def upgradeToVersion1(self):
120
if hasattr(self, 'indexName'):
121
self.indexNames = [self.indexName]
126
configTypes = {'path': types.StringType,
127
'execCGI': 'boolean',
128
'execEPY': 'boolean'}
130
configName = 'Web Filesystem Access'
132
def config_path(self, path):
135
def config_execCGI(self, allowed):
138
self.processors['.cgi'] = twcgi.CGIScript
140
if self.processors.has_key('.cgi'):
141
del self.processors['.cgi']
143
def config_execEPY(self, allowed):
146
self.processors['.epy'] = script.PythonScript
148
if self.processors.has_key('.epy'):
149
del self.processors['.epy']
152
def getConfiguration(self):
153
return {'path': self.path,
154
'execCGI': self.processors.has_key('.cgi'),
155
'execEPY': self.processors.has_key('.epy')}
157
def configInit(self, container, name):
158
self.__init__("nowhere/nohow")
160
### End Configuration
162
def __init__(self, path):
163
"""Create a file with the given path.
165
resource.Resource.__init__(self)
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))
178
def getChild(self, path, request):
179
"""See twisted.web.Resource.getChild.
182
return error.NoResource()
184
for path in self.indexNames:
185
# This next step is so urls like
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
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)
200
return processor(newpath)
202
f.processors = self.processors
203
f.indexNames = self.indexNames[:]
207
def render(self, request):
208
"You know what you doing."
210
mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime =\
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)
221
request.setHeader('accept-ranges','bytes')
222
request.setHeader('last-modified', server.date_time_string(mtime))
224
request.setHeader('content-type', self.type)
226
request.setHeader('content-encoding', self.encoding)
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)
237
f = open(self.path,'rb')
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],'-')
253
request.setResponseCode(http.PARTIAL_CONTENT)
254
request.setHeader('content-range',"bytes %s-%s/%s " % (
255
str(start), str(end), str(size)))
257
request.setHeader('content-length',size)
259
traceback.print_exc(file=log.logfile)
261
if request.method == 'HEAD':
265
FileTransfer(f, size, request)
266
# and make sure the connection doesn't get closed
267
return server.NOT_DONE_YET
269
coil.registerClass(File)
271
class FileTransfer(pb.Viewable):
273
A class to represent the transfer of a file over the network.
276
def __init__(self, file, size, request):
279
self.request = request
280
request.registerProducer(self, 0)
282
def resumeProducing(self):
285
self.request.write(self.file.read(abstract.FileDescriptor.bufferSize))
286
if self.file.tell() == self.size:
287
self.request.finish()
290
def pauseProducing(self):
293
def stopProducing(self):
297
# Remotely relay producer interface.
299
def view_resumeProducing(self, issuer):
300
self.resumeProducing()
302
def view_pauseProducing(self, issuer):
303
self.pauseProducing()
305
def view_stopProducing(self, issuer):
309
synchronized = ['resumeProducing', 'stopProducing']
311
threadable.synchronize(FileTransfer)