"""A few utility classes/functions used by CherryPy.""" import cgi import datetime import sys import traceback import cherrypy from cherrypy.lib import httptools def get_object_trail(objectpath=None): """ List of (name, object) pairs, from cherrypy.root to the current object. If any named objects are unreachable, (name, None) pairs are used. """ if objectpath is None: try: objectpath = cherrypy.request.object_path except AttributeError: pass if objectpath is not None: objectpath = objectpath.strip('/') # Convert the objectpath into a list of names if not objectpath: nameList = [] else: nameList = objectpath.split('/') if nameList == ['global']: # Special-case a Request-URI of * to allow for our default handler. root = getattr(cherrypy, 'root', None) if root is None: return [('root', None), ('global_', None), ('index', None)] gh = getattr(root, 'global_', _cpGlobalHandler) return [('root', cherrypy.root), ('global_', gh), ('index', None)] nameList = ['root'] + nameList + ['index'] # Convert the list of names into a list of objects node = cherrypy objectTrail = [] for name in nameList: # maps virtual names to Python identifiers (replaces '.' with '_') objname = name.replace('.', '_') node = getattr(node, objname, None) if node is None: objectTrail.append((name, node)) else: objectTrail.append((objname, node)) return objectTrail def get_special_attribute(name, old_name = None): """Return the special attribute. A special attribute is one that applies to all of the children from where it is defined, such as _cp_filters.""" # First, we look in the right-most object to see if this special # attribute is implemented. If not, then we try the previous object, # and so on until we reach cherrypy.root, or a mount point. # If it's still not there, we use the implementation from this module. mounted_app_roots = cherrypy.tree.mount_points.values() objectList = get_object_trail() objectList.reverse() for objname, obj in objectList: if old_name and hasattr(obj, old_name): return getattr(obj, old_name) elif hasattr(obj, name): return getattr(obj, name) if obj in mounted_app_roots: break try: if old_name: return globals()[old_name] else: return globals()[name] except KeyError: if old_name: return get_special_attribute(name) msg = "Special attribute %s could not be found" % repr(name) raise cherrypy.HTTPError(500, msg) def _cpGlobalHandler(): """Default handler for a Request-URI of '*'.""" response = cherrypy.response response.headers['Content-Type'] = 'text/plain' # OPTIONS is defined in HTTP 1.1 and greater request = cherrypy.request if request.method == 'OPTIONS' and request.version >= 1.1: response.headers['Allow'] = 'HEAD, GET, POST, PUT, OPTIONS' else: response.headers['Allow'] = 'HEAD, GET, POST' return "" _cpGlobalHandler.exposed = True def logtime(): now = datetime.datetime.now() month = httptools.monthname[now.month][:3].capitalize() return '%02d/%s/%04d:%02d:%02d:%02d' % ( now.day, month, now.year, now.hour, now.minute, now.second) def _cp_log_access(): """ Default method for logging access """ tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' s = tmpl % {'h': cherrypy.request.remoteHost or cherrypy.request.remoteAddr, 'l': '-', 'u': getattr(cherrypy.request, "login", None) or "-", 't': logtime(), 'r': cherrypy.request.requestLine, 's': cherrypy.response.status.split(" ", 1)[0], 'b': cherrypy.response.headers.get('Content-Length', '') or "-", 'f': cherrypy.request.headers.get('referer', ''), 'a': cherrypy.request.headers.get('user-agent', ''), } if cherrypy.config.get('server.log_to_screen', True): print s fname = cherrypy.config.get('server.log_access_file', '') if fname: f = open(fname, 'ab') try: f.write(s + '\n') finally: f.close() _log_severity_levels = {0: "INFO", 1: "WARNING", 2: "ERROR"} def _cp_log_message(msg, context = '', severity = 0): """Default method for logging messages (error log). This is not just for errors! Applications may call this at any time to log application-specific information. """ level = _log_severity_levels.get(severity, "UNKNOWN") s = ' '.join((logtime(), context, level, msg)) if cherrypy.config.get('server.log_to_screen', True): print s fname = cherrypy.config.get('server.log_file', '') #logdir = os.path.dirname(fname) #if logdir and not os.path.exists(logdir): # os.makedirs(logdir) if fname: f = open(fname, 'ab') try: f.write(s + '\n') finally: f.close() _HTTPErrorTemplate = ''' %(status)s

%(status)s

%(message)s

