1"""
2This module processes Python exceptions that relate to HTTP exceptions
3by defining a set of exceptions, all subclasses of HTTPException.
4Each exception, in addition to being a Python exception that can be
5raised and caught, is also a WSGI application and ``webob.Response``
6object.
7
8This module defines exceptions according to RFC 2068 [1]_ : codes with
9100-300 are not really errors; 400's are client errors, and 500's are
10server errors.  According to the WSGI specification [2]_ , the application
11can call ``start_response`` more then once only under two conditions:
12(a) the response has not yet been sent, or (b) if the second and
13subsequent invocations of ``start_response`` have a valid ``exc_info``
14argument obtained from ``sys.exc_info()``.  The WSGI specification then
15requires the server or gateway to handle the case where content has been
16sent and then an exception was encountered.
17
18Exception
19  HTTPException
20    HTTPOk
21      * 200 - :class:`HTTPOk`
22      * 201 - :class:`HTTPCreated`
23      * 202 - :class:`HTTPAccepted`
24      * 203 - :class:`HTTPNonAuthoritativeInformation`
25      * 204 - :class:`HTTPNoContent`
26      * 205 - :class:`HTTPResetContent`
27      * 206 - :class:`HTTPPartialContent`
28    HTTPRedirection
29      * 300 - :class:`HTTPMultipleChoices`
30      * 301 - :class:`HTTPMovedPermanently`
31      * 302 - :class:`HTTPFound`
32      * 303 - :class:`HTTPSeeOther`
33      * 304 - :class:`HTTPNotModified`
34      * 305 - :class:`HTTPUseProxy`
35      * 307 - :class:`HTTPTemporaryRedirect`
36    HTTPError
37      HTTPClientError
38        * 400 - :class:`HTTPBadRequest`
39        * 401 - :class:`HTTPUnauthorized`
40        * 402 - :class:`HTTPPaymentRequired`
41        * 403 - :class:`HTTPForbidden`
42        * 404 - :class:`HTTPNotFound`
43        * 405 - :class:`HTTPMethodNotAllowed`
44        * 406 - :class:`HTTPNotAcceptable`
45        * 407 - :class:`HTTPProxyAuthenticationRequired`
46        * 408 - :class:`HTTPRequestTimeout`
47        * 409 - :class:`HTTPConflict`
48        * 410 - :class:`HTTPGone`
49        * 411 - :class:`HTTPLengthRequired`
50        * 412 - :class:`HTTPPreconditionFailed`
51        * 413 - :class:`HTTPRequestEntityTooLarge`
52        * 414 - :class:`HTTPRequestURITooLong`
53        * 415 - :class:`HTTPUnsupportedMediaType`
54        * 416 - :class:`HTTPRequestRangeNotSatisfiable`
55        * 417 - :class:`HTTPExpectationFailed`
56        * 422 - :class:`HTTPUnprocessableEntity`
57        * 423 - :class:`HTTPLocked`
58        * 424 - :class:`HTTPFailedDependency`
59        * 428 - :class:`HTTPPreconditionRequired`
60        * 429 - :class:`HTTPTooManyRequests`
61        * 431 - :class:`HTTPRequestHeaderFieldsTooLarge`
62        * 451 - :class:`HTTPUnavailableForLegalReasons`
63      HTTPServerError
64        * 500 - :class:`HTTPInternalServerError`
65        * 501 - :class:`HTTPNotImplemented`
66        * 502 - :class:`HTTPBadGateway`
67        * 503 - :class:`HTTPServiceUnavailable`
68        * 504 - :class:`HTTPGatewayTimeout`
69        * 505 - :class:`HTTPVersionNotSupported`
70        * 511 - :class:`HTTPNetworkAuthenticationRequired`
71
72Usage notes
73-----------
74
75The HTTPException class is complicated by 4 factors:
76
77  1. The content given to the exception may either be plain-text or
78     as html-text.
79
80  2. The template may want to have string-substitutions taken from
81     the current ``environ`` or values from incoming headers. This
82     is especially troublesome due to case sensitivity.
83
84  3. The final output may either be text/plain or text/html
85     mime-type as requested by the client application.
86
87  4. Each exception has a default explanation, but those who
88     raise exceptions may want to provide additional detail.
89
90Subclass attributes and call parameters are designed to provide an easier path
91through the complications.
92
93Attributes:
94
95   ``code``
96       the HTTP status code for the exception
97
98   ``title``
99       remainder of the status line (stuff after the code)
100
101   ``explanation``
102       a plain-text explanation of the error message that is
103       not subject to environment or header substitutions;
104       it is accessible in the template via %(explanation)s
105
106   ``detail``
107       a plain-text message customization that is not subject
108       to environment or header substitutions; accessible in
109       the template via %(detail)s
110
111   ``body_template``
112       a content fragment (in HTML) used for environment and
113       header substitution; the default template includes both
114       the explanation and further detail provided in the
115       message
116
117Parameters:
118
119   ``detail``
120     a plain-text override of the default ``detail``
121
122   ``headers``
123     a list of (k,v) header pairs
124
125   ``comment``
126     a plain-text additional information which is
127     usually stripped/hidden for end-users
128
129   ``body_template``
130     a string.Template object containing a content fragment in HTML
131     that frames the explanation and further detail
132
133To override the template (which is HTML content) or the plain-text
134explanation, one must subclass the given exception; or customize it
135after it has been created.  This particular breakdown of a message
136into explanation, detail and template allows both the creation of
137plain-text and html messages for various clients as well as
138error-free substitution of environment variables and headers.
139
140
141The subclasses of :class:`~_HTTPMove`
142(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
143:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
144:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
145field. Reflecting this, these subclasses have two additional keyword arguments:
146``location`` and ``add_slash``.
147
148Parameters:
149
150    ``location``
151      to set the location immediately
152
153    ``add_slash``
154      set to True to redirect to the same URL as the request, except with a
155      ``/`` appended
156
157Relative URLs in the location will be resolved to absolute.
158
159References:
160
161.. [1] http://www.python.org/peps/pep-0333.html#error-handling
162.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
163
164
165"""
166
167from string import Template
168import re
169import sys
170
171from webob.compat import (
172    class_types,
173    text_,
174    text_type,
175    urlparse,
176    )
177from webob.request import Request
178from webob.response import Response
179from webob.util import (
180    html_escape,
181    warn_deprecation,
182    )
183
184tag_re = re.compile(r'<.*?>', re.S)
185br_re = re.compile(r'<br.*?>', re.I|re.S)
186comment_re = re.compile(r'<!--|-->')
187
188def no_escape(value):
189    if value is None:
190        return ''
191    if not isinstance(value, text_type):
192        if hasattr(value, '__unicode__'):
193            value = value.__unicode__()
194        if isinstance(value, bytes):
195            value = text_(value, 'utf-8')
196        else:
197            value = text_type(value)
198    return value
199
200def strip_tags(value):
201    value = value.replace('\n', ' ')
202    value = value.replace('\r', '')
203    value = br_re.sub('\n', value)
204    value = comment_re.sub('', value)
205    value = tag_re.sub('', value)
206    return value
207
208class HTTPException(Exception):
209    def __init__(self, message, wsgi_response):
210        Exception.__init__(self, message)
211        self.wsgi_response = wsgi_response
212
213    def __call__(self, environ, start_response):
214        return self.wsgi_response(environ, start_response)
215
216class WSGIHTTPException(Response, HTTPException):
217
218    ## You should set in subclasses:
219    # code = 200
220    # title = 'OK'
221    # explanation = 'why this happens'
222    # body_template_obj = Template('response template')
223    code = 500
224    title = 'Internal Server Error'
225    explanation = ''
226    body_template_obj = Template('''\
227${explanation}<br /><br />
228${detail}
229${html_comment}
230''')
231
232    plain_template_obj = Template('''\
233${status}
234
235${body}''')
236
237    html_template_obj = Template('''\
238<html>
239 <head>
240  <title>${status}</title>
241 </head>
242 <body>
243  <h1>${status}</h1>
244  ${body}
245 </body>
246</html>''')
247
248    ## Set this to True for responses that should have no request body
249    empty_body = False
250
251    def __init__(self, detail=None, headers=None, comment=None,
252                 body_template=None, **kw):
253        Response.__init__(self,
254                          status='%s %s' % (self.code, self.title),
255                          **kw)
256        Exception.__init__(self, detail)
257        if headers:
258            self.headers.extend(headers)
259        self.detail = detail
260        self.comment = comment
261        if body_template is not None:
262            self.body_template = body_template
263            self.body_template_obj = Template(body_template)
264        if self.empty_body:
265            del self.content_type
266            del self.content_length
267
268    def __str__(self):
269        return self.detail or self.explanation
270
271    def _make_body(self, environ, escape):
272        args = {
273            'explanation': escape(self.explanation),
274            'detail': escape(self.detail or ''),
275            'comment': escape(self.comment or ''),
276            }
277        if self.comment:
278            args['html_comment'] = '<!-- %s -->' % escape(self.comment)
279        else:
280            args['html_comment'] = ''
281        if WSGIHTTPException.body_template_obj is not self.body_template_obj:
282            # Custom template; add headers to args
283            for k, v in environ.items():
284                args[k] = escape(v)
285            for k, v in self.headers.items():
286                args[k.lower()] = escape(v)
287        t_obj = self.body_template_obj
288        return t_obj.substitute(args)
289
290    def plain_body(self, environ):
291        body = self._make_body(environ, no_escape)
292        body = strip_tags(body)
293        return self.plain_template_obj.substitute(status=self.status,
294                                                  title=self.title,
295                                                  body=body)
296
297    def html_body(self, environ):
298        body = self._make_body(environ, html_escape)
299        return self.html_template_obj.substitute(status=self.status,
300                                                 body=body)
301
302    def generate_response(self, environ, start_response):
303        if self.content_length is not None:
304            del self.content_length
305        headerlist = list(self.headerlist)
306        accept = environ.get('HTTP_ACCEPT', '')
307        if accept and 'html' in accept or '*/*' in accept:
308            content_type = 'text/html'
309            body = self.html_body(environ)
310        else:
311            content_type = 'text/plain'
312            body = self.plain_body(environ)
313        extra_kw = {}
314        if isinstance(body, text_type):
315            extra_kw.update(charset='utf-8')
316        resp = Response(body,
317            status=self.status,
318            headerlist=headerlist,
319            content_type=content_type,
320            **extra_kw
321        )
322        resp.content_type = content_type
323        return resp(environ, start_response)
324
325    def __call__(self, environ, start_response):
326        is_head = environ['REQUEST_METHOD'] == 'HEAD'
327        if self.body or self.empty_body or is_head:
328            app_iter = Response.__call__(self, environ, start_response)
329        else:
330            app_iter = self.generate_response(environ, start_response)
331        if is_head:
332            app_iter = []
333        return app_iter
334
335    @property
336    def wsgi_response(self):
337        return self
338
339
340
341class HTTPError(WSGIHTTPException):
342    """
343    base class for status codes in the 400's and 500's
344
345    This is an exception which indicates that an error has occurred,
346    and that any work in progress should not be committed.  These are
347    typically results in the 400's and 500's.
348    """
349
350class HTTPRedirection(WSGIHTTPException):
351    """
352    base class for 300's status code (redirections)
353
354    This is an abstract base class for 3xx redirection.  It indicates
355    that further action needs to be taken by the user agent in order
356    to fulfill the request.  It does not necessarly signal an error
357    condition.
358    """
359
360class HTTPOk(WSGIHTTPException):
361    """
362    Base class for the 200's status code (successful responses)
363
364    code: 200, title: OK
365    """
366    code = 200
367    title = 'OK'
368
369############################################################
370## 2xx success
371############################################################
372
373class HTTPCreated(HTTPOk):
374    """
375    subclass of :class:`~HTTPOk`
376
377    This indicates that request has been fulfilled and resulted in a new
378    resource being created.
379
380    code: 201, title: Created
381    """
382    code = 201
383    title = 'Created'
384
385class HTTPAccepted(HTTPOk):
386    """
387    subclass of :class:`~HTTPOk`
388
389    This indicates that the request has been accepted for processing, but the
390    processing has not been completed.
391
392    code: 202, title: Accepted
393    """
394    code = 202
395    title = 'Accepted'
396    explanation = 'The request is accepted for processing.'
397
398class HTTPNonAuthoritativeInformation(HTTPOk):
399    """
400    subclass of :class:`~HTTPOk`
401
402    This indicates that the returned metainformation in the entity-header is
403    not the definitive set as available from the origin server, but is
404    gathered from a local or a third-party copy.
405
406    code: 203, title: Non-Authoritative Information
407    """
408    code = 203
409    title = 'Non-Authoritative Information'
410
411class HTTPNoContent(HTTPOk):
412    """
413    subclass of :class:`~HTTPOk`
414
415    This indicates that the server has fulfilled the request but does
416    not need to return an entity-body, and might want to return updated
417    metainformation.
418
419    code: 204, title: No Content
420    """
421    code = 204
422    title = 'No Content'
423    empty_body = True
424
425class HTTPResetContent(HTTPOk):
426    """
427    subclass of :class:`~HTTPOk`
428
429    This indicates that the the server has fulfilled the request and
430    the user agent SHOULD reset the document view which caused the
431    request to be sent.
432
433    code: 205, title: Reset Content
434    """
435    code = 205
436    title = 'Reset Content'
437    empty_body = True
438
439class HTTPPartialContent(HTTPOk):
440    """
441    subclass of :class:`~HTTPOk`
442
443    This indicates that the server has fulfilled the partial GET
444    request for the resource.
445
446    code: 206, title: Partial Content
447    """
448    code = 206
449    title = 'Partial Content'
450
451############################################################
452## 3xx redirection
453############################################################
454
455class _HTTPMove(HTTPRedirection):
456    """
457    redirections which require a Location field
458
459    Since a 'Location' header is a required attribute of 301, 302, 303,
460    305 and 307 (but not 304), this base class provides the mechanics to
461    make this easy.
462
463    You can provide a location keyword argument to set the location
464    immediately.  You may also give ``add_slash=True`` if you want to
465    redirect to the same URL as the request, except with a ``/`` added
466    to the end.
467
468    Relative URLs in the location will be resolved to absolute.
469    """
470    explanation = 'The resource has been moved to'
471    body_template_obj = Template('''\
472${explanation} <a href="${location}">${location}</a>;
473you should be redirected automatically.
474${detail}
475${html_comment}''')
476
477    def __init__(self, detail=None, headers=None, comment=None,
478                 body_template=None, location=None, add_slash=False):
479        super(_HTTPMove, self).__init__(
480            detail=detail, headers=headers, comment=comment,
481            body_template=body_template)
482        if location is not None:
483            self.location = location
484            if add_slash:
485                raise TypeError(
486                    "You can only provide one of the arguments location "
487                    "and add_slash")
488        self.add_slash = add_slash
489
490    def __call__(self, environ, start_response):
491        req = Request(environ)
492        if self.add_slash:
493            url = req.path_url
494            url += '/'
495            if req.environ.get('QUERY_STRING'):
496                url += '?' + req.environ['QUERY_STRING']
497            self.location = url
498        self.location = urlparse.urljoin(req.path_url, self.location)
499        return super(_HTTPMove, self).__call__(
500            environ, start_response)
501
502class HTTPMultipleChoices(_HTTPMove):
503    """
504    subclass of :class:`~_HTTPMove`
505
506    This indicates that the requested resource corresponds to any one
507    of a set of representations, each with its own specific location,
508    and agent-driven negotiation information is being provided so that
509    the user can select a preferred representation and redirect its
510    request to that location.
511
512    code: 300, title: Multiple Choices
513    """
514    code = 300
515    title = 'Multiple Choices'
516
517class HTTPMovedPermanently(_HTTPMove):
518    """
519    subclass of :class:`~_HTTPMove`
520
521    This indicates that the requested resource has been assigned a new
522    permanent URI and any future references to this resource SHOULD use
523    one of the returned URIs.
524
525    code: 301, title: Moved Permanently
526    """
527    code = 301
528    title = 'Moved Permanently'
529
530class HTTPFound(_HTTPMove):
531    """
532    subclass of :class:`~_HTTPMove`
533
534    This indicates that the requested resource resides temporarily under
535    a different URI.
536
537    code: 302, title: Found
538    """
539    code = 302
540    title = 'Found'
541    explanation = 'The resource was found at'
542
543# This one is safe after a POST (the redirected location will be
544# retrieved with GET):
545class HTTPSeeOther(_HTTPMove):
546    """
547    subclass of :class:`~_HTTPMove`
548
549    This indicates that the response to the request can be found under
550    a different URI and SHOULD be retrieved using a GET method on that
551    resource.
552
553    code: 303, title: See Other
554    """
555    code = 303
556    title = 'See Other'
557
558class HTTPNotModified(HTTPRedirection):
559    """
560    subclass of :class:`~HTTPRedirection`
561
562    This indicates that if the client has performed a conditional GET
563    request and access is allowed, but the document has not been
564    modified, the server SHOULD respond with this status code.
565
566    code: 304, title: Not Modified
567    """
568    # TODO: this should include a date or etag header
569    code = 304
570    title = 'Not Modified'
571    empty_body = True
572
573class HTTPUseProxy(_HTTPMove):
574    """
575    subclass of :class:`~_HTTPMove`
576
577    This indicates that the requested resource MUST be accessed through
578    the proxy given by the Location field.
579
580    code: 305, title: Use Proxy
581    """
582    # Not a move, but looks a little like one
583    code = 305
584    title = 'Use Proxy'
585    explanation = (
586        'The resource must be accessed through a proxy located at')
587
588class HTTPTemporaryRedirect(_HTTPMove):
589    """
590    subclass of :class:`~_HTTPMove`
591
592    This indicates that the requested resource resides temporarily
593    under a different URI.
594
595    code: 307, title: Temporary Redirect
596    """
597    code = 307
598    title = 'Temporary Redirect'
599
600############################################################
601## 4xx client error
602############################################################
603
604class HTTPClientError(HTTPError):
605    """
606    base class for the 400's, where the client is in error
607
608    This is an error condition in which the client is presumed to be
609    in-error.  This is an expected problem, and thus is not considered
610    a bug.  A server-side traceback is not warranted.  Unless specialized,
611    this is a '400 Bad Request'
612
613    code: 400, title: Bad Request
614    """
615    code = 400
616    title = 'Bad Request'
617    explanation = ('The server could not comply with the request since\r\n'
618                   'it is either malformed or otherwise incorrect.\r\n')
619
620class HTTPBadRequest(HTTPClientError):
621    pass
622
623class HTTPUnauthorized(HTTPClientError):
624    """
625    subclass of :class:`~HTTPClientError`
626
627    This indicates that the request requires user authentication.
628
629    code: 401, title: Unauthorized
630    """
631    code = 401
632    title = 'Unauthorized'
633    explanation = (
634        'This server could not verify that you are authorized to\r\n'
635        'access the document you requested.  Either you supplied the\r\n'
636        'wrong credentials (e.g., bad password), or your browser\r\n'
637        'does not understand how to supply the credentials required.\r\n')
638
639class HTTPPaymentRequired(HTTPClientError):
640    """
641    subclass of :class:`~HTTPClientError`
642
643    code: 402, title: Payment Required
644    """
645    code = 402
646    title = 'Payment Required'
647    explanation = ('Access was denied for financial reasons.')
648
649class HTTPForbidden(HTTPClientError):
650    """
651    subclass of :class:`~HTTPClientError`
652
653    This indicates that the server understood the request, but is
654    refusing to fulfill it.
655
656    code: 403, title: Forbidden
657    """
658    code = 403
659    title = 'Forbidden'
660    explanation = ('Access was denied to this resource.')
661
662class HTTPNotFound(HTTPClientError):
663    """
664    subclass of :class:`~HTTPClientError`
665
666    This indicates that the server did not find anything matching the
667    Request-URI.
668
669    code: 404, title: Not Found
670    """
671    code = 404
672    title = 'Not Found'
673    explanation = ('The resource could not be found.')
674
675class HTTPMethodNotAllowed(HTTPClientError):
676    """
677    subclass of :class:`~HTTPClientError`
678
679    This indicates that the method specified in the Request-Line is
680    not allowed for the resource identified by the Request-URI.
681
682    code: 405, title: Method Not Allowed
683    """
684    code = 405
685    title = 'Method Not Allowed'
686    # override template since we need an environment variable
687    body_template_obj = Template('''\
688The method ${REQUEST_METHOD} is not allowed for this resource. <br /><br />
689${detail}''')
690
691class HTTPNotAcceptable(HTTPClientError):
692    """
693    subclass of :class:`~HTTPClientError`
694
695    This indicates the resource identified by the request is only
696    capable of generating response entities which have content
697    characteristics not acceptable according to the accept headers
698    sent in the request.
699
700    code: 406, title: Not Acceptable
701    """
702    code = 406
703    title = 'Not Acceptable'
704    # override template since we need an environment variable
705    template = Template('''\
706The resource could not be generated that was acceptable to your browser
707(content of type ${HTTP_ACCEPT}. <br /><br />
708${detail}''')
709
710class HTTPProxyAuthenticationRequired(HTTPClientError):
711    """
712    subclass of :class:`~HTTPClientError`
713
714    This is similar to 401, but indicates that the client must first
715    authenticate itself with the proxy.
716
717    code: 407, title: Proxy Authentication Required
718    """
719    code = 407
720    title = 'Proxy Authentication Required'
721    explanation = ('Authentication with a local proxy is needed.')
722
723class HTTPRequestTimeout(HTTPClientError):
724    """
725    subclass of :class:`~HTTPClientError`
726
727    This indicates that the client did not produce a request within
728    the time that the server was prepared to wait.
729
730    code: 408, title: Request Timeout
731    """
732    code = 408
733    title = 'Request Timeout'
734    explanation = ('The server has waited too long for the request to '
735                   'be sent by the client.')
736
737class HTTPConflict(HTTPClientError):
738    """
739    subclass of :class:`~HTTPClientError`
740
741    This indicates that the request could not be completed due to a
742    conflict with the current state of the resource.
743
744    code: 409, title: Conflict
745    """
746    code = 409
747    title = 'Conflict'
748    explanation = ('There was a conflict when trying to complete '
749                   'your request.')
750
751class HTTPGone(HTTPClientError):
752    """
753    subclass of :class:`~HTTPClientError`
754
755    This indicates that the requested resource is no longer available
756    at the server and no forwarding address is known.
757
758    code: 410, title: Gone
759    """
760    code = 410
761    title = 'Gone'
762    explanation = ('This resource is no longer available.  No forwarding '
763                   'address is given.')
764
765class HTTPLengthRequired(HTTPClientError):
766    """
767    subclass of :class:`~HTTPClientError`
768
769    This indicates that the the server refuses to accept the request
770    without a defined Content-Length.
771
772    code: 411, title: Length Required
773    """
774    code = 411
775    title = 'Length Required'
776    explanation = ('Content-Length header required.')
777
778class HTTPPreconditionFailed(HTTPClientError):
779    """
780    subclass of :class:`~HTTPClientError`
781
782    This indicates that the precondition given in one or more of the
783    request-header fields evaluated to false when it was tested on the
784    server.
785
786    code: 412, title: Precondition Failed
787    """
788    code = 412
789    title = 'Precondition Failed'
790    explanation = ('Request precondition failed.')
791
792class HTTPRequestEntityTooLarge(HTTPClientError):
793    """
794    subclass of :class:`~HTTPClientError`
795
796    This indicates that the server is refusing to process a request
797    because the request entity is larger than the server is willing or
798    able to process.
799
800    code: 413, title: Request Entity Too Large
801    """
802    code = 413
803    title = 'Request Entity Too Large'
804    explanation = ('The body of your request was too large for this server.')
805
806class HTTPRequestURITooLong(HTTPClientError):
807    """
808    subclass of :class:`~HTTPClientError`
809
810    This indicates that the server is refusing to service the request
811    because the Request-URI is longer than the server is willing to
812    interpret.
813
814    code: 414, title: Request-URI Too Long
815    """
816    code = 414
817    title = 'Request-URI Too Long'
818    explanation = ('The request URI was too long for this server.')
819
820class HTTPUnsupportedMediaType(HTTPClientError):
821    """
822    subclass of :class:`~HTTPClientError`
823
824    This indicates that the server is refusing to service the request
825    because the entity of the request is in a format not supported by
826    the requested resource for the requested method.
827
828    code: 415, title: Unsupported Media Type
829    """
830    code = 415
831    title = 'Unsupported Media Type'
832    # override template since we need an environment variable
833    template_obj = Template('''\
834The request media type ${CONTENT_TYPE} is not supported by this server.
835<br /><br />
836${detail}''')
837
838class HTTPRequestRangeNotSatisfiable(HTTPClientError):
839    """
840    subclass of :class:`~HTTPClientError`
841
842    The server SHOULD return a response with this status code if a
843    request included a Range request-header field, and none of the
844    range-specifier values in this field overlap the current extent
845    of the selected resource, and the request did not include an
846    If-Range request-header field.
847
848    code: 416, title: Request Range Not Satisfiable
849    """
850    code = 416
851    title = 'Request Range Not Satisfiable'
852    explanation = ('The Range requested is not available.')
853
854class HTTPExpectationFailed(HTTPClientError):
855    """
856    subclass of :class:`~HTTPClientError`
857
858    This indidcates that the expectation given in an Expect
859    request-header field could not be met by this server.
860
861    code: 417, title: Expectation Failed
862    """
863    code = 417
864    title = 'Expectation Failed'
865    explanation = ('Expectation failed.')
866
867class HTTPUnprocessableEntity(HTTPClientError):
868    """
869    subclass of :class:`~HTTPClientError`
870
871    This indicates that the server is unable to process the contained
872    instructions.
873
874    code: 422, title: Unprocessable Entity
875    """
876    ## Note: from WebDAV
877    code = 422
878    title = 'Unprocessable Entity'
879    explanation = 'Unable to process the contained instructions'
880
881class HTTPLocked(HTTPClientError):
882    """
883    subclass of :class:`~HTTPClientError`
884
885    This indicates that the resource is locked.
886
887    code: 423, title: Locked
888    """
889    ## Note: from WebDAV
890    code = 423
891    title = 'Locked'
892    explanation = ('The resource is locked')
893
894class HTTPFailedDependency(HTTPClientError):
895    """
896    subclass of :class:`~HTTPClientError`
897
898    This indicates that the method could not be performed because the
899    requested action depended on another action and that action failed.
900
901    code: 424, title: Failed Dependency
902    """
903    ## Note: from WebDAV
904    code = 424
905    title = 'Failed Dependency'
906    explanation = (
907        'The method could not be performed because the requested '
908        'action dependended on another action and that action failed')
909
910class HTTPPreconditionRequired(HTTPClientError):
911    """
912    subclass of :class:`~HTTPClientError`
913
914    This indicates that the origin server requires the request to be
915    conditional.  From RFC 6585, "Additional HTTP Status Codes".
916
917    code: 428, title: Precondition Required
918    """
919    code = 428
920    title = 'Precondition Required'
921    explanation = ('This request is required to be conditional')
922
923class HTTPTooManyRequests(HTTPClientError):
924    """
925    subclass of :class:`~HTTPClientError`
926
927    This indicates that the client has sent too many requests in a
928    given amount of time.  Useful for rate limiting.
929
930    From RFC 6585, "Additional HTTP Status Codes".
931
932    code: 429, title: Too Many Requests
933    """
934    code = 429
935    title = 'Too Many Requests'
936    explanation = (
937        'The client has sent too many requests in a given amount of time')
938
939class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
940    """
941    subclass of :class:`~HTTPClientError`
942
943    This indicates that the server is unwilling to process the request
944    because its header fields are too large. The request may be resubmitted
945    after reducing the size of the request header fields.
946
947    From RFC 6585, "Additional HTTP Status Codes".
948
949    code: 431, title: Request Header Fields Too Large
950    """
951    code = 431
952    title = 'Request Header Fields Too Large'
953    explanation = (
954        'The request header fields were too large')
955
956class HTTPUnavailableForLegalReasons(HTTPClientError):
957    """
958    subclass of :class:`~HTTPClientError`
959
960    This indicates that the server is unable to process the request
961    because of legal reasons, e.g. censorship or government-mandated
962    blocked access.
963
964    From the draft "A New HTTP Status Code for Legally-restricted Resources"
965    by Tim Bray:
966
967    http://tools.ietf.org/html/draft-tbray-http-legally-restricted-status-00
968
969    code: 451, title: Unavailable For Legal Reasons
970    """
971    code = 451
972    title = 'Unavailable For Legal Reasons'
973    explanation = ('The resource is not available due to legal reasons.')
974
975############################################################
976## 5xx Server Error
977############################################################
978#  Response status codes beginning with the digit "5" indicate cases in
979#  which the server is aware that it has erred or is incapable of
980#  performing the request. Except when responding to a HEAD request, the
981#  server SHOULD include an entity containing an explanation of the error
982#  situation, and whether it is a temporary or permanent condition. User
983#  agents SHOULD display any included entity to the user. These response
984#  codes are applicable to any request method.
985
986class HTTPServerError(HTTPError):
987    """
988    base class for the 500's, where the server is in-error
989
990    This is an error condition in which the server is presumed to be
991    in-error.  This is usually unexpected, and thus requires a traceback;
992    ideally, opening a support ticket for the customer. Unless specialized,
993    this is a '500 Internal Server Error'
994    """
995    code = 500
996    title = 'Internal Server Error'
997    explanation = (
998      'The server has either erred or is incapable of performing\r\n'
999      'the requested operation.\r\n')
1000
1001class HTTPInternalServerError(HTTPServerError):
1002    pass
1003
1004class HTTPNotImplemented(HTTPServerError):
1005    """
1006    subclass of :class:`~HTTPServerError`
1007
1008    This indicates that the server does not support the functionality
1009    required to fulfill the request.
1010
1011    code: 501, title: Not Implemented
1012    """
1013    code = 501
1014    title = 'Not Implemented'
1015    template = Template('''
1016The request method ${REQUEST_METHOD} is not implemented for this server. <br /><br />
1017${detail}''')
1018
1019class HTTPBadGateway(HTTPServerError):
1020    """
1021    subclass of :class:`~HTTPServerError`
1022
1023    This indicates that the server, while acting as a gateway or proxy,
1024    received an invalid response from the upstream server it accessed
1025    in attempting to fulfill the request.
1026
1027    code: 502, title: Bad Gateway
1028    """
1029    code = 502
1030    title = 'Bad Gateway'
1031    explanation = ('Bad gateway.')
1032
1033class HTTPServiceUnavailable(HTTPServerError):
1034    """
1035    subclass of :class:`~HTTPServerError`
1036
1037    This indicates that the server is currently unable to handle the
1038    request due to a temporary overloading or maintenance of the server.
1039
1040    code: 503, title: Service Unavailable
1041    """
1042    code = 503
1043    title = 'Service Unavailable'
1044    explanation = ('The server is currently unavailable. '
1045                   'Please try again at a later time.')
1046
1047class HTTPGatewayTimeout(HTTPServerError):
1048    """
1049    subclass of :class:`~HTTPServerError`
1050
1051    This indicates that the server, while acting as a gateway or proxy,
1052    did not receive a timely response from the upstream server specified
1053    by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
1054    (e.g. DNS) it needed to access in attempting to complete the request.
1055
1056    code: 504, title: Gateway Timeout
1057    """
1058    code = 504
1059    title = 'Gateway Timeout'
1060    explanation = ('The gateway has timed out.')
1061
1062class HTTPVersionNotSupported(HTTPServerError):
1063    """
1064    subclass of :class:`~HTTPServerError`
1065
1066    This indicates that the server does not support, or refuses to
1067    support, the HTTP protocol version that was used in the request
1068    message.
1069
1070    code: 505, title: HTTP Version Not Supported
1071    """
1072    code = 505
1073    title = 'HTTP Version Not Supported'
1074    explanation = ('The HTTP version is not supported.')
1075
1076class HTTPInsufficientStorage(HTTPServerError):
1077    """
1078    subclass of :class:`~HTTPServerError`
1079
1080    This indicates that the server does not have enough space to save
1081    the resource.
1082
1083    code: 507, title: Insufficient Storage
1084    """
1085    code = 507
1086    title = 'Insufficient Storage'
1087    explanation = ('There was not enough space to save the resource')
1088
1089class HTTPNetworkAuthenticationRequired(HTTPServerError):
1090    """
1091    subclass of :class:`~HTTPServerError`
1092
1093    This indicates that the client needs to authenticate to gain
1094    network access.  From RFC 6585, "Additional HTTP Status Codes".
1095
1096    code: 511, title: Network Authentication Required
1097    """
1098    code = 511
1099    title = 'Network Authentication Required'
1100    explanation = ('Network authentication is required')
1101
1102class HTTPExceptionMiddleware(object):
1103    """
1104    Middleware that catches exceptions in the sub-application.  This
1105    does not catch exceptions in the app_iter; only during the initial
1106    calling of the application.
1107
1108    This should be put *very close* to applications that might raise
1109    these exceptions.  This should not be applied globally; letting
1110    *expected* exceptions raise through the WSGI stack is dangerous.
1111    """
1112
1113    def __init__(self, application):
1114        self.application = application
1115    def __call__(self, environ, start_response):
1116        try:
1117            return self.application(environ, start_response)
1118        except HTTPException:
1119            parent_exc_info = sys.exc_info()
1120            def repl_start_response(status, headers, exc_info=None):
1121                if exc_info is None:
1122                    exc_info = parent_exc_info
1123                return start_response(status, headers, exc_info)
1124            return parent_exc_info[1](environ, repl_start_response)
1125
1126try:
1127    from paste import httpexceptions
1128except ImportError:   # pragma: no cover
1129    # Without Paste we don't need to do this fixup
1130    pass
1131else: # pragma: no cover
1132    for name in dir(httpexceptions):
1133        obj = globals().get(name)
1134        if (obj and isinstance(obj, type) and issubclass(obj, HTTPException)
1135            and obj is not HTTPException
1136            and obj is not WSGIHTTPException):
1137            obj.__bases__ = obj.__bases__ + (getattr(httpexceptions, name),)
1138    del name, obj, httpexceptions
1139
1140__all__ = ['HTTPExceptionMiddleware', 'status_map']
1141status_map={}
1142for name, value in list(globals().items()):
1143    if (isinstance(value, (type, class_types)) and
1144        issubclass(value, HTTPException)
1145        and not name.startswith('_')):
1146        __all__.append(name)
1147        if getattr(value, 'code', None):
1148            status_map[value.code]=value
1149        if hasattr(value, 'explanation'):
1150            value.explanation = ' '.join(value.explanation.strip().split())
1151del name, value
1152