| 1 |
import os |
|---|
| 2 |
import warnings |
|---|
| 3 |
|
|---|
| 4 |
import cherrypy |
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
class Checker(object): |
|---|
| 8 |
"""A checker for CherryPy sites and their mounted applications. |
|---|
| 9 |
|
|---|
| 10 |
on: set this to False to turn off the checker completely. |
|---|
| 11 |
|
|---|
| 12 |
When this object is called at engine startup, it executes each |
|---|
| 13 |
of its own methods whose names start with "check_". If you wish |
|---|
| 14 |
to disable selected checks, simply add a line in your global |
|---|
| 15 |
config which sets the appropriate method to False: |
|---|
| 16 |
|
|---|
| 17 |
[global] |
|---|
| 18 |
checker.check_skipped_app_config = False |
|---|
| 19 |
|
|---|
| 20 |
You may also dynamically add or replace check_* methods in this way. |
|---|
| 21 |
""" |
|---|
| 22 |
|
|---|
| 23 |
on = True |
|---|
| 24 |
|
|---|
| 25 |
def __init__(self): |
|---|
| 26 |
self._populate_known_types() |
|---|
| 27 |
|
|---|
| 28 |
def __call__(self): |
|---|
| 29 |
"""Run all check_* methods.""" |
|---|
| 30 |
if self.on: |
|---|
| 31 |
oldformatwarning = warnings.formatwarning |
|---|
| 32 |
warnings.formatwarning = self.formatwarning |
|---|
| 33 |
try: |
|---|
| 34 |
for name in dir(self): |
|---|
| 35 |
if name.startswith("check_"): |
|---|
| 36 |
method = getattr(self, name) |
|---|
| 37 |
if method and callable(method): |
|---|
| 38 |
method() |
|---|
| 39 |
finally: |
|---|
| 40 |
warnings.formatwarning = oldformatwarning |
|---|
| 41 |
|
|---|
| 42 |
def formatwarning(self, message, category, filename, lineno): |
|---|
| 43 |
"""Function to format a warning.""" |
|---|
| 44 |
return "CherryPy Checker:\n%s\n\n" % message |
|---|
| 45 |
|
|---|
| 46 |
|
|---|
| 47 |
global_config_contained_paths = False |
|---|
| 48 |
|
|---|
| 49 |
def check_skipped_app_config(self): |
|---|
| 50 |
for sn, app in cherrypy.tree.apps.iteritems(): |
|---|
| 51 |
if not isinstance(app, cherrypy.Application): |
|---|
| 52 |
continue |
|---|
| 53 |
if not app.config: |
|---|
| 54 |
msg = "The Application mounted at %r has an empty config." % sn |
|---|
| 55 |
if self.global_config_contained_paths: |
|---|
| 56 |
msg += (" It looks like the config you passed to " |
|---|
| 57 |
"cherrypy.config.update() contains application-" |
|---|
| 58 |
"specific sections. You must explicitly pass " |
|---|
| 59 |
"application config via " |
|---|
| 60 |
"cherrypy.tree.mount(..., config=app_config)") |
|---|
| 61 |
warnings.warn(msg) |
|---|
| 62 |
return |
|---|
| 63 |
|
|---|
| 64 |
def check_static_paths(self): |
|---|
| 65 |
|
|---|
| 66 |
request = cherrypy.request |
|---|
| 67 |
for sn, app in cherrypy.tree.apps.iteritems(): |
|---|
| 68 |
if not isinstance(app, cherrypy.Application): |
|---|
| 69 |
continue |
|---|
| 70 |
request.app = app |
|---|
| 71 |
for section in app.config: |
|---|
| 72 |
|
|---|
| 73 |
request.get_resource(section + "/dummy.html") |
|---|
| 74 |
conf = request.config.get |
|---|
| 75 |
|
|---|
| 76 |
if conf("tools.staticdir.on", False): |
|---|
| 77 |
msg = "" |
|---|
| 78 |
root = conf("tools.staticdir.root") |
|---|
| 79 |
dir = conf("tools.staticdir.dir") |
|---|
| 80 |
if dir is None: |
|---|
| 81 |
msg = "tools.staticdir.dir is not set." |
|---|
| 82 |
else: |
|---|
| 83 |
fulldir = "" |
|---|
| 84 |
if os.path.isabs(dir): |
|---|
| 85 |
fulldir = dir |
|---|
| 86 |
if root: |
|---|
| 87 |
msg = ("dir is an absolute path, even " |
|---|
| 88 |
"though a root is provided.") |
|---|
| 89 |
testdir = os.path.join(root, dir[1:]) |
|---|
| 90 |
if os.path.exists(testdir): |
|---|
| 91 |
msg += ("\nIf you meant to serve the " |
|---|
| 92 |
"filesystem folder at %r, remove " |
|---|
| 93 |
"the leading slash from dir." % testdir) |
|---|
| 94 |
else: |
|---|
| 95 |
if not root: |
|---|
| 96 |
msg = "dir is a relative path and no root provided." |
|---|
| 97 |
else: |
|---|
| 98 |
fulldir = os.path.join(root, dir) |
|---|
| 99 |
if not os.path.isabs(fulldir): |
|---|
| 100 |
msg = "%r is not an absolute path." % fulldir |
|---|
| 101 |
|
|---|
| 102 |
if fulldir and not os.path.exists(fulldir): |
|---|
| 103 |
if msg: |
|---|
| 104 |
msg += "\n" |
|---|
| 105 |
msg += ("%r (root + dir) is not an existing " |
|---|
| 106 |
"filesystem path." % fulldir) |
|---|
| 107 |
|
|---|
| 108 |
if msg: |
|---|
| 109 |
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r" |
|---|
| 110 |
% (msg, section, root, dir)) |
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 |
obsolete = { |
|---|
| 116 |
'server.default_content_type': 'tools.response_headers.headers', |
|---|
| 117 |
'log_access_file': 'log.access_file', |
|---|
| 118 |
'log_config_options': None, |
|---|
| 119 |
'log_file': 'log.error_file', |
|---|
| 120 |
'log_file_not_found': None, |
|---|
| 121 |
'log_request_headers': 'tools.log_headers.on', |
|---|
| 122 |
'log_to_screen': 'log.screen', |
|---|
| 123 |
'show_tracebacks': 'request.show_tracebacks', |
|---|
| 124 |
'throw_errors': 'request.throw_errors', |
|---|
| 125 |
'profiler.on': ('cherrypy.tree.mount(profiler.make_app(' |
|---|
| 126 |
'cherrypy.Application(Root())))'), |
|---|
| 127 |
} |
|---|
| 128 |
|
|---|
| 129 |
deprecated = {} |
|---|
| 130 |
|
|---|
| 131 |
def _compat(self, config): |
|---|
| 132 |
"""Process config and warn on each obsolete or deprecated entry.""" |
|---|
| 133 |
for section, conf in config.iteritems(): |
|---|
| 134 |
if isinstance(conf, dict): |
|---|
| 135 |
for k, v in conf.iteritems(): |
|---|
| 136 |
if k in self.obsolete: |
|---|
| 137 |
warnings.warn("%r is obsolete. Use %r instead.\n" |
|---|
| 138 |
"section: [%s]" % |
|---|
| 139 |
(k, self.obsolete[k], section)) |
|---|
| 140 |
elif k in self.deprecated: |
|---|
| 141 |
warnings.warn("%r is deprecated. Use %r instead.\n" |
|---|
| 142 |
"section: [%s]" % |
|---|
| 143 |
(k, self.deprecated[k], section)) |
|---|
| 144 |
else: |
|---|
| 145 |
if section in self.obsolete: |
|---|
| 146 |
warnings.warn("%r is obsolete. Use %r instead." |
|---|
| 147 |
% (section, self.obsolete[section])) |
|---|
| 148 |
elif section in self.deprecated: |
|---|
| 149 |
warnings.warn("%r is deprecated. Use %r instead." |
|---|
| 150 |
% (section, self.deprecated[section])) |
|---|
| 151 |
|
|---|
| 152 |
def check_compatibility(self): |
|---|
| 153 |
"""Process config and warn on each obsolete or deprecated entry.""" |
|---|
| 154 |
self._compat(cherrypy.config) |
|---|
| 155 |
for sn, app in cherrypy.tree.apps.iteritems(): |
|---|
| 156 |
if not isinstance(app, cherrypy.Application): |
|---|
| 157 |
continue |
|---|
| 158 |
self._compat(app.config) |
|---|
| 159 |
|
|---|
| 160 |
|
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
extra_config_namespaces = [] |
|---|
| 164 |
|
|---|
| 165 |
def _known_ns(self, app): |
|---|
| 166 |
ns = ["wsgi"] |
|---|
| 167 |
ns.extend(app.toolboxes.keys()) |
|---|
| 168 |
ns.extend(app.namespaces.keys()) |
|---|
| 169 |
ns.extend(app.request_class.namespaces.keys()) |
|---|
| 170 |
ns.extend(cherrypy.config.namespaces.keys()) |
|---|
| 171 |
ns += self.extra_config_namespaces |
|---|
| 172 |
|
|---|
| 173 |
for section, conf in app.config.iteritems(): |
|---|
| 174 |
is_path_section = section.startswith("/") |
|---|
| 175 |
if is_path_section and isinstance(conf, dict): |
|---|
| 176 |
for k, v in conf.iteritems(): |
|---|
| 177 |
atoms = k.split(".") |
|---|
| 178 |
if len(atoms) > 1: |
|---|
| 179 |
if atoms[0] not in ns: |
|---|
| 180 |
|
|---|
| 181 |
|
|---|
| 182 |
if (atoms[0] == "cherrypy" and atoms[1] in ns): |
|---|
| 183 |
msg = ("The config entry %r is invalid; " |
|---|
| 184 |
"try %r instead.\nsection: [%s]" |
|---|
| 185 |
% (k, ".".join(atoms[1:]), section)) |
|---|
| 186 |
else: |
|---|
| 187 |
msg = ("The config entry %r is invalid, because " |
|---|
| 188 |
"the %r config namespace is unknown.\n" |
|---|
| 189 |
"section: [%s]" % (k, atoms[0], section)) |
|---|
| 190 |
warnings.warn(msg) |
|---|
| 191 |
elif atoms[0] == "tools": |
|---|
| 192 |
if atoms[1] not in dir(cherrypy.tools): |
|---|
| 193 |
msg = ("The config entry %r may be invalid, " |
|---|
| 194 |
"because the %r tool was not found.\n" |
|---|
| 195 |
"section: [%s]" % (k, atoms[1], section)) |
|---|
| 196 |
warnings.warn(msg) |
|---|
| 197 |
|
|---|
| 198 |
def check_config_namespaces(self): |
|---|
| 199 |
"""Process config and warn on each unknown config namespace.""" |
|---|
| 200 |
for sn, app in cherrypy.tree.apps.iteritems(): |
|---|
| 201 |
if not isinstance(app, cherrypy.Application): |
|---|
| 202 |
continue |
|---|
| 203 |
self._known_ns(app) |
|---|
| 204 |
|
|---|
| 205 |
|
|---|
| 206 |
|
|---|
| 207 |
|
|---|
| 208 |
|
|---|
| 209 |
|
|---|
| 210 |
known_config_types = {} |
|---|
| 211 |
|
|---|
| 212 |
def _populate_known_types(self): |
|---|
| 213 |
import __builtin__ |
|---|
| 214 |
builtins = [x for x in vars(__builtin__).values() |
|---|
| 215 |
if type(x) is type(str)] |
|---|
| 216 |
|
|---|
| 217 |
def traverse(obj, namespace): |
|---|
| 218 |
for name in dir(obj): |
|---|
| 219 |
vtype = type(getattr(obj, name, None)) |
|---|
| 220 |
if vtype in builtins: |
|---|
| 221 |
self.known_config_types[namespace + "." + name] = vtype |
|---|
| 222 |
|
|---|
| 223 |
traverse(cherrypy.request, "request") |
|---|
| 224 |
traverse(cherrypy.response, "response") |
|---|
| 225 |
traverse(cherrypy.server, "server") |
|---|
| 226 |
traverse(cherrypy.engine, "engine") |
|---|
| 227 |
traverse(cherrypy.log, "log") |
|---|
| 228 |
|
|---|
| 229 |
def _known_types(self, config): |
|---|
| 230 |
msg = ("The config entry %r in section %r is of type %r, " |
|---|
| 231 |
"which does not match the expected type %r.") |
|---|
| 232 |
|
|---|
| 233 |
for section, conf in config.iteritems(): |
|---|
| 234 |
if isinstance(conf, dict): |
|---|
| 235 |
for k, v in conf.iteritems(): |
|---|
| 236 |
if v is not None: |
|---|
| 237 |
expected_type = self.known_config_types.get(k, None) |
|---|
| 238 |
vtype = type(v) |
|---|
| 239 |
if expected_type and vtype != expected_type: |
|---|
| 240 |
warnings.warn(msg % (k, section, vtype.__name__, |
|---|
| 241 |
expected_type.__name__)) |
|---|
| 242 |
else: |
|---|
| 243 |
k, v = section, conf |
|---|
| 244 |
if v is not None: |
|---|
| 245 |
expected_type = self.known_config_types.get(k, None) |
|---|
| 246 |
vtype = type(v) |
|---|
| 247 |
if expected_type and vtype != expected_type: |
|---|
| 248 |
warnings.warn(msg % (k, section, vtype.__name__, |
|---|
| 249 |
expected_type.__name__)) |
|---|
| 250 |
|
|---|
| 251 |
def check_config_types(self): |
|---|
| 252 |
"""Assert that config values are of the same type as default values.""" |
|---|
| 253 |
self._known_types(cherrypy.config) |
|---|
| 254 |
for sn, app in cherrypy.tree.apps.iteritems(): |
|---|
| 255 |
if not isinstance(app, cherrypy.Application): |
|---|
| 256 |
continue |
|---|
| 257 |
self._known_types(app.config) |
|---|
| 258 |
|
|---|
| 259 |
|
|---|
| 260 |
|
|---|
| 261 |
|
|---|
| 262 |
def check_localhost(self): |
|---|
| 263 |
"""Warn if any socket_host is 'localhost'. See #711.""" |
|---|
| 264 |
for k, v in cherrypy.config.iteritems(): |
|---|
| 265 |
if k == 'server.socket_host' and v == 'localhost': |
|---|
| 266 |
warnings.warn("The use of 'localhost' as a socket host can " |
|---|
| 267 |
"cause problems on newer systems, since 'localhost' can " |
|---|
| 268 |
"map to either an IPv4 or an IPv6 address. You should " |
|---|
| 269 |
"use '127.0.0.1' or '[::1]' instead.") |
|---|