1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import string
23 import os
24
25 from twisted.web import resource, server, http
26 from twisted.web import error as weberror, static
27 from twisted.internet import defer, reactor, error, abstract
28 from twisted.python import filepath
29 from twisted.cred import credentials
30
31 from flumotion.configure import configure
32 from flumotion.component import component
33 from flumotion.common import log, messages, errors, netutils
34 from flumotion.component.component import moods
35 from flumotion.component.misc.porter import porterclient
36 from flumotion.component.base import http as httpbase
37 from flumotion.twisted import fdserver
38
39
41 d = static.loadMimeTypes()
42 d['.flv'] = 'video/x-flv'
43 return d
44
45
46
47 -class File(resource.Resource, filepath.FilePath, log.Loggable):
48 contentTypes = loadMimeTypes()
49 defaultType = "application/octet-stream"
50
51 childNotFound = weberror.NoResource("File not found.")
52
53 - def __init__(self, path, httpauth, mimeToResource=None):
54 resource.Resource.__init__(self)
55 filepath.FilePath.__init__(self, path)
56
57 self._httpauth = httpauth
58
59 self._mimeToResource = mimeToResource or {}
60 self._factory = MimedFileFactory(httpauth, self._mimeToResource)
61
63 self.log('getChild: self %r, path %r', self, path)
64
65 if path == '':
66 return self
67
68 self.restat()
69
70 if not self.isdir():
71 return self.childNotFound
72
73 if path:
74 fpath = self.child(path)
75 else:
76 return self.childNotFound
77
78 if not fpath.exists():
79 return self.childNotFound
80
81 return self._factory.create(fpath.path)
82
84 """Open a file and return it."""
85 f = self.open()
86 self.debug("Reading file from FD: %d", f.fileno())
87 return f
88
90 """Return file size."""
91 return self.getsize()
92
98
99 d = self._httpauth.startAuthentication(request)
100 d.addCallback(self.renderAuthenticated, request)
101 d.addCallback(terminateSimpleRequest, request)
102
103 d.addErrback(lambda x: None)
104
105 return server.NOT_DONE_YET
106
108
109
110
111
112
113
114 self.debug('renderAuthenticated request %r' % request)
115
116
117 self.restat()
118
119 ext = os.path.splitext(self.basename())[1].lower()
120 contentType = self.contentTypes.get(ext, self.defaultType)
121
122 if not self.exists():
123 self.debug("Couldn't find resource %s", self.path)
124 return self.childNotFound.render(request)
125
126 if self.isdir():
127 self.debug("%s is a directory, can't be GET", self.path)
128 return self.childNotFound.render(request)
129
130
131
132
133
134
135 request.setHeader('Server', 'Flumotion/%s' % configure.version)
136 request.setHeader('Connection', 'close')
137
138 request.setHeader('Accept-Ranges', 'bytes')
139
140 if contentType:
141 self.debug('content type %r' % contentType)
142 request.setHeader('content-type', contentType)
143
144 try:
145 f = self.openForReading()
146 except IOError, e:
147 import errno
148 if e[0] == errno.EACCES:
149 return weberror.ForbiddenResource().render(request)
150 else:
151 raise
152
153 if request.setLastModified(self.getmtime()) is http.CACHED:
154 return ''
155
156 fileSize = self.getFileSize()
157
158 first = 0
159 last = fileSize - 1
160
161 range = request.getHeader('range')
162 if range is not None:
163
164
165
166 self.log('range request, %r', range)
167 rangeKeyValue = string.split(range, '=')
168 if len(rangeKeyValue) != 2:
169 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
170 return ''
171
172 if rangeKeyValue[0] != 'bytes':
173 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
174 return ''
175
176
177 ranges = rangeKeyValue[1].split(',')[0]
178 l = ranges.split('-')
179 if len(l) != 2:
180 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
181 return ''
182
183 start, end = l
184
185 if start:
186
187 first = int(start)
188 if end:
189 last = int(end)
190 elif end:
191
192 count = int(end)
193
194 if count > fileSize:
195 count = fileSize
196 first = fileSize - count
197 else:
198
199 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
200 return ''
201
202
203
204 request.setResponseCode(http.PARTIAL_CONTENT)
205 request.setHeader('Content-Range', "bytes %d-%d/%d" %
206 (first, last, fileSize))
207
208 if first:
209
210
211 self.debug("Request for range \"%s\" of file, seeking to "
212 "%d of total file size %d", ranges, first, fileSize)
213 f.seek(first)
214
215 self.do_prepareBody(request, f, first, last)
216
217 if request.method == 'HEAD':
218 return ''
219
220 request._transfer = FileTransfer(f, last + 1, request)
221
222 return server.NOT_DONE_YET
223
224 - def do_prepareBody(self, request, f, first, last):
225 """
226 I am called before the body of the response gets written,
227 and after generic header setting has been done.
228
229 I set Content-Length.
230
231 Override me to send additional headers, or to prefix the body
232 with data headers.
233 """
234 request.setHeader("Content-Length", str(last - first + 1))
235
237 """
238 I create File subclasses based on the mime type of the given path.
239 """
240 contentTypes = loadMimeTypes()
241 defaultType = "application/octet-stream"
242
243 - def __init__(self, httpauth, mimeToResource=None):
244 self._httpauth = httpauth
245 self._mimeToResource = mimeToResource or {}
246
248 """
249 Creates and returns an instance of a File subclass based on the mime
250 type/extension of the given path.
251 """
252
253 self.debug("createMimedFile at %r", path)
254 ext = os.path.splitext(path)[1].lower()
255 mimeType = self.contentTypes.get(ext, self.defaultType)
256 klazz = self._mimeToResource.get(mimeType, File)
257 self.debug("mimetype %s, class %r" % (mimeType, klazz))
258 return klazz(path, self._httpauth, mimeToResource=self._mimeToResource)
259
261 """
262 I am a File resource for FLV files.
263 I can handle requests with a 'start' GET parameter.
264 This parameter represents the byte offset from where to start.
265 If it is non-zero, I will output an FLV header so the result is
266 playable.
267 """
268 header = 'FLV\x01\x01\000\000\000\x09\000\000\000\x09'
269
270 - def do_prepareBody(self, request, f, first, last):
271 self.log('do_prepareBody for FLV')
272 length = last - first + 1
273
274
275
276
277 start = int(request.args.get('start', ['0'])[0])
278
279 if first == 0 and start:
280 self.debug('start %d passed, seeking', start)
281 f.seek(start)
282 length = last - start + 1 + len(self.header)
283
284 request.setHeader("Content-Length", str(length))
285
286 if request.method == 'HEAD':
287 return ''
288
289 if first == 0 and start:
290 request.write(self.header)
291
293 """
294 A class to represent the transfer of a file over the network.
295 """
296 request = None
297
298 - def __init__(self, file, size, request):
299 self.file = file
300 self.size = size
301 self.request = request
302 self.written = self.file.tell()
303 self.bytesWritten = 0
304 request.registerProducer(self, 0)
305
321
324
328