%(traceback)s
Powered by CherryPy %(version)s
''' def getErrorPage(status, **kwargs): """Return an HTML page, containing a pretty error response. status should be an int or a str. kwargs will be interpolated into the page template. """ try: code, reason, message = httptools.validStatus(status) except ValueError, x: raise cherrypy.HTTPError(500, x.args[0]) # We can't use setdefault here, because some # callers send None for kwarg values. if kwargs.get('status') is None: kwargs['status'] = "%s %s" % (code, reason) if kwargs.get('message') is None: kwargs['message'] = message if kwargs.get('traceback') is None: kwargs['traceback'] = '' if kwargs.get('version') is None: kwargs['version'] = cherrypy.__version__ for k, v in kwargs.iteritems(): if v is None: kwargs[k] = "" else: kwargs[k] = cgi.escape(kwargs[k]) template = _HTTPErrorTemplate error_page_file = cherrypy.config.get('error_page.%s' % code, '') if error_page_file: try: template = file(error_page_file, 'rb').read() except: m = kwargs['message'] if m: m += "
" m += ("In addition, the custom error page " "failed:\n
%s" % (sys.exc_info()[1])) kwargs['message'] = m return template % kwargs def _cp_on_http_error(status, message): """ Default _cp_on_http_error method. status should be an int. """ tb = formatExc() logmsg = "" if cherrypy.config.get('server.log_tracebacks', True): logmsg = tb if cherrypy.config.get('server.log_request_headers', True): h = [" %s: %s" % (k, v) for k, v in cherrypy.request.header_list] logmsg += 'Request Headers:\n' + '\n'.join(h) if logmsg: cherrypy.log(logmsg, "HTTP") if not cherrypy.config.get('server.show_tracebacks', False): tb = None response = cherrypy.response # Remove headers which applied to the original content, # but do not apply to the error page. for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", "Vary", "Content-Encoding", "Content-Length", "Expires", "Content-Location", "Content-MD5", "Last-Modified"]: if response.headers.has_key(key): del response.headers[key] if status != 416: # A server sending a response with status code 416 (Requested # range not satisfiable) SHOULD include a Content-Range field # with a byte-range- resp-spec of "*". The instance-length # specifies the current length of the selected resource. # A response with status code 206 (Partial Content) MUST NOT # include a Content-Range field with a byte-range- resp-spec of "*". if response.headers.has_key("Content-Range"): del response.headers["Content-Range"] # In all cases, finalize will be called after this method, # so don't bother cleaning up response values here. response.status = status content = getErrorPage(status, traceback=tb, message=message) response.body = content response.headers['Content-Length'] = len(content) response.headers['Content-Type'] = "text/html" be_ie_unfriendly(status) _ie_friendly_error_sizes = { 400: 512, 403: 256, 404: 512, 405: 256, 406: 512, 408: 512, 409: 512, 410: 256, 500: 512, 501: 512, 505: 512, } def be_ie_unfriendly(status): response = cherrypy.response # For some statuses, Internet Explorer 5+ shows "friendly error # messages" instead of our response.body if the body is smaller # than a given size. Fix this by returning a body over that size # (by adding whitespace). # See http://support.microsoft.com/kb/q218155/ s = _ie_friendly_error_sizes.get(status, 0) if s: s += 1 # Since we are issuing an HTTP error status, we assume that # the entity is short, and we should just collapse it. content = response.collapse_body() l = len(content) if l and l < s: # IN ADDITION: the response must be written to IE # in one chunk or it will still get replaced! Bah. content = content + (" " * (s - l)) response.body = content response.headers['Content-Length'] = len(content) def lower_to_camel(s): """Turns lowercase_with_underscore into camelCase.""" sp = s.split('_') new_sp = [] for i, s in enumerate(sp): if i != 0: s = s[0].upper() + s[1:] new_sp.append(s) return ''.join(new_sp) def formatExc(exc=None): """formatExc(exc=None) -> exc (or sys.exc_info if None), formatted.""" if exc is None: exc = sys.exc_info() if exc == (None, None, None): return "" page_handler_str = "" args = list(getattr(exc[1], "args", [])) if args: if len(args) > 1: page_handler = args.pop() page_handler_str = 'Page handler: %s\n' % repr(page_handler) exc[1].args = tuple(args) return page_handler_str + "".join(traceback.format_exception(*exc)) def bareError(extrabody=None): """Produce status, headers, body for a critical error. Returns a triple without calling any other questionable functions, so it should be as error-free as possible. Call it from an HTTP server if you get errors after Request() is done. If extrabody is None, a friendly but rather unhelpful error message is set in the body. If extrabody is a string, it will be appended as-is to the body. """ # The whole point of this function is to be a last line-of-defense # in handling errors. That is, it must not raise any errors itself; # it cannot be allowed to fail. Therefore, don't add to it! # In particular, don't call any other CP functions. body = "Unrecoverable error in the server." if extrabody is not None: body += "\n" + extrabody return ("500 Internal Server Error", [('Content-Type', 'text/plain'), ('Content-Length', str(len(body)))], [body]) def _cp_on_error(): """ Default _cp_on_error method """ # Allow logging of only *unexpected* HTTPError's. if (not cherrypy.config.get('server.log_tracebacks', True) and cherrypy.config.get('server.log_unhandled_tracebacks', True)): cherrypy.log(traceback=True) cherrypy.HTTPError(500).set_response() def headers(headers): """ Provides a simple way to add specific headers to page handler Any previously set headers provided in the list of tuples will be changed headers - a list of tuple : (header_name, header_value) """ def wrapper(func): def inner(*args): for item in headers: headername = item[0] headervalue = item[1] cherrypy.response.headerMap[headername] = headervalue return func(*args) return inner return wrapper _cp_filters = []