1 """Error classes for CherryPy."""
2
3 from cgi import escape as _escape
4 from sys import exc_info as _exc_info
5 from traceback import format_exception as _format_exception
6 from urlparse import urljoin as _urljoin
7 from cherrypy.lib import http as _http
8
9
12
13
15 """Exception raised when Response.timed_out is detected."""
16 pass
17
18
20 """Exception raised to switch to the handler for a different URL.
21
22 Any request.params must be supplied in a query string.
23 """
24
45
46
48 """Exception raised when the request should be redirected.
49
50 The new URL must be passed as the first argument to the Exception,
51 e.g., HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL is
52 absolute, it will be used as-is. If it is relative, it is assumed
53 to be relative to the current cherrypy.request.path_info.
54 """
55
89
91 """Modify cherrypy.response status, headers, and body to represent self.
92
93 CherryPy uses this internally, but you can also use it to create an
94 HTTPRedirect object and set its output without *raising* the exception.
95 """
96 import cherrypy
97 response = cherrypy.response
98 response.status = status = self.status
99
100 if status in (300, 301, 302, 303, 307):
101 response.headers['Content-Type'] = "text/html"
102
103
104 response.headers['Location'] = self.urls[0]
105
106
107
108
109 msg = {300: "This resource can be found at <a href='%s'>%s</a>.",
110 301: "This resource has permanently moved to <a href='%s'>%s</a>.",
111 302: "This resource resides temporarily at <a href='%s'>%s</a>.",
112 303: "This resource can be found at <a href='%s'>%s</a>.",
113 307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
114 }[status]
115 response.body = "<br />\n".join([msg % (u, u) for u in self.urls])
116
117
118 response.headers.pop('Content-Length', None)
119 elif status == 304:
120
121
122
123
124
125
126 for key in ('Allow', 'Content-Encoding', 'Content-Language',
127 'Content-Length', 'Content-Location', 'Content-MD5',
128 'Content-Range', 'Content-Type', 'Expires',
129 'Last-Modified'):
130 if key in response.headers:
131 del response.headers[key]
132
133
134 response.body = None
135
136 response.headers.pop('Content-Length', None)
137 elif status == 305:
138
139
140 response.headers['Location'] = self.urls[0]
141 response.body = None
142
143 response.headers.pop('Content-Length', None)
144 else:
145 raise ValueError("The %s status code is unknown." % status)
146
148 """Use this exception as a request.handler (raise self)."""
149 raise self
150
151
153 """Remove any headers which should not apply to an error response."""
154 import cherrypy
155
156 response = cherrypy.response
157
158
159
160 respheaders = response.headers
161 for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
162 "Vary", "Content-Encoding", "Content-Length", "Expires",
163 "Content-Location", "Content-MD5", "Last-Modified"]:
164 if respheaders.has_key(key):
165 del respheaders[key]
166
167 if status != 416:
168
169
170
171
172
173
174 if respheaders.has_key("Content-Range"):
175 del respheaders["Content-Range"]
176
177
179 """ Exception used to return an HTTP error code (4xx-5xx) to the client.
180 This exception will automatically set the response status and body.
181
182 A custom message (a long description to display in the browser)
183 can be provided in place of the default.
184 """
185
186 - def __init__(self, status=500, message=None):
194
221
222 - def get_error_page(self, *args, **kwargs):
224
226 """Use this exception as a request.handler (raise self)."""
227 raise self
228
229
231 """Exception raised when a URL could not be mapped to any handler (404)."""
232
239
240
241 _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
242 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
243 <html>
244 <head>
245 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
246 <title>%(status)s</title>
247 <style type="text/css">
248 #powered_by {
249 margin-top: 20px;
250 border-top: 2px solid black;
251 font-style: italic;
252 }
253
254 #traceback {
255 color: red;
256 }
257 </style>
258 </head>
259 <body>
260 <h2>%(status)s</h2>
261 <p>%(message)s</p>
262 <pre id="traceback">%(traceback)s</pre>
263 <div id="powered_by">
264 <span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span>
265 </div>
266 </body>
267 </html>
268 '''
269
270 -def get_error_page(status, **kwargs):
271 """Return an HTML page, containing a pretty error response.
272
273 status should be an int or a str.
274 kwargs will be interpolated into the page template.
275 """
276 import cherrypy
277
278 try:
279 code, reason, message = _http.valid_status(status)
280 except ValueError, x:
281 raise cherrypy.HTTPError(500, x.args[0])
282
283
284
285 if kwargs.get('status') is None:
286 kwargs['status'] = "%s %s" % (code, reason)
287 if kwargs.get('message') is None:
288 kwargs['message'] = message
289 if kwargs.get('traceback') is None:
290 kwargs['traceback'] = ''
291 if kwargs.get('version') is None:
292 kwargs['version'] = cherrypy.__version__
293
294 for k, v in kwargs.iteritems():
295 if v is None:
296 kwargs[k] = ""
297 else:
298 kwargs[k] = _escape(kwargs[k])
299
300
301 pages = cherrypy.request.error_page
302 error_page = pages.get(code) or pages.get('default')
303 if error_page:
304 try:
305 if callable(error_page):
306 return error_page(**kwargs)
307 else:
308 return file(error_page, 'rb').read() % kwargs
309 except:
310 e = _format_exception(*_exc_info())[-1]
311 m = kwargs['message']
312 if m:
313 m += "<br />"
314 m += "In addition, the custom error page failed:\n<br />%s" % e
315 kwargs['message'] = m
316
317 return _HTTPErrorTemplate % kwargs
318
319
320 _ie_friendly_error_sizes = {
321 400: 512, 403: 256, 404: 512, 405: 256,
322 406: 512, 408: 512, 409: 512, 410: 256,
323 500: 512, 501: 512, 505: 512,
324 }
325
326
349
350
359
361 """Produce status, headers, body for a critical error.
362
363 Returns a triple without calling any other questionable functions,
364 so it should be as error-free as possible. Call it from an HTTP server
365 if you get errors outside of the request.
366
367 If extrabody is None, a friendly but rather unhelpful error message
368 is set in the body. If extrabody is a string, it will be appended
369 as-is to the body.
370 """
371
372
373
374
375
376
377 body = "Unrecoverable error in the server."
378 if extrabody is not None:
379 body += "\n" + extrabody
380
381 return ("500 Internal Server Error",
382 [('Content-Type', 'text/plain'),
383 ('Content-Length', str(len(body)))],
384 [body])
385