1 """Functions for builtin CherryPy tools."""
2
3 import md5
4 import re
5
6 import cherrypy
7 from cherrypy.lib import http as _http
8
9
10
11
67
93
94
95
96
97 -def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
98 scheme='X-Forwarded-Proto'):
99 """Change the base URL (scheme://host[:port][/path]).
100
101 For running a CP server behind Apache, lighttpd, or other HTTP server.
102
103 If you want the new request.base to include path info (not just the host),
104 you must explicitly set base to the full base path, and ALSO set 'local'
105 to '', so that the X-Forwarded-Host request header (which never includes
106 path info) does not override it.
107
108 cherrypy.request.remote.ip (the IP address of the client) will be
109 rewritten if the header specified by the 'remote' arg is valid.
110 By default, 'remote' is set to 'X-Forwarded-For'. If you do not
111 want to rewrite remote.ip, set the 'remote' arg to an empty string.
112 """
113
114 request = cherrypy.request
115
116 if scheme:
117 scheme = request.headers.get(scheme, None)
118 if not scheme:
119 scheme = request.base[:request.base.find("://")]
120
121 if local:
122 base = request.headers.get(local, base)
123 if not base:
124 port = cherrypy.request.local.port
125 if port == 80:
126 base = 'localhost'
127 else:
128 base = 'localhost:%s' % port
129
130 if base.find("://") == -1:
131
132 base = scheme + "://" + base
133
134 request.base = base
135
136 if remote:
137 xff = request.headers.get(remote)
138 if xff:
139 if remote == 'X-Forwarded-For':
140
141 xff = xff.split(',')[-1].strip()
142 request.remote.ip = xff
143
144
146 """Delete request headers whose field names are included in 'headers'.
147
148 This is a useful tool for working behind certain HTTP servers;
149 for example, Apache duplicates the work that CP does for 'Range'
150 headers, and will doubly-truncate the response.
151 """
152 request = cherrypy.request
153 for name in headers:
154 if name in request.headers:
155 del request.headers[name]
156
157
162 response_headers.failsafe = True
163
164
165 -def referer(pattern, accept=True, accept_missing=False, error=403,
166 message='Forbidden Referer header.'):
167 """Raise HTTPError if Referer header does not pass our test.
168
169 pattern: a regular expression pattern to test against the Referer.
170 accept: if True, the Referer must match the pattern; if False,
171 the Referer must NOT match the pattern.
172 accept_missing: if True, permit requests with no Referer header.
173 error: the HTTP error code to return to the client on failure.
174 message: a string to include in the response body on failure.
175 """
176 try:
177 match = bool(re.match(pattern, cherrypy.request.headers['Referer']))
178 if accept == match:
179 return
180 except KeyError:
181 if accept_missing:
182 return
183
184 raise cherrypy.HTTPError(error, message)
185
186
188 """Assert that the user is logged in."""
189
190 session_key = "username"
191
194
196 """Provide a temporary user name for anonymous users."""
197 pass
198
201
204
207
208 - def login_screen(self, from_page='..', username='', error_msg=''):
209 return """<html><body>
210 Message: %(error_msg)s
211 <form method="post" action="do_login">
212 Login: <input type="text" name="username" value="%(username)s" size="10" /><br />
213 Password: <input type="password" name="password" size="10" /><br />
214 <input type="hidden" name="from_page" value="%(from_page)s" /><br />
215 <input type="submit" />
216 </form>
217 </body></html>""" % {'from_page': from_page, 'username': username,
218 'error_msg': error_msg}
219
220 - def do_login(self, username, password, from_page='..'):
234
244
261
273
274
280 session_auth.__doc__ = """Session authentication hook.
281
282 Any attribute of the SessionAuth class may be overridden via a keyword arg
283 to this function:
284
285 """ + "\n".join(["%s: %s" % (k, type(getattr(SessionAuth, k)).__name__)
286 for k in dir(SessionAuth) if not k.startswith("__")])
287
288
293
298
305
322
324 """Wrap response.body in a generator that recursively iterates over body.
325
326 This allows cherrypy.response.body to consist of 'nested generators';
327 that is, a set of generators that yield generators.
328 """
329 import types
330 def flattener(input):
331 for x in input:
332 if not isinstance(x, types.GeneratorType):
333 yield x
334 else:
335 for y in flattener(x):
336 yield y
337 response = cherrypy.response
338 response.body = flattener(response.body)
339
340
342 """Return the client's preferred media-type (from the given Content-Types).
343
344 If 'media' is None (the default), no test will be performed.
345
346 If 'media' is provided, it should be the Content-Type value (as a string)
347 or values (as a list or tuple of strings) which the current request
348 can emit. The client's acceptable media ranges (as declared in the
349 Accept request header) will be matched in order to these Content-Type
350 values; the first such string is returned. That is, the return value
351 will always be one of the strings provided in the 'media' arg (or None
352 if 'media' is None).
353
354 If no match is found, then HTTPError 406 (Not Acceptable) is raised.
355 Note that most web browsers send */* as a (low-quality) acceptable
356 media range, which should match any Content-Type. In addition, "...if
357 no Accept header field is present, then it is assumed that the client
358 accepts all media types."
359
360 Matching types are checked in order of client preference first,
361 and then in the order of the given 'media' values.
362
363 Note that this function does not honor accept-params (other than "q").
364 """
365 if not media:
366 return
367 if isinstance(media, basestring):
368 media = [media]
369
370
371
372 ranges = cherrypy.request.headers.elements('Accept')
373 if not ranges:
374
375 return media[0]
376 else:
377
378 for element in ranges:
379 if element.qvalue > 0:
380 if element.value == "*/*":
381
382 return media[0]
383 elif element.value.endswith("/*"):
384
385 mtype = element.value[:-1]
386 for m in media:
387 if m.startswith(mtype):
388 return m
389 else:
390
391 if element.value in media:
392 return element.value
393
394
395 ah = cherrypy.request.headers.get('Accept')
396 if ah is None:
397 msg = "Your client did not send an Accept header."
398 else:
399 msg = "Your client sent this Accept header: %s." % ah
400 msg += (" But this resource only emits these media types: %s." %
401 ", ".join(media))
402 raise cherrypy.HTTPError(406, msg)
403