mirror of https://github.com/evilhero/mylar
333 lines
15 KiB
Python
Executable File
333 lines
15 KiB
Python
Executable File
import os
|
|
import warnings
|
|
|
|
import cherrypy
|
|
from cherrypy._cpcompat import iteritems, copykeys, builtins
|
|
|
|
|
|
class Checker(object):
|
|
|
|
"""A checker for CherryPy sites and their mounted applications.
|
|
|
|
When this object is called at engine startup, it executes each
|
|
of its own methods whose names start with ``check_``. If you wish
|
|
to disable selected checks, simply add a line in your global
|
|
config which sets the appropriate method to False::
|
|
|
|
[global]
|
|
checker.check_skipped_app_config = False
|
|
|
|
You may also dynamically add or replace ``check_*`` methods in this way.
|
|
"""
|
|
|
|
on = True
|
|
"""If True (the default), run all checks; if False, turn off all checks."""
|
|
|
|
def __init__(self):
|
|
self._populate_known_types()
|
|
|
|
def __call__(self):
|
|
"""Run all check_* methods."""
|
|
if self.on:
|
|
oldformatwarning = warnings.formatwarning
|
|
warnings.formatwarning = self.formatwarning
|
|
try:
|
|
for name in dir(self):
|
|
if name.startswith("check_"):
|
|
method = getattr(self, name)
|
|
if method and hasattr(method, '__call__'):
|
|
method()
|
|
finally:
|
|
warnings.formatwarning = oldformatwarning
|
|
|
|
def formatwarning(self, message, category, filename, lineno, line=None):
|
|
"""Function to format a warning."""
|
|
return "CherryPy Checker:\n%s\n\n" % message
|
|
|
|
# This value should be set inside _cpconfig.
|
|
global_config_contained_paths = False
|
|
|
|
def check_app_config_entries_dont_start_with_script_name(self):
|
|
"""Check for Application config with sections that repeat script_name.
|
|
"""
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
if not app.config:
|
|
continue
|
|
if sn == '':
|
|
continue
|
|
sn_atoms = sn.strip("/").split("/")
|
|
for key in app.config.keys():
|
|
key_atoms = key.strip("/").split("/")
|
|
if key_atoms[:len(sn_atoms)] == sn_atoms:
|
|
warnings.warn(
|
|
"The application mounted at %r has config "
|
|
"entries that start with its script name: %r" % (sn,
|
|
key))
|
|
|
|
def check_site_config_entries_in_app_config(self):
|
|
"""Check for mounted Applications that have site-scoped config."""
|
|
for sn, app in iteritems(cherrypy.tree.apps):
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
|
|
msg = []
|
|
for section, entries in iteritems(app.config):
|
|
if section.startswith('/'):
|
|
for key, value in iteritems(entries):
|
|
for n in ("engine.", "server.", "tree.", "checker."):
|
|
if key.startswith(n):
|
|
msg.append("[%s] %s = %s" %
|
|
(section, key, value))
|
|
if msg:
|
|
msg.insert(0,
|
|
"The application mounted at %r contains the "
|
|
"following config entries, which are only allowed "
|
|
"in site-wide config. Move them to a [global] "
|
|
"section and pass them to cherrypy.config.update() "
|
|
"instead of tree.mount()." % sn)
|
|
warnings.warn(os.linesep.join(msg))
|
|
|
|
def check_skipped_app_config(self):
|
|
"""Check for mounted Applications that have no config."""
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
if not app.config:
|
|
msg = "The Application mounted at %r has an empty config." % sn
|
|
if self.global_config_contained_paths:
|
|
msg += (" It looks like the config you passed to "
|
|
"cherrypy.config.update() contains application-"
|
|
"specific sections. You must explicitly pass "
|
|
"application config via "
|
|
"cherrypy.tree.mount(..., config=app_config)")
|
|
warnings.warn(msg)
|
|
return
|
|
|
|
def check_app_config_brackets(self):
|
|
"""Check for Application config with extraneous brackets in section
|
|
names.
|
|
"""
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
if not app.config:
|
|
continue
|
|
for key in app.config.keys():
|
|
if key.startswith("[") or key.endswith("]"):
|
|
warnings.warn(
|
|
"The application mounted at %r has config "
|
|
"section names with extraneous brackets: %r. "
|
|
"Config *files* need brackets; config *dicts* "
|
|
"(e.g. passed to tree.mount) do not." % (sn, key))
|
|
|
|
def check_static_paths(self):
|
|
"""Check Application config for incorrect static paths."""
|
|
# Use the dummy Request object in the main thread.
|
|
request = cherrypy.request
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
request.app = app
|
|
for section in app.config:
|
|
# get_resource will populate request.config
|
|
request.get_resource(section + "/dummy.html")
|
|
conf = request.config.get
|
|
|
|
if conf("tools.staticdir.on", False):
|
|
msg = ""
|
|
root = conf("tools.staticdir.root")
|
|
dir = conf("tools.staticdir.dir")
|
|
if dir is None:
|
|
msg = "tools.staticdir.dir is not set."
|
|
else:
|
|
fulldir = ""
|
|
if os.path.isabs(dir):
|
|
fulldir = dir
|
|
if root:
|
|
msg = ("dir is an absolute path, even "
|
|
"though a root is provided.")
|
|
testdir = os.path.join(root, dir[1:])
|
|
if os.path.exists(testdir):
|
|
msg += (
|
|
"\nIf you meant to serve the "
|
|
"filesystem folder at %r, remove the "
|
|
"leading slash from dir." % (testdir,))
|
|
else:
|
|
if not root:
|
|
msg = (
|
|
"dir is a relative path and "
|
|
"no root provided.")
|
|
else:
|
|
fulldir = os.path.join(root, dir)
|
|
if not os.path.isabs(fulldir):
|
|
msg = ("%r is not an absolute path." % (
|
|
fulldir,))
|
|
|
|
if fulldir and not os.path.exists(fulldir):
|
|
if msg:
|
|
msg += "\n"
|
|
msg += ("%r (root + dir) is not an existing "
|
|
"filesystem path." % fulldir)
|
|
|
|
if msg:
|
|
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
|
|
% (msg, section, root, dir))
|
|
|
|
# -------------------------- Compatibility -------------------------- #
|
|
obsolete = {
|
|
'server.default_content_type': 'tools.response_headers.headers',
|
|
'log_access_file': 'log.access_file',
|
|
'log_config_options': None,
|
|
'log_file': 'log.error_file',
|
|
'log_file_not_found': None,
|
|
'log_request_headers': 'tools.log_headers.on',
|
|
'log_to_screen': 'log.screen',
|
|
'show_tracebacks': 'request.show_tracebacks',
|
|
'throw_errors': 'request.throw_errors',
|
|
'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
|
|
'cherrypy.Application(Root())))'),
|
|
}
|
|
|
|
deprecated = {}
|
|
|
|
def _compat(self, config):
|
|
"""Process config and warn on each obsolete or deprecated entry."""
|
|
for section, conf in config.items():
|
|
if isinstance(conf, dict):
|
|
for k, v in conf.items():
|
|
if k in self.obsolete:
|
|
warnings.warn("%r is obsolete. Use %r instead.\n"
|
|
"section: [%s]" %
|
|
(k, self.obsolete[k], section))
|
|
elif k in self.deprecated:
|
|
warnings.warn("%r is deprecated. Use %r instead.\n"
|
|
"section: [%s]" %
|
|
(k, self.deprecated[k], section))
|
|
else:
|
|
if section in self.obsolete:
|
|
warnings.warn("%r is obsolete. Use %r instead."
|
|
% (section, self.obsolete[section]))
|
|
elif section in self.deprecated:
|
|
warnings.warn("%r is deprecated. Use %r instead."
|
|
% (section, self.deprecated[section]))
|
|
|
|
def check_compatibility(self):
|
|
"""Process config and warn on each obsolete or deprecated entry."""
|
|
self._compat(cherrypy.config)
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
self._compat(app.config)
|
|
|
|
# ------------------------ Known Namespaces ------------------------ #
|
|
extra_config_namespaces = []
|
|
|
|
def _known_ns(self, app):
|
|
ns = ["wsgi"]
|
|
ns.extend(copykeys(app.toolboxes))
|
|
ns.extend(copykeys(app.namespaces))
|
|
ns.extend(copykeys(app.request_class.namespaces))
|
|
ns.extend(copykeys(cherrypy.config.namespaces))
|
|
ns += self.extra_config_namespaces
|
|
|
|
for section, conf in app.config.items():
|
|
is_path_section = section.startswith("/")
|
|
if is_path_section and isinstance(conf, dict):
|
|
for k, v in conf.items():
|
|
atoms = k.split(".")
|
|
if len(atoms) > 1:
|
|
if atoms[0] not in ns:
|
|
# Spit out a special warning if a known
|
|
# namespace is preceded by "cherrypy."
|
|
if atoms[0] == "cherrypy" and atoms[1] in ns:
|
|
msg = (
|
|
"The config entry %r is invalid; "
|
|
"try %r instead.\nsection: [%s]"
|
|
% (k, ".".join(atoms[1:]), section))
|
|
else:
|
|
msg = (
|
|
"The config entry %r is invalid, "
|
|
"because the %r config namespace "
|
|
"is unknown.\n"
|
|
"section: [%s]" % (k, atoms[0], section))
|
|
warnings.warn(msg)
|
|
elif atoms[0] == "tools":
|
|
if atoms[1] not in dir(cherrypy.tools):
|
|
msg = (
|
|
"The config entry %r may be invalid, "
|
|
"because the %r tool was not found.\n"
|
|
"section: [%s]" % (k, atoms[1], section))
|
|
warnings.warn(msg)
|
|
|
|
def check_config_namespaces(self):
|
|
"""Process config and warn on each unknown config namespace."""
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
self._known_ns(app)
|
|
|
|
# -------------------------- Config Types -------------------------- #
|
|
known_config_types = {}
|
|
|
|
def _populate_known_types(self):
|
|
b = [x for x in vars(builtins).values()
|
|
if type(x) is type(str)]
|
|
|
|
def traverse(obj, namespace):
|
|
for name in dir(obj):
|
|
# Hack for 3.2's warning about body_params
|
|
if name == 'body_params':
|
|
continue
|
|
vtype = type(getattr(obj, name, None))
|
|
if vtype in b:
|
|
self.known_config_types[namespace + "." + name] = vtype
|
|
|
|
traverse(cherrypy.request, "request")
|
|
traverse(cherrypy.response, "response")
|
|
traverse(cherrypy.server, "server")
|
|
traverse(cherrypy.engine, "engine")
|
|
traverse(cherrypy.log, "log")
|
|
|
|
def _known_types(self, config):
|
|
msg = ("The config entry %r in section %r is of type %r, "
|
|
"which does not match the expected type %r.")
|
|
|
|
for section, conf in config.items():
|
|
if isinstance(conf, dict):
|
|
for k, v in conf.items():
|
|
if v is not None:
|
|
expected_type = self.known_config_types.get(k, None)
|
|
vtype = type(v)
|
|
if expected_type and vtype != expected_type:
|
|
warnings.warn(msg % (k, section, vtype.__name__,
|
|
expected_type.__name__))
|
|
else:
|
|
k, v = section, conf
|
|
if v is not None:
|
|
expected_type = self.known_config_types.get(k, None)
|
|
vtype = type(v)
|
|
if expected_type and vtype != expected_type:
|
|
warnings.warn(msg % (k, section, vtype.__name__,
|
|
expected_type.__name__))
|
|
|
|
def check_config_types(self):
|
|
"""Assert that config values are of the same type as default values."""
|
|
self._known_types(cherrypy.config)
|
|
for sn, app in cherrypy.tree.apps.items():
|
|
if not isinstance(app, cherrypy.Application):
|
|
continue
|
|
self._known_types(app.config)
|
|
|
|
# -------------------- Specific config warnings -------------------- #
|
|
def check_localhost(self):
|
|
"""Warn if any socket_host is 'localhost'. See #711."""
|
|
for k, v in cherrypy.config.items():
|
|
if k == 'server.socket_host' and v == 'localhost':
|
|
warnings.warn("The use of 'localhost' as a socket host can "
|
|
"cause problems on newer systems, since "
|
|
"'localhost' can map to either an IPv4 or an "
|
|
"IPv6 address. You should use '127.0.0.1' "
|
|
"or '[::1]' instead.")
|