| 1 |
"""Functions for builtin CherryPy tools.""" |
|---|
| 2 |
|
|---|
| 3 |
import cherrypy |
|---|
| 4 |
import http as _http |
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
def validate_etags(autotags=False): |
|---|
| 10 |
"""Validate the current ETag against If-Match, If-None-Match headers.""" |
|---|
| 11 |
|
|---|
| 12 |
if hasattr(cherrypy.response, "ETag"): |
|---|
| 13 |
return |
|---|
| 14 |
|
|---|
| 15 |
etag = cherrypy.response.headers.get('ETag') |
|---|
| 16 |
|
|---|
| 17 |
if (not etag) and autotags: |
|---|
| 18 |
import md5 |
|---|
| 19 |
etag = '"%s"' % md5.new(cherrypy.response.collapse_body()).hexdigest() |
|---|
| 20 |
cherrypy.response.headers['ETag'] = etag |
|---|
| 21 |
|
|---|
| 22 |
if etag: |
|---|
| 23 |
cherrypy.response.ETag = etag |
|---|
| 24 |
|
|---|
| 25 |
status, reason, msg = _http.valid_status(cherrypy.response.status) |
|---|
| 26 |
|
|---|
| 27 |
conditions = cherrypy.request.headers.elements('If-Match') or [] |
|---|
| 28 |
conditions = [str(x) for x in conditions] |
|---|
| 29 |
if conditions and not (conditions == ["*"] or etag in conditions): |
|---|
| 30 |
if (status >= 200 and status < 299) or status == 412: |
|---|
| 31 |
raise cherrypy.HTTPError(412) |
|---|
| 32 |
|
|---|
| 33 |
conditions = cherrypy.request.headers.elements('If-None-Match') or [] |
|---|
| 34 |
conditions = [str(x) for x in conditions] |
|---|
| 35 |
if conditions == ["*"] or etag in conditions: |
|---|
| 36 |
if (status >= 200 and status < 299) or status == 304: |
|---|
| 37 |
if cherrypy.request.method in ("GET", "HEAD"): |
|---|
| 38 |
raise cherrypy.HTTPRedirect([], 304) |
|---|
| 39 |
else: |
|---|
| 40 |
raise cherrypy.HTTPError(412) |
|---|
| 41 |
|
|---|
| 42 |
def validate_since(): |
|---|
| 43 |
"""Validate the current Last-Modified against If-Modified-Since headers.""" |
|---|
| 44 |
lastmod = cherrypy.response.headers.get('Last-Modified') |
|---|
| 45 |
if lastmod: |
|---|
| 46 |
status, reason, msg = _http.valid_status(cherrypy.response.status) |
|---|
| 47 |
|
|---|
| 48 |
since = cherrypy.request.headers.get('If-Unmodified-Since') |
|---|
| 49 |
if since and since != lastmod: |
|---|
| 50 |
if (status >= 200 and status < 299) or status == 412: |
|---|
| 51 |
raise cherrypy.HTTPError(412) |
|---|
| 52 |
|
|---|
| 53 |
since = cherrypy.request.headers.get('If-Modified-Since') |
|---|
| 54 |
if since and since == lastmod: |
|---|
| 55 |
if (status >= 200 and status < 299) or status == 304: |
|---|
| 56 |
if cherrypy.request.method in ("GET", "HEAD"): |
|---|
| 57 |
raise cherrypy.HTTPRedirect([], 304) |
|---|
| 58 |
else: |
|---|
| 59 |
raise cherrypy.HTTPError(412) |
|---|
| 60 |
|
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For'): |
|---|
| 65 |
"""Change the base URL (scheme://host[:port][/path]). |
|---|
| 66 |
|
|---|
| 67 |
Useful when running a CP server behind Apache. |
|---|
| 68 |
|
|---|
| 69 |
If you want the new request.base to include path info (not just the host), |
|---|
| 70 |
you must explicitly set base to the full base path, and ALSO set 'local' |
|---|
| 71 |
to '', so that the X-Forwarded-Host request header (which never includes |
|---|
| 72 |
path info) does not override it. |
|---|
| 73 |
|
|---|
| 74 |
cherrypy.request.remote.ip (the IP address of the client) will be |
|---|
| 75 |
rewritten if the header specified by the 'remote' arg is valid. |
|---|
| 76 |
By default, 'remote' is set to 'X-Forwarded-For'. If you do not |
|---|
| 77 |
want to rewrite remote.ip, set the 'remote' arg to an empty string. |
|---|
| 78 |
""" |
|---|
| 79 |
|
|---|
| 80 |
request = cherrypy.request |
|---|
| 81 |
|
|---|
| 82 |
if base is None: |
|---|
| 83 |
port = cherrypy.local.port |
|---|
| 84 |
if port == 80: |
|---|
| 85 |
base = 'http://localhost' |
|---|
| 86 |
else: |
|---|
| 87 |
base = 'http://localhost:%s' % port |
|---|
| 88 |
|
|---|
| 89 |
if local: |
|---|
| 90 |
base = request.headers.get(local, base) |
|---|
| 91 |
|
|---|
| 92 |
if base.find("://") == -1: |
|---|
| 93 |
|
|---|
| 94 |
base = request.base[:request.base.find("://") + 3] + base |
|---|
| 95 |
|
|---|
| 96 |
request.base = base |
|---|
| 97 |
|
|---|
| 98 |
if remote: |
|---|
| 99 |
xff = request.headers.get(remote) |
|---|
| 100 |
if xff: |
|---|
| 101 |
if remote == 'X-Forwarded-For': |
|---|
| 102 |
|
|---|
| 103 |
xff = xff.split(',')[-1].strip() |
|---|
| 104 |
request.remote.ip = xff |
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
def response_headers(headers=None): |
|---|
| 108 |
"""Set headers on the response.""" |
|---|
| 109 |
for name, value in (headers or []): |
|---|
| 110 |
cherrypy.response.headers[name] = value |
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
_login_screen = """<html><body> |
|---|
| 114 |
Message: %(error_msg)s |
|---|
| 115 |
<form method="post" action="do_login"> |
|---|
| 116 |
Login: <input type="text" name="login" value="%(login)s" size="10" /><br /> |
|---|
| 117 |
Password: <input type="password" name="password" size="10" /><br /> |
|---|
| 118 |
<input type="hidden" name="from_page" value="%(from_page)s" /><br /> |
|---|
| 119 |
<input type="submit" /> |
|---|
| 120 |
</form> |
|---|
| 121 |
</body></html>""" |
|---|
| 122 |
|
|---|
| 123 |
def session_auth(check_login_and_password=None, not_logged_in=None, |
|---|
| 124 |
load_user_by_username=None, session_key='username', |
|---|
| 125 |
on_login=None, on_logout=None, login_screen=None): |
|---|
| 126 |
"""Assert that the user is logged in.""" |
|---|
| 127 |
|
|---|
| 128 |
if login_screen is None: |
|---|
| 129 |
login_screen = _login_screen |
|---|
| 130 |
|
|---|
| 131 |
request = cherrypy.request |
|---|
| 132 |
tdata = cherrypy.thread_data |
|---|
| 133 |
sess = cherrypy.session |
|---|
| 134 |
request.user = None |
|---|
| 135 |
tdata.user = None |
|---|
| 136 |
|
|---|
| 137 |
if request.path.endswith('login_screen'): |
|---|
| 138 |
return False |
|---|
| 139 |
elif request.path.endswith('do_logout'): |
|---|
| 140 |
login = sess.get(session_key) |
|---|
| 141 |
sess[session_key] = None |
|---|
| 142 |
request.user = None |
|---|
| 143 |
tdata.user = None |
|---|
| 144 |
if login and on_logout: |
|---|
| 145 |
on_logout(login) |
|---|
| 146 |
from_page = request.params.get('from_page', '..') |
|---|
| 147 |
raise cherrypy.HTTPRedirect(from_page) |
|---|
| 148 |
elif request.path.endswith('do_login'): |
|---|
| 149 |
from_page = request.params.get('from_page', '..') |
|---|
| 150 |
login = request.params['login'] |
|---|
| 151 |
password = request.params['password'] |
|---|
| 152 |
error_msg = check_login_and_password(login, password) |
|---|
| 153 |
if error_msg: |
|---|
| 154 |
kw = {"from_page": from_page, |
|---|
| 155 |
"login": login, "error_msg": error_msg} |
|---|
| 156 |
cherrypy.response.body = login_screen % kw |
|---|
| 157 |
return True |
|---|
| 158 |
|
|---|
| 159 |
sess[session_key] = login |
|---|
| 160 |
if on_login: |
|---|
| 161 |
on_login(login) |
|---|
| 162 |
raise cherrypy.HTTPRedirect(from_page or "/") |
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 |
temp_user = None |
|---|
| 166 |
if (not sess.get(session_key)) and not_logged_in: |
|---|
| 167 |
|
|---|
| 168 |
|
|---|
| 169 |
temp_user = not_logged_in() |
|---|
| 170 |
if (not sess.get(session_key)) and not temp_user: |
|---|
| 171 |
kw = {"from_page": request.browser_url, "login": "", "error_msg": ""} |
|---|
| 172 |
cherrypy.response.body = login_screen % kw |
|---|
| 173 |
return True |
|---|
| 174 |
|
|---|
| 175 |
|
|---|
| 176 |
if load_user_by_username and not tdata.user: |
|---|
| 177 |
username = temp_user or sess[session_key] |
|---|
| 178 |
request.user = load_user_by_username(username) |
|---|
| 179 |
tdata.user = request.user |
|---|
| 180 |
|
|---|
| 181 |
return False |
|---|
| 182 |
|
|---|
| 183 |
def virtual_host(use_x_forwarded_host=True, **domains): |
|---|
| 184 |
"""Redirect internally based on the Host header. |
|---|
| 185 |
|
|---|
| 186 |
Useful when running multiple sites within one CP server. |
|---|
| 187 |
|
|---|
| 188 |
From http://groups.google.com/group/cherrypy-users/browse_thread/thread/f393540fe278e54d: |
|---|
| 189 |
|
|---|
| 190 |
For various reasons I need several domains to point to different parts of a |
|---|
| 191 |
single website structure as well as to their own "homepage" EG |
|---|
| 192 |
|
|---|
| 193 |
http://www.mydom1.com -> root |
|---|
| 194 |
http://www.mydom2.com -> root/mydom2/ |
|---|
| 195 |
http://www.mydom3.com -> root/mydom3/ |
|---|
| 196 |
http://www.mydom4.com -> under construction page |
|---|
| 197 |
|
|---|
| 198 |
but also to have http://www.mydom1.com/mydom2/ etc to be valid pages in |
|---|
| 199 |
their own right. |
|---|
| 200 |
""" |
|---|
| 201 |
if hasattr(cherrypy.request, "virtual_prefix"): |
|---|
| 202 |
return |
|---|
| 203 |
|
|---|
| 204 |
domain = cherrypy.request.headers.get('Host', '') |
|---|
| 205 |
if use_x_forwarded_host: |
|---|
| 206 |
domain = cherrypy.request.headers.get("X-Forwarded-Host", domain) |
|---|
| 207 |
|
|---|
| 208 |
cherrypy.request.virtual_prefix = prefix = domains.get(domain, "") |
|---|
| 209 |
if prefix: |
|---|
| 210 |
raise cherrypy.InternalRedirect(_http.urljoin(prefix, cherrypy.request.path_info)) |
|---|
| 211 |
|
|---|
| 212 |
def log_traceback(): |
|---|
| 213 |
"""Write the last error's traceback to the cherrypy error log.""" |
|---|
| 214 |
from cherrypy import _cperror |
|---|
| 215 |
cherrypy.log(_cperror.format_exc(), "HTTP") |
|---|
| 216 |
|
|---|
| 217 |
def log_request_headers(): |
|---|
| 218 |
"""Write request headers to the cherrypy error log.""" |
|---|
| 219 |
h = [" %s: %s" % (k, v) for k, v in cherrypy.request.header_list] |
|---|
| 220 |
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), "HTTP") |
|---|
| 221 |
|
|---|
| 222 |
def redirect(url=''): |
|---|
| 223 |
"""Raise cherrypy.HTTPRedirect to the given url.""" |
|---|
| 224 |
raise cherrypy.HTTPRedirect(url) |
|---|