~ubuntu-branches/debian/jessie/cherrypy3/jessie

« back to all changes in this revision

Viewing changes to cherrypy/_cperror.py

  • Committer: Package Import Robot
  • Author(s): Gustavo Noronha Silva, JCF Ploemen, Stéphane Graber, Gustavo Noronha
  • Date: 2012-01-06 10:13:27 UTC
  • mfrom: (1.1.4) (7.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20120106101327-smxnhguqs14ubl7e
Tags: 3.2.2-1
[ JCF Ploemen ]
* New upstream release (Closes: #571196).
* Bumped Standards-Version to 3.8.4 (no changes needed).
* Removing patch 02: no longer needed, incorporated upstream.
* Updating patch 00 to match release.
* Install cherryd man page via debian/manpages.
* debian/copyright:
  + Added notice for cherrypy/lib/httpauth.py.
  + Fixed years.
* debian/watch:
  + Don't hit on the -py3 release by blocking '-' from the version.
  + Mangle upstream version, inserting a tilde for beta/rc.

[ Stéphane Graber <stgraber@ubuntu.com> ]
 * Convert from python-support to dh_python2 (#654375)
  - debian/pyversions: Removed (no longer needed)
  - debian/rules
   + Replace call to dh_pysupport by dh_python2
   + Add --with=python2 to all dh calls
  - debian/control
   + Drop build-depends on python-support
   + Bump build-depends on python-all to >= 2.6.6-3~
   + Replace XS-Python-Version by X-Python-Version
   + Remove XB-Python-Version from binary package

[ Gustavo Noronha ]
* debian/control, debian/rules, debian/manpages:
 - use help2man to generate a manpage for cherryd at build time, since
  one is no longer shipped along with the source code
* debian/control:
- add python-nose to Build-Depends, since it's used during the
  documentation build for cross-reference generation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""Error classes for CherryPy."""
 
1
"""Exception classes for CherryPy.
 
2
 
 
3
CherryPy provides (and uses) exceptions for declaring that the HTTP response
 
4
should be a status other than the default "200 OK". You can ``raise`` them like
 
5
normal Python exceptions. You can also call them and they will raise themselves;
 
6
this means you can set an :class:`HTTPError<cherrypy._cperror.HTTPError>`
 
7
or :class:`HTTPRedirect<cherrypy._cperror.HTTPRedirect>` as the
 
8
:attr:`request.handler<cherrypy._cprequest.Request.handler>`.
 
9
 
 
10
.. _redirectingpost:
 
11
 
 
12
Redirecting POST
 
13
================
 
14
 
 
15
When you GET a resource and are redirected by the server to another Location,
 
16
there's generally no problem since GET is both a "safe method" (there should
 
17
be no side-effects) and an "idempotent method" (multiple calls are no different
 
18
than a single call).
 
19
 
 
20
POST, however, is neither safe nor idempotent--if you
 
21
charge a credit card, you don't want to be charged twice by a redirect!
 
22
 
 
23
For this reason, *none* of the 3xx responses permit a user-agent (browser) to
 
24
resubmit a POST on redirection without first confirming the action with the user:
 
25
 
 
26
=====    =================================    ===========
 
27
300      Multiple Choices                     Confirm with the user
 
28
301      Moved Permanently                    Confirm with the user
 
29
302      Found (Object moved temporarily)     Confirm with the user
 
30
303      See Other                            GET the new URI--no confirmation
 
31
304      Not modified                         (for conditional GET only--POST should not raise this error)
 
32
305      Use Proxy                            Confirm with the user
 
33
307      Temporary Redirect                   Confirm with the user
 
34
=====    =================================    ===========
 
35
 
 
36
However, browsers have historically implemented these restrictions poorly;
 
37
in particular, many browsers do not force the user to confirm 301, 302
 
38
or 307 when redirecting POST. For this reason, CherryPy defaults to 303,
 
39
which most user-agents appear to have implemented correctly. Therefore, if
 
40
you raise HTTPRedirect for a POST request, the user-agent will most likely
 
41
attempt to GET the new URI (without asking for confirmation from the user).
 
42
We realize this is confusing for developers, but it's the safest thing we
 
43
could do. You are of course free to raise ``HTTPRedirect(uri, status=302)``
 
44
or any other 3xx status if you know what you're doing, but given the
 
45
environment, we couldn't let any of those be the default.
 
46
 
 
47
Custom Error Handling
 
48
=====================
 
49
 
 
50
.. image:: /refman/cperrors.gif
 
51
 
 
52
Anticipated HTTP responses
 
53
--------------------------
 
54
 
 
55
The 'error_page' config namespace can be used to provide custom HTML output for
 
56
expected responses (like 404 Not Found). Supply a filename from which the output
 
57
will be read. The contents will be interpolated with the values %(status)s,
 
58
%(message)s, %(traceback)s, and %(version)s using plain old Python
 
59
`string formatting <http://www.python.org/doc/2.6.4/library/stdtypes.html#string-formatting-operations>`_.
 
60
 
 
61
::
 
62
 
 
63
    _cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")}
 
64
 
 
65
 
 
66
Beginning in version 3.1, you may also provide a function or other callable as
 
67
an error_page entry. It will be passed the same status, message, traceback and
 
68
version arguments that are interpolated into templates::
 
69
 
 
70
    def error_page_402(status, message, traceback, version):
 
71
        return "Error %s - Well, I'm very sorry but you haven't paid!" % status
 
72
    cherrypy.config.update({'error_page.402': error_page_402})
 
73
 
 
74
Also in 3.1, in addition to the numbered error codes, you may also supply
 
75
"error_page.default" to handle all codes which do not have their own error_page entry.
 
76
 
 
77
 
 
78
 
 
79
Unanticipated errors
 
80
--------------------
 
81
 
 
82
CherryPy also has a generic error handling mechanism: whenever an unanticipated
 
83
error occurs in your code, it will call
 
84
:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to set
 
85
the response status, headers, and body. By default, this is the same output as
 
86
:class:`HTTPError(500) <cherrypy._cperror.HTTPError>`. If you want to provide
 
87
some other behavior, you generally replace "request.error_response".
 
88
 
 
89
Here is some sample code that shows how to display a custom error message and
 
90
send an e-mail containing the error::
 
91
 
 
92
    from cherrypy import _cperror
 
93
 
 
94
    def handle_error():
 
95
        cherrypy.response.status = 500
 
96
        cherrypy.response.body = ["<html><body>Sorry, an error occured</body></html>"]
 
97
        sendMail('error@domain.com', 'Error in your web app', _cperror.format_exc())
 
98
 
 
99
    class Root:
 
100
        _cp_config = {'request.error_response': handle_error}
 
101
 
 
102
 
 
103
Note that you have to explicitly set :attr:`response.body <cherrypy._cprequest.Response.body>`
 
104
and not simply return an error message as a result.
 
105
"""
2
106
 
3
107
from cgi import escape as _escape
4
108
from sys import exc_info as _exc_info
5
109
from traceback import format_exception as _format_exception
6
 
from urlparse import urljoin as _urljoin
7
 
from cherrypy.lib import http as _http
 
110
from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
 
111
from cherrypy.lib import httputil as _httputil
8
112
 
9
113
 
10
114
class CherryPyException(Exception):
 
115
    """A base class for CherryPy exceptions."""
11
116
    pass
12
117
 
13
118
 
19
124
class InternalRedirect(CherryPyException):
20
125
    """Exception raised to switch to the handler for a different URL.
21
126
    
22
 
    Any request.params must be supplied in a query string.
 
127
    This exception will redirect processing to another path within the site
 
128
    (without informing the client). Provide the new path as an argument when
 
129
    raising the exception. Provide any params in the querystring for the new URL.
23
130
    """
24
131
    
25
 
    def __init__(self, path):
 
132
    def __init__(self, path, query_string=""):
26
133
        import cherrypy
27
 
        request = cherrypy.request
 
134
        self.request = cherrypy.serving.request
28
135
        
29
 
        self.query_string = ""
 
136
        self.query_string = query_string
30
137
        if "?" in path:
31
138
            # Separate any params included in the path
32
139
            path, self.query_string = path.split("?", 1)
35
142
        #  1. a URL relative to root (e.g. "/dummy")
36
143
        #  2. a URL relative to the current path
37
144
        # Note that any query string will be discarded.
38
 
        path = _urljoin(request.path_info, path)
 
145
        path = _urljoin(self.request.path_info, path)
39
146
        
40
147
        # Set a 'path' member attribute so that code which traps this
41
148
        # error can have access to it.
47
154
class HTTPRedirect(CherryPyException):
48
155
    """Exception raised when the request should be redirected.
49
156
    
 
157
    This exception will force a HTTP redirect to the URL or URL's you give it.
50
158
    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.
 
159
    e.g., HTTPRedirect(newUrl). Multiple URLs are allowed in a list.
 
160
    If a URL is absolute, it will be used as-is. If it is relative, it is
 
161
    assumed to be relative to the current cherrypy.request.path_info.
 
162
 
 
163
    If one of the provided URL is a unicode object, it will be encoded
 
164
    using the default encoding or the one passed in parameter.
 
165
    
 
166
    There are multiple types of redirect, from which you can select via the
 
167
    ``status`` argument. If you do not provide a ``status`` arg, it defaults to
 
168
    303 (or 302 if responding with HTTP/1.0).
 
169
    
 
170
    Examples::
 
171
    
 
172
        raise cherrypy.HTTPRedirect("")
 
173
        raise cherrypy.HTTPRedirect("/abs/path", 307)
 
174
        raise cherrypy.HTTPRedirect(["path1", "path2?a=1&b=2"], 301)
 
175
    
 
176
    See :ref:`redirectingpost` for additional caveats.
54
177
    """
55
178
    
56
 
    def __init__(self, urls, status=None):
 
179
    status = None
 
180
    """The integer HTTP status code to emit."""
 
181
    
 
182
    urls = None
 
183
    """The list of URL's to emit."""
 
184
 
 
185
    encoding = 'utf-8'
 
186
    """The encoding when passed urls are not native strings"""
 
187
    
 
188
    def __init__(self, urls, status=None, encoding=None):
57
189
        import cherrypy
58
 
        request = cherrypy.request
 
190
        request = cherrypy.serving.request
59
191
        
60
192
        if isinstance(urls, basestring):
61
193
            urls = [urls]
62
194
        
63
195
        abs_urls = []
64
196
        for url in urls:
 
197
            url = tonative(url, encoding or self.encoding)
 
198
                
65
199
            # Note that urljoin will "do the right thing" whether url is:
66
200
            #  1. a complete URL with host (e.g. "http://www.example.com/test")
67
201
            #  2. a URL relative to root (e.g. "/dummy")
73
207
        
74
208
        # RFC 2616 indicates a 301 response code fits our goal; however,
75
209
        # browser support for 301 is quite messy. Do 302/303 instead. See
76
 
        # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html
 
210
        # http://www.alanflavell.org.uk/www/post-redirect.html
77
211
        if status is None:
78
212
            if request.protocol >= (1, 1):
79
213
                status = 303
94
228
        HTTPRedirect object and set its output without *raising* the exception.
95
229
        """
96
230
        import cherrypy
97
 
        response = cherrypy.response
 
231
        response = cherrypy.serving.response
98
232
        response.status = status = self.status
99
233
        
100
234
        if status in (300, 301, 302, 303, 307):
101
 
            response.headers['Content-Type'] = "text/html"
 
235
            response.headers['Content-Type'] = "text/html;charset=utf-8"
102
236
            # "The ... URI SHOULD be given by the Location field
103
237
            # in the response."
104
238
            response.headers['Location'] = self.urls[0]
112
246
                   303: "This resource can be found at <a href='%s'>%s</a>.",
113
247
                   307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
114
248
                   }[status]
115
 
            response.body = "<br />\n".join([msg % (u, u) for u in self.urls])
 
249
            msgs = [msg % (u, u) for u in self.urls]
 
250
            response.body = ntob("<br />\n".join(msgs), 'utf-8')
116
251
            # Previous code may have set C-L, so we have to reset it
117
252
            # (allow finalize to set it).
118
253
            response.headers.pop('Content-Length', None)
153
288
    """Remove any headers which should not apply to an error response."""
154
289
    import cherrypy
155
290
    
156
 
    response = cherrypy.response
 
291
    response = cherrypy.serving.response
157
292
    
158
293
    # Remove headers which applied to the original content,
159
294
    # but do not apply to the error page.
161
296
    for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
162
297
                "Vary", "Content-Encoding", "Content-Length", "Expires",
163
298
                "Content-Location", "Content-MD5", "Last-Modified"]:
164
 
        if respheaders.has_key(key):
 
299
        if key in respheaders:
165
300
            del respheaders[key]
166
301
    
167
302
    if status != 416:
171
306
        # specifies the current length of the selected resource.
172
307
        # A response with status code 206 (Partial Content) MUST NOT
173
308
        # include a Content-Range field with a byte-range- resp-spec of "*".
174
 
        if respheaders.has_key("Content-Range"):
 
309
        if "Content-Range" in respheaders:
175
310
            del respheaders["Content-Range"]
176
311
 
177
312
 
178
313
class HTTPError(CherryPyException):
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.
 
314
    """Exception used to return an HTTP error code (4xx-5xx) to the client.
 
315
    
 
316
    This exception can be used to automatically send a response using a http status
 
317
    code, with an appropriate error page. It takes an optional
 
318
    ``status`` argument (which must be between 400 and 599); it defaults to 500
 
319
    ("Internal Server Error"). It also takes an optional ``message`` argument,
 
320
    which will be returned in the response body. See
 
321
    `RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_
 
322
    for a complete list of available error codes and when to use them.
 
323
    
 
324
    Examples::
181
325
        
182
 
        A custom message (a long description to display in the browser)
183
 
        can be provided in place of the default.
 
326
        raise cherrypy.HTTPError(403)
 
327
        raise cherrypy.HTTPError("403 Forbidden", "You are not allowed to access this resource.")
184
328
    """
185
329
    
 
330
    status = None
 
331
    """The HTTP status code. May be of type int or str (with a Reason-Phrase)."""
 
332
    
 
333
    code = None
 
334
    """The integer HTTP status code."""
 
335
    
 
336
    reason = None
 
337
    """The HTTP Reason-Phrase string."""
 
338
    
186
339
    def __init__(self, status=500, message=None):
187
 
        self.status = status = int(status)
188
 
        if status < 400 or status > 599:
 
340
        self.status = status
 
341
        try:
 
342
            self.code, self.reason, defaultmsg = _httputil.valid_status(status)
 
343
        except ValueError:
 
344
            raise self.__class__(500, _exc_info()[1].args[0])
 
345
        
 
346
        if self.code < 400 or self.code > 599:
189
347
            raise ValueError("status must be between 400 and 599.")
 
348
        
190
349
        # See http://www.python.org/dev/peps/pep-0352/
191
350
        # self.message = message
192
 
        self._message = message
 
351
        self._message = message or defaultmsg
193
352
        CherryPyException.__init__(self, status, message)
194
353
    
195
354
    def set_response(self):
200
359
        """
201
360
        import cherrypy
202
361
        
203
 
        response = cherrypy.response
 
362
        response = cherrypy.serving.response
204
363
        
205
 
        clean_headers(self.status)
 
364
        clean_headers(self.code)
206
365
        
207
366
        # In all cases, finalize will be called after this method,
208
367
        # so don't bother cleaning up response values here.
209
368
        response.status = self.status
210
369
        tb = None
211
 
        if cherrypy.request.show_tracebacks:
 
370
        if cherrypy.serving.request.show_tracebacks:
212
371
            tb = format_exc()
213
 
        response.headers['Content-Type'] = "text/html"
 
372
        response.headers['Content-Type'] = "text/html;charset=utf-8"
 
373
        response.headers.pop('Content-Length', None)
214
374
        
215
 
        content = self.get_error_page(self.status, traceback=tb,
216
 
                                      message=self._message)
 
375
        content = ntob(self.get_error_page(self.status, traceback=tb,
 
376
                                           message=self._message), 'utf-8')
217
377
        response.body = content
218
 
        response.headers['Content-Length'] = len(content)
219
378
        
220
 
        _be_ie_unfriendly(self.status)
 
379
        _be_ie_unfriendly(self.code)
221
380
    
222
381
    def get_error_page(self, *args, **kwargs):
223
382
        return get_error_page(*args, **kwargs)
228
387
 
229
388
 
230
389
class NotFound(HTTPError):
231
 
    """Exception raised when a URL could not be mapped to any handler (404)."""
 
390
    """Exception raised when a URL could not be mapped to any handler (404).
 
391
    
 
392
    This is equivalent to raising
 
393
    :class:`HTTPError("404 Not Found") <cherrypy._cperror.HTTPError>`.
 
394
    """
232
395
    
233
396
    def __init__(self, path=None):
234
397
        if path is None:
235
398
            import cherrypy
236
 
            path = cherrypy.request.script_name + cherrypy.request.path_info
 
399
            request = cherrypy.serving.request
 
400
            path = request.script_name + request.path_info
237
401
        self.args = (path,)
238
 
        HTTPError.__init__(self, 404, "The path %r was not found." % path)
 
402
        HTTPError.__init__(self, 404, "The path '%s' was not found." % path)
239
403
 
240
404
 
241
405
_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
276
440
    import cherrypy
277
441
    
278
442
    try:
279
 
        code, reason, message = _http.valid_status(status)
280
 
    except ValueError, x:
281
 
        raise cherrypy.HTTPError(500, x.args[0])
 
443
        code, reason, message = _httputil.valid_status(status)
 
444
    except ValueError:
 
445
        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
282
446
    
283
447
    # We can't use setdefault here, because some
284
448
    # callers send None for kwarg values.
291
455
    if kwargs.get('version') is None:
292
456
        kwargs['version'] = cherrypy.__version__
293
457
    
294
 
    for k, v in kwargs.iteritems():
 
458
    for k, v in iteritems(kwargs):
295
459
        if v is None:
296
460
            kwargs[k] = ""
297
461
        else:
298
462
            kwargs[k] = _escape(kwargs[k])
299
463
    
300
464
    # Use a custom template or callable for the error page?
301
 
    pages = cherrypy.request.error_page
 
465
    pages = cherrypy.serving.request.error_page
302
466
    error_page = pages.get(code) or pages.get('default')
303
467
    if error_page:
304
468
        try:
305
 
            if callable(error_page):
 
469
            if hasattr(error_page, '__call__'):
306
470
                return error_page(**kwargs)
307
471
            else:
308
 
                return file(error_page, 'rb').read() % kwargs
 
472
                data = open(error_page, 'rb').read()
 
473
                return tonative(data) % kwargs
309
474
        except:
310
475
            e = _format_exception(*_exc_info())[-1]
311
476
            m = kwargs['message']
326
491
 
327
492
def _be_ie_unfriendly(status):
328
493
    import cherrypy
329
 
    response = cherrypy.response
 
494
    response = cherrypy.serving.response
330
495
    
331
496
    # For some statuses, Internet Explorer 5+ shows "friendly error
332
497
    # messages" instead of our response.body if the body is smaller
343
508
        if l and l < s:
344
509
            # IN ADDITION: the response must be written to IE
345
510
            # in one chunk or it will still get replaced! Bah.
346
 
            content = content + (" " * (s - l))
 
511
            content = content + (ntob(" ") * (s - l))
347
512
        response.body = content
348
 
        response.headers['Content-Length'] = len(content)
 
513
        response.headers['Content-Length'] = str(len(content))
349
514
 
350
515
 
351
516
def format_exc(exc=None):
352
517
    """Return exc (or sys.exc_info if None), formatted."""
353
 
    if exc is None:
354
 
        exc = _exc_info()
355
 
    if exc == (None, None, None):
356
 
        return ""
357
 
    import traceback
358
 
    return "".join(traceback.format_exception(*exc))
 
518
    try:
 
519
        if exc is None:
 
520
            exc = _exc_info()
 
521
        if exc == (None, None, None):
 
522
            return ""
 
523
        import traceback
 
524
        return "".join(traceback.format_exception(*exc))
 
525
    finally:
 
526
        del exc
359
527
 
360
528
def bare_error(extrabody=None):
361
529
    """Produce status, headers, body for a critical error.
374
542
    # it cannot be allowed to fail. Therefore, don't add to it!
375
543
    # In particular, don't call any other CP functions.
376
544
    
377
 
    body = "Unrecoverable error in the server."
 
545
    body = ntob("Unrecoverable error in the server.")
378
546
    if extrabody is not None:
379
 
        body += "\n" + extrabody
 
547
        if not isinstance(extrabody, bytestr):
 
548
            extrabody = extrabody.encode('utf-8')
 
549
        body += ntob("\n") + extrabody
380
550
    
381
 
    return ("500 Internal Server Error",
382
 
            [('Content-Type', 'text/plain'),
383
 
             ('Content-Length', str(len(body)))],
 
551
    return (ntob("500 Internal Server Error"),
 
552
            [(ntob('Content-Type'), ntob('text/plain')),
 
553
             (ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
384
554
            [body])
385
555
 
386
556