NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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)