cptools.py (16098B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\cptools.py 2 import logging 3 import re 4 import cherrypy 5 from cherrypy._cpcompat import basestring, ntob, md5, set 6 from cherrypy.lib import httputil as _httputil 7 8 def validate_etags(autotags = False, debug = False): 9 response = cherrypy.serving.response 10 if hasattr(response, 'ETag'): 11 return 12 status, reason, msg = _httputil.valid_status(response.status) 13 etag = response.headers.get('ETag') 14 if etag: 15 if debug: 16 cherrypy.log('ETag already set: %s' % etag, 'TOOLS.ETAGS') 17 elif not autotags: 18 if debug: 19 cherrypy.log('Autotags off', 'TOOLS.ETAGS') 20 elif status != 200: 21 if debug: 22 cherrypy.log('Status not 200', 'TOOLS.ETAGS') 23 else: 24 etag = response.collapse_body() 25 etag = '"%s"' % md5(etag).hexdigest() 26 if debug: 27 cherrypy.log('Setting ETag: %s' % etag, 'TOOLS.ETAGS') 28 response.headers['ETag'] = etag 29 response.ETag = etag 30 if debug: 31 cherrypy.log('Status: %s' % status, 'TOOLS.ETAGS') 32 if status >= 200 and status <= 299: 33 request = cherrypy.serving.request 34 conditions = request.headers.elements('If-Match') or [] 35 conditions = [ str(x) for x in conditions ] 36 if debug: 37 cherrypy.log('If-Match conditions: %s' % repr(conditions), 'TOOLS.ETAGS') 38 if conditions and not (conditions == ['*'] or etag in conditions): 39 raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did not match %r' % (etag, conditions)) 40 conditions = request.headers.elements('If-None-Match') or [] 41 conditions = [ str(x) for x in conditions ] 42 if debug: 43 cherrypy.log('If-None-Match conditions: %s' % repr(conditions), 'TOOLS.ETAGS') 44 if conditions == ['*'] or etag in conditions: 45 if debug: 46 cherrypy.log('request.method: %s' % request.method, 'TOOLS.ETAGS') 47 if request.method in ('GET', 'HEAD'): 48 raise cherrypy.HTTPRedirect([], 304) 49 else: 50 raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r matched %r' % (etag, conditions)) 51 52 53 def validate_since(): 54 response = cherrypy.serving.response 55 lastmod = response.headers.get('Last-Modified') 56 if lastmod: 57 status, reason, msg = _httputil.valid_status(response.status) 58 request = cherrypy.serving.request 59 since = request.headers.get('If-Unmodified-Since') 60 if since and since != lastmod: 61 if status >= 200 and status <= 299 or status == 412: 62 raise cherrypy.HTTPError(412) 63 since = request.headers.get('If-Modified-Since') 64 if since and since == lastmod: 65 if status >= 200 and status <= 299 or status == 304: 66 if request.method in ('GET', 'HEAD'): 67 raise cherrypy.HTTPRedirect([], 304) 68 else: 69 raise cherrypy.HTTPError(412) 70 71 72 def allow(methods = None, debug = False): 73 if not isinstance(methods, (tuple, list)): 74 methods = [methods] 75 methods = [ m.upper() for m in methods if m ] 76 if not methods: 77 methods = ['GET', 'HEAD'] 78 elif 'GET' in methods and 'HEAD' not in methods: 79 methods.append('HEAD') 80 cherrypy.response.headers['Allow'] = ', '.join(methods) 81 if cherrypy.request.method not in methods: 82 if debug: 83 cherrypy.log('request.method %r not in methods %r' % (cherrypy.request.method, methods), 'TOOLS.ALLOW') 84 raise cherrypy.HTTPError(405) 85 elif debug: 86 cherrypy.log('request.method %r in methods %r' % (cherrypy.request.method, methods), 'TOOLS.ALLOW') 87 88 89 def proxy(base = None, local = 'X-Forwarded-Host', remote = 'X-Forwarded-For', scheme = 'X-Forwarded-Proto', debug = False): 90 request = cherrypy.serving.request 91 if scheme: 92 s = request.headers.get(scheme, None) 93 if debug: 94 cherrypy.log('Testing scheme %r:%r' % (scheme, s), 'TOOLS.PROXY') 95 if s == 'on' and 'ssl' in scheme.lower(): 96 scheme = 'https' 97 else: 98 scheme = s 99 if not scheme: 100 scheme = request.base[:request.base.find('://')] 101 if local: 102 lbase = request.headers.get(local, None) 103 if debug: 104 cherrypy.log('Testing local %r:%r' % (local, lbase), 'TOOLS.PROXY') 105 if lbase is not None: 106 base = lbase.split(',')[0] 107 if not base: 108 port = request.local.port 109 if port == 80: 110 base = '127.0.0.1' 111 else: 112 base = '127.0.0.1:%s' % port 113 if base.find('://') == -1: 114 base = scheme + '://' + base 115 request.base = base 116 if remote: 117 xff = request.headers.get(remote) 118 if debug: 119 cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY') 120 if xff: 121 if remote == 'X-Forwarded-For': 122 xff = xff.split(',')[-1].strip() 123 request.remote.ip = xff 124 125 126 def ignore_headers(headers = ('Range',), debug = False): 127 request = cherrypy.serving.request 128 for name in headers: 129 if name in request.headers: 130 if debug: 131 cherrypy.log('Ignoring request header %r' % name, 'TOOLS.IGNORE_HEADERS') 132 del request.headers[name] 133 134 135 def response_headers(headers = None, debug = False): 136 if debug: 137 cherrypy.log('Setting response headers: %s' % repr(headers), 'TOOLS.RESPONSE_HEADERS') 138 for name, value in headers or []: 139 cherrypy.serving.response.headers[name] = value 140 141 142 response_headers.failsafe = True 143 144 def referer(pattern, accept = True, accept_missing = False, error = 403, message = 'Forbidden Referer header.', debug = False): 145 try: 146 ref = cherrypy.serving.request.headers['Referer'] 147 match = bool(re.match(pattern, ref)) 148 if debug: 149 cherrypy.log('Referer %r matches %r' % (ref, pattern), 'TOOLS.REFERER') 150 if accept == match: 151 return 152 except KeyError: 153 if debug: 154 cherrypy.log('No Referer header', 'TOOLS.REFERER') 155 if accept_missing: 156 return 157 158 raise cherrypy.HTTPError(error, message) 159 160 161 class SessionAuth(object): 162 session_key = 'username' 163 debug = False 164 165 def check_username_and_password(self, username, password): 166 pass 167 168 def anonymous(self): 169 pass 170 171 def on_login(self, username): 172 pass 173 174 def on_logout(self, username): 175 pass 176 177 def on_check(self, username): 178 pass 179 180 def login_screen(self, from_page = '..', username = '', error_msg = '', **kwargs): 181 return ntob('<html><body>\nMessage: %(error_msg)s\n<form method="post" action="do_login">\n Login: <input type="text" name="username" value="%(username)s" size="10" /><br />\n Password: <input type="password" name="password" size="10" /><br />\n <input type="hidden" name="from_page" value="%(from_page)s" /><br />\n <input type="submit" />\n</form>\n</body></html>' % {'from_page': from_page, 182 'username': username, 183 'error_msg': error_msg}, 'utf-8') 184 185 def do_login(self, username, password, from_page = '..', **kwargs): 186 response = cherrypy.serving.response 187 error_msg = self.check_username_and_password(username, password) 188 if error_msg: 189 body = self.login_screen(from_page, username, error_msg) 190 response.body = body 191 if 'Content-Length' in response.headers: 192 del response.headers['Content-Length'] 193 return True 194 cherrypy.serving.request.login = username 195 cherrypy.session[self.session_key] = username 196 self.on_login(username) 197 raise cherrypy.HTTPRedirect(from_page or '/') 198 199 def do_logout(self, from_page = '..', **kwargs): 200 sess = cherrypy.session 201 username = sess.get(self.session_key) 202 sess[self.session_key] = None 203 if username: 204 cherrypy.serving.request.login = None 205 self.on_logout(username) 206 raise cherrypy.HTTPRedirect(from_page) 207 208 def do_check(self): 209 sess = cherrypy.session 210 request = cherrypy.serving.request 211 response = cherrypy.serving.response 212 username = sess.get(self.session_key) 213 if not username: 214 sess[self.session_key] = username = self.anonymous() 215 if self.debug: 216 cherrypy.log('No session[username], trying anonymous', 'TOOLS.SESSAUTH') 217 if not username: 218 url = cherrypy.url(qs=request.query_string) 219 if self.debug: 220 cherrypy.log('No username, routing to login_screen with from_page %r' % url, 'TOOLS.SESSAUTH') 221 response.body = self.login_screen(url) 222 if 'Content-Length' in response.headers: 223 del response.headers['Content-Length'] 224 return True 225 if self.debug: 226 cherrypy.log('Setting request.login to %r' % username, 'TOOLS.SESSAUTH') 227 request.login = username 228 self.on_check(username) 229 230 def run(self): 231 request = cherrypy.serving.request 232 response = cherrypy.serving.response 233 path = request.path_info 234 if path.endswith('login_screen'): 235 if self.debug: 236 cherrypy.log('routing %r to login_screen' % path, 'TOOLS.SESSAUTH') 237 return self.login_screen(**request.params) 238 elif path.endswith('do_login'): 239 if request.method != 'POST': 240 response.headers['Allow'] = 'POST' 241 if self.debug: 242 cherrypy.log('do_login requires POST', 'TOOLS.SESSAUTH') 243 raise cherrypy.HTTPError(405) 244 if self.debug: 245 cherrypy.log('routing %r to do_login' % path, 'TOOLS.SESSAUTH') 246 return self.do_login(**request.params) 247 elif path.endswith('do_logout'): 248 if request.method != 'POST': 249 response.headers['Allow'] = 'POST' 250 raise cherrypy.HTTPError(405) 251 if self.debug: 252 cherrypy.log('routing %r to do_logout' % path, 'TOOLS.SESSAUTH') 253 return self.do_logout(**request.params) 254 else: 255 if self.debug: 256 cherrypy.log('No special path, running do_check', 'TOOLS.SESSAUTH') 257 return self.do_check() 258 259 260 def session_auth(**kwargs): 261 sa = SessionAuth() 262 for k, v in kwargs.items(): 263 setattr(sa, k, v) 264 265 return sa.run() 266 267 268 session_auth.__doc__ = 'Session authentication hook.\n\nAny attribute of the SessionAuth class may be overridden via a keyword arg\nto this function:\n\n' + '\n'.join([ '%s: %s' % (k, type(getattr(SessionAuth, k)).__name__) for k in dir(SessionAuth) if not k.startswith('__') ]) 269 270 def log_traceback(severity = logging.ERROR, debug = False): 271 cherrypy.log('', 'HTTP', severity=severity, traceback=True) 272 273 274 def log_request_headers(debug = False): 275 h = [ ' %s: %s' % (k, v) for k, v in cherrypy.serving.request.header_list ] 276 cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), 'HTTP') 277 278 279 def log_hooks(debug = False): 280 request = cherrypy.serving.request 281 msg = [] 282 from cherrypy import _cprequest 283 points = _cprequest.hookpoints 284 for k in request.hooks.keys(): 285 if k not in points: 286 points.append(k) 287 288 for k in points: 289 msg.append(' %s:' % k) 290 v = request.hooks.get(k, []) 291 v.sort() 292 for h in v: 293 msg.append(' %r' % h) 294 295 cherrypy.log('\nRequest Hooks for ' + cherrypy.url() + ':\n' + '\n'.join(msg), 'HTTP') 296 297 298 def redirect(url = '', internal = True, debug = False): 299 if debug: 300 cherrypy.log('Redirecting %sto: %s' % ({True: 'internal ', 301 False: ''}[internal], url), 'TOOLS.REDIRECT') 302 if internal: 303 raise cherrypy.InternalRedirect(url) 304 else: 305 raise cherrypy.HTTPRedirect(url) 306 307 308 def trailing_slash(missing = True, extra = False, status = None, debug = False): 309 request = cherrypy.serving.request 310 pi = request.path_info 311 if debug: 312 cherrypy.log('is_index: %r, missing: %r, extra: %r, path_info: %r' % (request.is_index, 313 missing, 314 extra, 315 pi), 'TOOLS.TRAILING_SLASH') 316 if request.is_index is True: 317 if missing: 318 if not pi.endswith('/'): 319 new_url = cherrypy.url(pi + '/', request.query_string) 320 raise cherrypy.HTTPRedirect(new_url, status=status or 301) 321 elif request.is_index is False: 322 if extra: 323 if pi.endswith('/') and pi != '/': 324 new_url = cherrypy.url(pi[:-1], request.query_string) 325 raise cherrypy.HTTPRedirect(new_url, status=status or 301) 326 327 328 def flatten(debug = False): 329 import types 330 331 def flattener(input): 332 numchunks = 0 333 for x in input: 334 if not isinstance(x, types.GeneratorType): 335 numchunks += 1 336 yield x 337 else: 338 for y in flattener(x): 339 numchunks += 1 340 yield y 341 342 if debug: 343 cherrypy.log('Flattened %d chunks' % numchunks, 'TOOLS.FLATTEN') 344 345 response = cherrypy.serving.response 346 response.body = flattener(response.body) 347 348 349 def accept(media = None, debug = False): 350 if not media: 351 return 352 if isinstance(media, basestring): 353 media = [media] 354 request = cherrypy.serving.request 355 ranges = request.headers.elements('Accept') 356 if not ranges: 357 if debug: 358 cherrypy.log('No Accept header elements', 'TOOLS.ACCEPT') 359 return media[0] 360 for element in ranges: 361 if element.qvalue > 0: 362 if element.value == '*/*': 363 if debug: 364 cherrypy.log('Match due to */*', 'TOOLS.ACCEPT') 365 return media[0] 366 if element.value.endswith('/*'): 367 mtype = element.value[:-1] 368 for m in media: 369 if m.startswith(mtype): 370 if debug: 371 cherrypy.log('Match due to %s' % element.value, 'TOOLS.ACCEPT') 372 return m 373 374 elif element.value in media: 375 if debug: 376 cherrypy.log('Match due to %s' % element.value, 'TOOLS.ACCEPT') 377 return element.value 378 379 ah = request.headers.get('Accept') 380 if ah is None: 381 msg = 'Your client did not send an Accept header.' 382 else: 383 msg = 'Your client sent this Accept header: %s.' % ah 384 msg += ' But this resource only emits these media types: %s.' % ', '.join(media) 385 raise cherrypy.HTTPError(406, msg) 386 387 388 class MonitoredHeaderMap(_httputil.HeaderMap): 389 390 def __init__(self): 391 self.accessed_headers = set() 392 393 def __getitem__(self, key): 394 self.accessed_headers.add(key) 395 return _httputil.HeaderMap.__getitem__(self, key) 396 397 def __contains__(self, key): 398 self.accessed_headers.add(key) 399 return _httputil.HeaderMap.__contains__(self, key) 400 401 def get(self, key, default = None): 402 self.accessed_headers.add(key) 403 return _httputil.HeaderMap.get(self, key, default=default) 404 405 def has_key(self, key): 406 self.accessed_headers.add(key) 407 return _httputil.HeaderMap.has_key(self, key) 408 409 410 def autovary(ignore = None, debug = False): 411 request = cherrypy.serving.request 412 req_h = request.headers 413 request.headers = MonitoredHeaderMap() 414 request.headers.update(req_h) 415 if ignore is None: 416 ignore = set(['Content-Disposition', 'Content-Length', 'Content-Type']) 417 418 def set_response_header(): 419 resp_h = cherrypy.serving.response.headers 420 v = set([ e.value for e in resp_h.elements('Vary') ]) 421 if debug: 422 cherrypy.log('Accessed headers: %s' % request.headers.accessed_headers, 'TOOLS.AUTOVARY') 423 v = v.union(request.headers.accessed_headers) 424 v = v.difference(ignore) 425 v = list(v) 426 v.sort() 427 resp_h['Vary'] = ', '.join(v) 428 429 request.hooks.attach('before_finalize', set_response_header, 95)