1 __all__ = []
2
3 import time
4 import sys
5 import cgitb
6 import binascii
7 from wsgitools.filters import CloseableList, CloseableIterator
8 try:
9 import io
10 except ImportError:
11 try:
12 import cStringIO as io
13 except ImportError:
14 import StringIO as io
15 try:
16 next
17 except NameError:
20
21 __all__.append("SubdirMiddleware")
23 """Middleware choosing wsgi applications based on a dict."""
24 - def __init__(self, default, mapping={}):
25 """
26 @type default: wsgi app
27 @type mapping: {str: wsgi app}
28 """
29 self.default = default
30 self.mapping = mapping
31 - def __call__(self, environ, start_response):
32 """wsgi interface
33 @type environ: {str: str}
34 @rtype: gen([str])
35 """
36 assert isinstance(environ, dict)
37 app = None
38 script = environ["PATH_INFO"]
39 path_info = ""
40 while '/' in script:
41 if script in self.mapping:
42 app = self.mapping[script]
43 break
44 script, tail = script.rsplit('/', 1)
45 path_info = "/%s%s" % (tail, path_info)
46 if app is None:
47 app = self.mapping.get(script, None)
48 if app is None:
49 app = self.default
50 environ["SCRIPT_NAME"] += script
51 environ["PATH_INFO"] = path_info
52 return app(environ, start_response)
53
54 __all__.append("NoWriteCallableMiddleware")
56 """This middleware wraps a wsgi application that needs the return value of
57 C{start_response} function to a wsgi application that doesn't need one by
58 writing the data to a C{StringIO} and then making it be the first result
59 element."""
61 """Wraps wsgi application app."""
62 self.app = app
63 - def __call__(self, environ, start_response):
64 """wsgi interface
65 @type environ: {str, str}
66 @rtype: gen([str])
67 """
68 assert isinstance(environ, dict)
69 todo = []
70 def modified_start_response(status, headers, exc_info=None):
71 assert isinstance(status, str)
72 assert isinstance(headers, list)
73 if exc_info is not None:
74 todo.append(None)
75 return start_response(status, headers, exc_info)
76 else:
77 sio = io.StringIO()
78 todo.append((status, headers, sio))
79 return sio.write
80
81 ret = self.app(environ, modified_start_response)
82 assert hasattr(ret, "__iter__")
83 assert len(todo) == 1
84
85 if todo[0] is None:
86 return ret
87
88 status, headers, data = todo[0]
89 data = data.getvalue()
90
91 if not data:
92 start_response(status, headers)
93 return ret
94
95 if isinstance(ret, list):
96 ret.insert(0, data)
97 start_response(status, headers)
98 return ret
99
100 ret = iter(ret)
101 stopped = False
102 try:
103 first = next(ret)
104 except StopIteration:
105 stopped = True
106
107 start_response(status, headers)
108
109 if stopped:
110 return CloseableList(getattr(ret, "close", None), (data,))
111
112 return CloseableIterator(getattr(ret, "close", None),
113 (data, first), ret)
114
115 __all__.append("ContentLengthMiddleware")
117 """Guesses the content length header if possible.
118 @note: The application used must not use the C{write} callable returned by
119 C{start_response}."""
120 - def __init__(self, app, maxstore=0):
121 """Wraps wsgi application app. It can also store the first result bytes
122 to possibly return a list of strings which will make guessing the size
123 of iterators possible. At most maxstore bytes will be accumulated.
124 Please note that a value larger than 0 will violate the wsgi standard.
125 The magical value C{()} will make it always gather all data.
126 @type maxstore: int or ()
127 """
128 self.app = app
129 self.maxstore = maxstore
130 - def __call__(self, environ, start_response):
131 """wsgi interface"""
132 assert isinstance(environ, dict)
133 todo = []
134 def modified_start_response(status, headers, exc_info=None):
135 assert isinstance(status, str)
136 assert isinstance(headers, list)
137 if (exc_info is not None or
138 [v for h, v in headers if h.lower() == "content-length"]):
139 todo[:] = (None,)
140 return start_response(status, headers, exc_info)
141 else:
142 todo[:] = ((status, headers),)
143 def raise_not_imp(*args):
144 raise NotImplementedError
145 return raise_not_imp
146
147 ret = self.app(environ, modified_start_response)
148 assert hasattr(ret, "__iter__")
149
150 if todo and todo[0] is None:
151
152 return ret
153
154 if isinstance(ret, list):
155
156 status, headers = todo[0]
157 length = sum(map(len, ret))
158 headers.append(("Content-length", str(length)))
159 start_response(status, headers)
160 return ret
161
162 ret = iter(ret)
163 stopped = False
164 data = CloseableList(getattr(ret, "close", None))
165 length = 0
166 try:
167 data.append(next(ret))
168 length += len(data[-1])
169 except StopIteration:
170 stopped = True
171
172 status, headers = todo[0]
173
174 while (not stopped) and length < self.maxstore:
175 try:
176 data.append(next(ret))
177 length += len(data[-1])
178 except StopIteration:
179 stopped = True
180
181 if stopped:
182
183 headers.append(("Content-length", str(length)))
184 start_response(status, headers)
185 return data
186
187
188 start_response(status, headers)
189
190 return CloseableIterator(getattr(ret, "close", None), data, ret)
191
193 if environ["REQUEST_METHOD"] != "GET":
194 return False
195 return True
196
198 if environ.get("HTTP_CACHE_CONTROL", "") == "max-age=0":
199 return False
200 return True
201
202 __all__.append("CachingMiddleware")
204 """Caches reponses to requests based on C{SCRIPT_NAME}, C{PATH_INFO} and
205 C{QUERY_STRING}."""
207 """
208 @param app: is a wsgi application to be cached.
209 @type maxage: int
210 @param maxage: is the number of seconds a reponse may be cached.
211 @param storable: is a predicate that determines whether the response
212 may be cached at all based on the C{environ} dict.
213 @param cacheable: is a predicate that determines whether this request
214 invalidates the cache."""
215 self.app = app
216 self.maxage = maxage
217 self.storable = storable
218 self.cacheable = cacheable
219 self.cache = {}
220 - def __call__(self, environ, start_response):
221 """wsgi interface
222 @type environ: {str: str}
223 """
224 assert isinstance(environ, dict)
225 if not self.storable(environ):
226 return self.app(environ, start_response)
227 path = environ.get("SCRIPT_NAME", "/")
228 path += environ.get("PATH_INFO", '')
229 path += "?" + environ.get("QUERY_STRING", "")
230 if self.cacheable(environ) and path in self.cache:
231 if self.cache[path][0] + self.maxage >= time.time():
232 start_response(self.cache[path][1], self.cache[path][2])
233 return self.cache[path][3]
234 else:
235 del self.cache[path]
236 cache_object = [time.time(), "", [], []]
237 def modified_start_respesponse(status, headers, exc_info=None):
238 assert isinstance(status, str)
239 assert isinstance(headers, list)
240 if exc_info is not None:
241 return self.app(status, headers, exc_info)
242 cache_object[1] = status
243 cache_object[2] = headers
244 write = start_response(status, headers)
245 def modified_write(data):
246 cache_object[3].append(data)
247 write(data)
248 return modified_write
249
250 ret = self.app(environ, modified_start_respesponse)
251 assert hasattr(ret, "__iter__")
252
253 if isinstance(ret, list):
254 cache_object[3].extend(ret)
255 self.cache[path] = cache_object
256 return ret
257 def pass_through():
258 for data in ret:
259 cache_object[3].append(data)
260 yield data
261 self.cache[path] = cache_object
262 return CloseableIterator(getattr(ret, "close", None), pass_through())
263
264 __all__.append("DictAuthChecker")
266 """Verifies usernames and passwords by looking them up in a dict."""
268 """
269 @type users: {str: str}
270 @param users: is a dict mapping usernames to password."""
271 self.users = users
272 - def __call__(self, username, password):
273 """check_function interface taking username and password and resulting
274 in a bool.
275 @type username: str
276 @type password: str
277 @rtype: bool
278 """
279 return username in self.users and self.users[username] == password
280
281 __all__.append("BasicAuthMiddleware")
283 """Middleware implementing HTTP Basic Auth."""
284 - def __init__(self, app, check_function, realm='www', app401=None):
285 """
286 @param app: is a WSGI application.
287 @param check_function: is a function taking three arguments username,
288 password and environment returning a bool indicating whether the
289 request may is allowed. The older interface of taking only the
290 first two arguments is still supported via catching a
291 C{TypeError}.
292 @type realm: str
293 @param app401: is an optional WSGI application to be used for error
294 messages
295 """
296 self.app = app
297 self.check_function = check_function
298 self.realm = realm
299 self.app401 = app401
300
301 - def __call__(self, environ, start_response):
302 """wsgi interface
303 @type environ: {str: str}
304 """
305 assert isinstance(environ, dict)
306 auth = environ.get("HTTP_AUTHORIZATION")
307 if not auth or ' ' not in auth:
308 return self.authorization_required(environ, start_response)
309 auth_type, enc_auth_info = auth.split(None, 1)
310 try:
311 auth_info = enc_auth_info.decode("base64")
312 except binascii.Error:
313 return self.authorization_required(environ, start_response)
314 if auth_type.lower() != "basic" or ':' not in auth_info:
315 return self.authorization_required(environ, start_response)
316 username, password = auth_info.split(':', 1)
317 try:
318 result = self.check_function(username, password, environ)
319 except TypeError:
320 result = self.check_function(username, password)
321 if result:
322 environ["REMOTE_USER"] = username
323 return self.app(environ, start_response)
324 return self.authorization_required(environ, start_response)
325
327 """wsgi application for indicating authorization is required.
328 @type environ: {str: str}
329 """
330 if self.app401 is None:
331 status = "401 Authorization required"
332 html = "<html><head><title>Authorization required</title></head>" \
333 "<body><h1>Authorization required</h1></body></html>\n"
334 headers = [('Content-type', 'text/html'),
335 ('WWW-Authenticate', 'Basic realm="%s"' % self.realm),
336 ("Content-length", str(len(html)))]
337 start_response(status, headers)
338 if environ["REQUEST_METHOD"].upper() == "HEAD":
339 return []
340 return [html]
341 return self.app401(environ, start_response)
342
343 __all__.append("TracebackMiddleware")
345 """In case the application throws an exception this middleware will show an
346 html-formatted traceback using C{cgitb}."""
348 """app is the wsgi application to proxy."""
349 self.app = app
350 - def __call__(self, environ, start_response):
351 """wsgi interface
352 @type environ: {str: str}
353 """
354 try:
355 assert isinstance(environ, dict)
356 ret = self.app(environ, start_response)
357 assert hasattr(ret, "__iter__")
358
359 if isinstance(ret, list):
360 return ret
361
362
363 ret = iter(ret)
364 try:
365 first = next(ret)
366 except StopIteration:
367 return CloseableList(getattr(ret, "close", None), [])
368 return CloseableIterator(getattr(ret, "close", None), [first], ret)
369 except:
370 exc_info = sys.exc_info()
371 data = cgitb.html(exc_info)
372 start_response("200 OK", [("Content-type", "text/html"),
373 ("Content-length", str(len(data)))])
374 if environ["REQUEST_METHOD"].upper() == "HEAD":
375 return []
376 return [data]
377