5 from cherrypy
._cpcompat
import iteritems
, copykeys
, builtins
9 """A checker for CherryPy sites and their mounted applications.
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::
17 checker.check_skipped_app_config = False
19 You may also dynamically add or replace ``check_*`` methods in this way.
23 """If True (the default), run all checks; if False, turn off all checks."""
27 self
._populate
_known
_types
()
30 """Run all check_* methods."""
32 oldformatwarning
= warnings
.formatwarning
33 warnings
.formatwarning
= self
.formatwarning
35 for name
in dir(self
):
36 if name
.startswith("check_"):
37 method
= getattr(self
, name
)
38 if method
and hasattr(method
, '__call__'):
41 warnings
.formatwarning
= oldformatwarning
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
47 # This value should be set inside _cpconfig.
48 global_config_contained_paths
= False
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
):
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
:
64 "The application mounted at %r has config " \
65 "entries that start with its script name: %r" % (sn
, key
))
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
):
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."):
79 msg
.append("[%s] %s = %s" % (section
, key
, value
))
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
))
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
):
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)")
104 def check_app_config_brackets(self
):
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
):
111 for key
in app
.config
.keys():
112 if key
.startswith("[") or key
.endswith("]"):
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
))
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
):
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
132 if conf("tools.staticdir.on", False):
134 root
= conf("tools.staticdir.root")
135 dir = conf("tools.staticdir.dir")
137 msg
= "tools.staticdir.dir is not set."
140 if os
.path
.isabs(dir):
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
)
152 msg
= "dir is a relative path and no root provided."
154 fulldir
= os
.path
.join(root
, dir)
155 if not os
.path
.isabs(fulldir
):
156 msg
= "%r is not an absolute path." % fulldir
158 if fulldir
and not os
.path
.exists(fulldir
):
161 msg
+= ("%r (root + dir) is not an existing "
162 "filesystem path." % fulldir
)
165 warnings
.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
166 % (msg
, section
, root
, dir))
169 # -------------------------- Compatibility -------------------------- #
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())))'),
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"
195 (k
, self
.obsolete
[k
], section
))
196 elif k
in self
.deprecated
:
197 warnings
.warn("%r is deprecated. Use %r instead.\n"
199 (k
, self
.deprecated
[k
], section
))
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
]))
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
):
214 self
._compat
(app
.config
)
217 # ------------------------ Known Namespaces ------------------------ #
219 extra_config_namespaces
= []
221 def _known_ns(self
, app
):
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
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():
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
))
243 msg
= ("The config entry %r is invalid, because "
244 "the %r config namespace is unknown.\n"
245 "section: [%s]" % (k
, atoms
[0], section
))
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
))
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
):
264 # -------------------------- Config Types -------------------------- #
266 known_config_types
= {}
268 def _populate_known_types(self
):
269 b
= [x
for x
in vars(builtins
).values()
270 if type(x
) is type(str)]
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':
277 vtype
= type(getattr(obj
, name
, None))
279 self
.known_config_types
[namespace
+ "." + name
] = vtype
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")
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.")
291 for section
, conf
in config
.items():
292 if isinstance(conf
, dict):
293 for k
, v
in conf
.items():
295 expected_type
= self
.known_config_types
.get(k
, None)
297 if expected_type
and vtype
!= expected_type
:
298 warnings
.warn(msg
% (k
, section
, vtype
.__name
__,
299 expected_type
.__name
__))
303 expected_type
= self
.known_config_types
.get(k
, None)
305 if expected_type
and vtype
!= expected_type
:
306 warnings
.warn(msg
% (k
, section
, vtype
.__name
__,
307 expected_type
.__name
__))
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
):
315 self
._known
_types
(app
.config
)
318 # -------------------- Specific config warnings -------------------- #
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.")