| 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.") |