1 import os
2 import warnings
3
4 import cherrypy
5 from cherrypy._cpcompat import iteritems, copykeys, builtins
6
7
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
28
42
46
47
48 global_config_contained_paths = False
49
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
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
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
105 """Check for Application config with extraneous brackets in section names."""
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
120 """Check Application config for incorrect static paths."""
121
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
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." % testdir)
150 else:
151 if not root:
152 msg = "dir is a relative path and no root provided."
153 else:
154 fulldir = os.path.join(root, dir)
155 if not os.path.isabs(fulldir):
156 msg = "%r is not an absolute path." % fulldir
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
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
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
215
216
217
218
219 extra_config_namespaces = []
220
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
237
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
260
261
262
263
264
265
266 known_config_types = {}
267
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
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
316
317
318
319
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.")
328