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