1 """Functions for builtin CherryPy tools."""
2
3 import logging
4
5 try:
6
7 from hashlib import md5
8 except ImportError:
9 from md5 import new as md5
10
11 import re
12
13 import cherrypy
14 from cherrypy.lib import http as _http
15
16
17
18
74
100
101
102
103
104 -def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
105 scheme='X-Forwarded-Proto'):
106 """Change the base URL (scheme://host[:port][/path]).
107
108 For running a CP server behind Apache, lighttpd, or other HTTP server.
109
110 If you want the new request.base to include path info (not just the host),
111 you must explicitly set base to the full base path, and ALSO set 'local'
112 to '', so that the X-Forwarded-Host request header (which never includes
113 path info) does not override it.
114
115 cherrypy.request.remote.ip (the IP address of the client) will be
116 rewritten if the header specified by the 'remote' arg is valid.
117 By default, 'remote' is set to 'X-Forwarded-For'. If you do not
118 want to rewrite remote.ip, set the 'remote' arg to an empty string.
119 """
120
121 request = cherrypy.request
122
123 if scheme:
124 s = request.headers.get(scheme, None)
125 if s == 'on' and 'ssl' in scheme.lower():
126
127 scheme = 'https'
128 else:
129
130 scheme = s
131 if not scheme:
132 scheme = request.base[:request.base.find("://")]
133
134 if local:
135 base = request.headers.get(local, base)
136 if not base:
137 port = cherrypy.request.local.port
138 if port == 80:
139 base = '127.0.0.1'
140 else:
141 base = '127.0.0.1:%s' % port
142
143 if base.find("://") == -1:
144
145 base = scheme + "://" + base
146
147 request.base = base
148
149 if remote:
150 xff = request.headers.get(remote)
151 if xff:
152 if remote == 'X-Forwarded-For':
153
154 xff = xff.split(',')[-1].strip()
155 request.remote.ip = xff
156
157
159 """Delete request headers whose field names are included in 'headers'.
160
161 This is a useful tool for working behind certain HTTP servers;
162 for example, Apache duplicates the work that CP does for 'Range'
163 headers, and will doubly-truncate the response.
164 """
165 request = cherrypy.request
166 for name in headers:
167 if name in request.headers:
168 del request.headers[name]
169
170
175 response_headers.failsafe = True
176
177
178 -def referer(pattern, accept=True, accept_missing=False, error=403,
179 message='Forbidden Referer header.'):
180 """Raise HTTPError if Referer header does/does not match the given pattern.
181
182 pattern: a regular expression pattern to test against the Referer.
183 accept: if True, the Referer must match the pattern; if False,
184 the Referer must NOT match the pattern.
185 accept_missing: if True, permit requests with no Referer header.
186 error: the HTTP error code to return to the client on failure.
187 message: a string to include in the response body on failure.
188 """
189 try:
190 match = bool(re.match(pattern, cherrypy.request.headers['Referer']))
191 if accept == match:
192 return
193 except KeyError:
194 if accept_missing:
195 return
196
197 raise cherrypy.HTTPError(error, message)
198
199
201 """Assert that the user is logged in."""
202
203 session_key = "username"
204
207
209 """Provide a temporary user name for anonymous users."""
210 pass
211
214
217
220
221 - def login_screen(self, from_page='..', username='', error_msg='', **kwargs):
222 return """<html><body>
223 Message: %(error_msg)s
224 <form method="post" action="do_login">
225 Login: <input type="text" name="username" value="%(username)s" size="10" /><br />
226 Password: <input type="password" name="password" size="10" /><br />
227 <input type="hidden" name="from_page" value="%(from_page)s" /><br />
228 <input type="submit" />
229 </form>
230 </body></html>""" % {'from_page': from_page, 'username': username,
231 'error_msg': error_msg}
232
233 - def do_login(self, username, password, from_page='..', **kwargs):
247
248 - def do_logout(self, from_page='..', **kwargs):
257
274
286
287
293 session_auth.__doc__ = """Session authentication hook.
294
295 Any attribute of the SessionAuth class may be overridden via a keyword arg
296 to this function:
297
298 """ + "\n".join(["%s: %s" % (k, type(getattr(SessionAuth, k)).__name__)
299 for k in dir(SessionAuth) if not k.startswith("__")])
300
301
303 """Write the last error's traceback to the cherrypy error log."""
304 cherrypy.log("", "HTTP", severity=severity, traceback=True)
305
310
329
336
353
355 """Wrap response.body in a generator that recursively iterates over body.
356
357 This allows cherrypy.response.body to consist of 'nested generators';
358 that is, a set of generators that yield generators.
359 """
360 import types
361 def flattener(input):
362 for x in input:
363 if not isinstance(x, types.GeneratorType):
364 yield x
365 else:
366 for y in flattener(x):
367 yield y
368 response = cherrypy.response
369 response.body = flattener(response.body)
370
371
373 """Return the client's preferred media-type (from the given Content-Types).
374
375 If 'media' is None (the default), no test will be performed.
376
377 If 'media' is provided, it should be the Content-Type value (as a string)
378 or values (as a list or tuple of strings) which the current request
379 can emit. The client's acceptable media ranges (as declared in the
380 Accept request header) will be matched in order to these Content-Type
381 values; the first such string is returned. That is, the return value
382 will always be one of the strings provided in the 'media' arg (or None
383 if 'media' is None).
384
385 If no match is found, then HTTPError 406 (Not Acceptable) is raised.
386 Note that most web browsers send */* as a (low-quality) acceptable
387 media range, which should match any Content-Type. In addition, "...if
388 no Accept header field is present, then it is assumed that the client
389 accepts all media types."
390
391 Matching types are checked in order of client preference first,
392 and then in the order of the given 'media' values.
393
394 Note that this function does not honor accept-params (other than "q").
395 """
396 if not media:
397 return
398 if isinstance(media, basestring):
399 media = [media]
400
401
402
403 ranges = cherrypy.request.headers.elements('Accept')
404 if not ranges:
405
406 return media[0]
407 else:
408
409 for element in ranges:
410 if element.qvalue > 0:
411 if element.value == "*/*":
412
413 return media[0]
414 elif element.value.endswith("/*"):
415
416 mtype = element.value[:-1]
417 for m in media:
418 if m.startswith(mtype):
419 return m
420 else:
421
422 if element.value in media:
423 return element.value
424
425
426 ah = cherrypy.request.headers.get('Accept')
427 if ah is None:
428 msg = "Your client did not send an Accept header."
429 else:
430 msg = "Your client sent this Accept header: %s." % ah
431 msg += (" But this resource only emits these media types: %s." %
432 ", ".join(media))
433 raise cherrypy.HTTPError(406, msg)
434