1 import os
2 import warnings
3
4 import cherrypy
5 from cherrypy._cpcompat import iteritems, copykeys, builtins
6
7
9
10 """A checker for CherryPy sites and their mounted applications.
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 """If True (the default), run all checks; if False, turn off all checks."""
25
28
42
46
47
48 global_config_contained_paths = False
49
51 """Check for Application config with sections that repeat script_name.
52 """
53 for sn, app in cherrypy.tree.apps.items():
54 if not isinstance(app, cherrypy.Application):
55 continue
56 if not app.config:
57 continue
58 if sn == '':
59 continue
60 sn_atoms = sn.strip("/").split("/")
61 for key in app.config.keys():
62 key_atoms = key.strip("/").split("/")
63 if key_atoms[:len(sn_atoms)] == sn_atoms:
64 warnings.warn(
65 "The application mounted at %r has config "
66 "entries that start with its script name: %r" % (sn,
67 key))
68
70 """Check for mounted Applications that have site-scoped config."""
71 for sn, app in iteritems(cherrypy.tree.apps):
72 if not isinstance(app, cherrypy.Application):
73 continue
74
75 msg = []
76 for section, entries in iteritems(app.config):
77 if section.startswith('/'):
78 for key, value in iteritems(entries):
79 for n in ("engine.", "server.", "tree.", "checker."):
80 if key.startswith(n):
81 msg.append("[%s] %s = %s" %
82 (section, key, value))
83 if msg:
84 msg.insert(0,
85 "The application mounted at %r contains the "
86 "following config entries, which are only allowed "
87 "in site-wide config. Move them to a [global] "
88 "section and pass them to cherrypy.config.update() "
89 "instead of tree.mount()." % sn)
90 warnings.warn(os.linesep.join(msg))
91
93 """Check for mounted Applications that have no config."""
94 for sn, app in cherrypy.tree.apps.items():
95 if not isinstance(app, cherrypy.Application):
96 continue
97 if not app.config:
98 msg = "The Application mounted at %r has an empty config." % sn
99 if self.global_config_contained_paths:
100 msg += (" It looks like the config you passed to "
101 "cherrypy.config.update() contains application-"
102 "specific sections. You must explicitly pass "
103 "application config via "
104 "cherrypy.tree.mount(..., config=app_config)")
105 warnings.warn(msg)
106 return
107
109 """Check for Application config with extraneous brackets in section
110 names.
111 """
112 for sn, app in cherrypy.tree.apps.items():
113 if not isinstance(app, cherrypy.Application):
114 continue
115 if not app.config:
116 continue
117 for key in app.config.keys():
118 if key.startswith("[") or key.endswith("]"):
119 warnings.warn(
120 "The application mounted at %r has config "
121 "section names with extraneous brackets: %r. "
122 "Config *files* need brackets; config *dicts* "
123 "(e.g. passed to tree.mount) do not." % (sn, key))
124
126 """Check Application config for incorrect static paths."""
127
128 request = cherrypy.request
129 for sn, app in cherrypy.tree.apps.items():
130 if not isinstance(app, cherrypy.Application):
131 continue
132 request.app = app
133 for section in app.config:
134
135 request.get_resource(section + "/dummy.html")
136 conf = request.config.get
137
138 if conf("tools.staticdir.on", False):
139 msg = ""
140 root = conf("tools.staticdir.root")
141 dir = conf("tools.staticdir.dir")
142 if dir is None:
143 msg = "tools.staticdir.dir is not set."
144 else:
145 fulldir = ""
146 if os.path.isabs(dir):
147 fulldir = dir
148 if root:
149 msg = ("dir is an absolute path, even "
150 "though a root is provided.")
151 testdir = os.path.join(root, dir[1:])
152 if os.path.exists(testdir):
153 msg += (
154 "\nIf you meant to serve the "
155 "filesystem folder at %r, remove the "
156 "leading slash from dir." % (testdir,))
157 else:
158 if not root:
159 msg = (
160 "dir is a relative path and "
161 "no root provided.")
162 else:
163 fulldir = os.path.join(root, dir)
164 if not os.path.isabs(fulldir):
165 msg = ("%r is not an absolute path." % (
166 fulldir,))
167
168 if fulldir and not os.path.exists(fulldir):
169 if msg:
170 msg += "\n"
171 msg += ("%r (root + dir) is not an existing "
172 "filesystem path." % fulldir)
173
174 if msg:
175 warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
176 % (msg, section, root, dir))
177
178
179 obsolete = {
180 'server.default_content_type': 'tools.response_headers.headers',
181 'log_access_file': 'log.access_file',
182 'log_config_options': None,
183 'log_file': 'log.error_file',
184 'log_file_not_found': None,
185 'log_request_headers': 'tools.log_headers.on',
186 'log_to_screen': 'log.screen',
187 'show_tracebacks': 'request.show_tracebacks',
188 'throw_errors': 'request.throw_errors',
189 'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
190 'cherrypy.Application(Root())))'),
191 }
192
193 deprecated = {}
194
196 """Process config and warn on each obsolete or deprecated entry."""
197 for section, conf in config.items():
198 if isinstance(conf, dict):
199 for k, v in conf.items():
200 if k in self.obsolete:
201 warnings.warn("%r is obsolete. Use %r instead.\n"
202 "section: [%s]" %
203 (k, self.obsolete[k], section))
204 elif k in self.deprecated:
205 warnings.warn("%r is deprecated. Use %r instead.\n"
206 "section: [%s]" %
207 (k, self.deprecated[k], section))
208 else:
209 if section in self.obsolete:
210 warnings.warn("%r is obsolete. Use %r instead."
211 % (section, self.obsolete[section]))
212 elif section in self.deprecated:
213 warnings.warn("%r is deprecated. Use %r instead."
214 % (section, self.deprecated[section]))
215
223
224
225 extra_config_namespaces = []
226
228 ns = ["wsgi"]
229 ns.extend(copykeys(app.toolboxes))
230 ns.extend(copykeys(app.namespaces))
231 ns.extend(copykeys(app.request_class.namespaces))
232 ns.extend(copykeys(cherrypy.config.namespaces))
233 ns += self.extra_config_namespaces
234
235 for section, conf in app.config.items():
236 is_path_section = section.startswith("/")
237 if is_path_section and isinstance(conf, dict):
238 for k, v in conf.items():
239 atoms = k.split(".")
240 if len(atoms) > 1:
241 if atoms[0] not in ns:
242
243
244 if atoms[0] == "cherrypy" and atoms[1] in ns:
245 msg = (
246 "The config entry %r is invalid; "
247 "try %r instead.\nsection: [%s]"
248 % (k, ".".join(atoms[1:]), section))
249 else:
250 msg = (
251 "The config entry %r is invalid, "
252 "because the %r config namespace "
253 "is unknown.\n"
254 "section: [%s]" % (k, atoms[0], section))
255 warnings.warn(msg)
256 elif atoms[0] == "tools":
257 if atoms[1] not in dir(cherrypy.tools):
258 msg = (
259 "The config entry %r may be invalid, "
260 "because the %r tool was not found.\n"
261 "section: [%s]" % (k, atoms[1], section))
262 warnings.warn(msg)
263
270
271
272 known_config_types = {}
273
286
287 traverse(cherrypy.request, "request")
288 traverse(cherrypy.response, "response")
289 traverse(cherrypy.server, "server")
290 traverse(cherrypy.engine, "engine")
291 traverse(cherrypy.log, "log")
292
294 msg = ("The config entry %r in section %r is of type %r, "
295 "which does not match the expected type %r.")
296
297 for section, conf in config.items():
298 if isinstance(conf, dict):
299 for k, v in conf.items():
300 if v is not None:
301 expected_type = self.known_config_types.get(k, None)
302 vtype = type(v)
303 if expected_type and vtype != expected_type:
304 warnings.warn(msg % (k, section, vtype.__name__,
305 expected_type.__name__))
306 else:
307 k, v = section, conf
308 if v is not None:
309 expected_type = self.known_config_types.get(k, None)
310 vtype = type(v)
311 if expected_type and vtype != expected_type:
312 warnings.warn(msg % (k, section, vtype.__name__,
313 expected_type.__name__))
314
322
323
325 """Warn if any socket_host is 'localhost'. See #711."""
326 for k, v in cherrypy.config.items():
327 if k == 'server.socket_host' and v == 'localhost':
328 warnings.warn("The use of 'localhost' as a socket host can "
329 "cause problems on newer systems, since "
330 "'localhost' can map to either an IPv4 or an "
331 "IPv6 address. You should use '127.0.0.1' "
332 "or '[::1]' instead.")
333