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