1"""
2HTTP Exceptions
3---------------
4
5This module contains Pyramid HTTP exception classes.  Each class relates to a
6single HTTP status code.  Each class is a subclass of the
7:class:`~HTTPException`.  Each exception class is also a :term:`response`
8object.
9
10Each exception class has a status code according to :rfc:`2068`:
11codes with 100-300 are not really errors; 400s are client errors,
12and 500s are server errors.
13
14Exception
15  HTTPException
16    HTTPSuccessful
17      * 200 - HTTPOk
18      * 201 - HTTPCreated
19      * 202 - HTTPAccepted
20      * 203 - HTTPNonAuthoritativeInformation
21      * 204 - HTTPNoContent
22      * 205 - HTTPResetContent
23      * 206 - HTTPPartialContent
24    HTTPRedirection
25      * 300 - HTTPMultipleChoices
26      * 301 - HTTPMovedPermanently
27      * 302 - HTTPFound
28      * 303 - HTTPSeeOther
29      * 304 - HTTPNotModified
30      * 305 - HTTPUseProxy
31      * 307 - HTTPTemporaryRedirect
32    HTTPError
33      HTTPClientError
34        * 400 - HTTPBadRequest
35        * 401 - HTTPUnauthorized
36        * 402 - HTTPPaymentRequired
37        * 403 - HTTPForbidden
38        * 404 - HTTPNotFound
39        * 405 - HTTPMethodNotAllowed
40        * 406 - HTTPNotAcceptable
41        * 407 - HTTPProxyAuthenticationRequired
42        * 408 - HTTPRequestTimeout
43        * 409 - HTTPConflict
44        * 410 - HTTPGone
45        * 411 - HTTPLengthRequired
46        * 412 - HTTPPreconditionFailed
47        * 413 - HTTPRequestEntityTooLarge
48        * 414 - HTTPRequestURITooLong
49        * 415 - HTTPUnsupportedMediaType
50        * 416 - HTTPRequestRangeNotSatisfiable
51        * 417 - HTTPExpectationFailed
52        * 422 - HTTPUnprocessableEntity
53        * 423 - HTTPLocked
54        * 424 - HTTPFailedDependency
55        * 428 - HTTPPreconditionRequired
56        * 429 - HTTPTooManyRequests
57        * 431 - HTTPRequestHeaderFieldsTooLarge
58      HTTPServerError
59        * 500 - HTTPInternalServerError
60        * 501 - HTTPNotImplemented
61        * 502 - HTTPBadGateway
62        * 503 - HTTPServiceUnavailable
63        * 504 - HTTPGatewayTimeout
64        * 505 - HTTPVersionNotSupported
65        * 507 - HTTPInsufficientStorage
66
67HTTP exceptions are also :term:`response` objects, thus they accept most of
68the same parameters that can be passed to a regular
69:class:`~pyramid.response.Response`. Each HTTP exception also has the
70following attributes:
71
72   ``code``
73       the HTTP status code for the exception
74
75   ``title``
76       remainder of the status line (stuff after the code)
77
78   ``explanation``
79       a plain-text explanation of the error message that is
80       not subject to environment or header substitutions;
81       it is accessible in the template via ${explanation}
82
83   ``detail``
84       a plain-text message customization that is not subject
85       to environment or header substitutions; accessible in
86       the template via ${detail}
87
88   ``body_template``
89       a ``String.template``-format content fragment used for environment
90       and header substitution; the default template includes both
91       the explanation and further detail provided in the
92       message.
93
94Each HTTP exception accepts the following parameters, any others will
95be forwarded to its :class:`~pyramid.response.Response` superclass:
96
97   ``detail``
98     a plain-text override of the default ``detail``
99
100   ``headers``
101     a list of (k,v) header pairs
102
103   ``comment``
104     a plain-text additional information which is
105     usually stripped/hidden for end-users
106
107   ``body_template``
108     a ``string.Template`` object containing a content fragment in HTML
109     that frames the explanation and further detail
110
111   ``body``
112     a string that will override the ``body_template`` and be used as the
113     body of the response.
114
115Substitution of response headers into template values is always performed.
116Substitution of WSGI environment values is performed if a ``request`` is
117passed to the exception's constructor.
118
119The subclasses of :class:`~_HTTPMove`
120(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
121:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
122:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
123field. Reflecting this, these subclasses have one additional keyword argument:
124``location``, which indicates the location to which to redirect.
125"""
126import json
127
128from string import Template
129
130from zope.interface import implementer
131
132from webob import html_escape as _html_escape
133from webob.acceptparse import MIMEAccept
134
135from pyramid.compat import (
136    class_types,
137    text_type,
138    binary_type,
139    text_,
140    )
141
142from pyramid.interfaces import IExceptionResponse
143from pyramid.response import Response
144
145def _no_escape(value):
146    if value is None:
147        return ''
148    if not isinstance(value, text_type):
149        if hasattr(value, '__unicode__'):
150            value = value.__unicode__()
151        if isinstance(value, binary_type):
152            value = text_(value, 'utf-8')
153        else:
154            value = text_type(value)
155    return value
156
157@implementer(IExceptionResponse)
158class HTTPException(Response, Exception):
159
160    ## You should set in subclasses:
161    # code = 200
162    # title = 'OK'
163    # explanation = 'why this happens'
164    # body_template_obj = Template('response template')
165    #
166    # This class itself uses the error code "520" with the error message/title
167    # of "Unknown Error". This is not an RFC standard, however it is
168    # implemented in practice. Sub-classes should be overriding the default
169    # values and 520 should not be seen in the wild from Pyramid applications.
170    # Due to changes in WebOb, a code of "None" is not valid, and WebOb due to
171    # more strict error checking rejects it now.
172
173    # differences from webob.exc.WSGIHTTPException:
174    #
175    # - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
176    #   in default body template)
177    #
178    # - __call__ never generates a new Response, it always mutates self
179    #
180    # - explicitly sets self.message = detail to prevent whining by Python
181    #   2.6.5+ access of Exception.message
182    #
183    # - its base class of HTTPException is no longer a Python 2.4 compatibility
184    #   shim; it's purely a base class that inherits from Exception.  This
185    #   implies that this class' ``exception`` property always returns
186    #   ``self`` (it exists only for bw compat at this point).
187    #
188    # - documentation improvements (Pyramid-specific docstrings where necessary)
189    #
190    code = 520
191    title = 'Unknown Error'
192    explanation = ''
193    body_template_obj = Template('''\
194${explanation}${br}${br}
195${detail}
196${html_comment}
197''')
198
199    plain_template_obj = Template('''\
200${status}
201
202${body}''')
203
204    html_template_obj = Template('''\
205<html>
206 <head>
207  <title>${status}</title>
208 </head>
209 <body>
210  <h1>${status}</h1>
211  ${body}
212 </body>
213</html>''')
214
215    ## Set this to True for responses that should have no request body
216    empty_body = False
217
218    def __init__(self, detail=None, headers=None, comment=None,
219                 body_template=None, json_formatter=None, **kw):
220        status = '%s %s' % (self.code, self.title)
221        Response.__init__(self, status=status, **kw)
222        Exception.__init__(self, detail)
223        self.detail = self.message = detail
224        if headers:
225            self.headers.extend(headers)
226        self.comment = comment
227        if body_template is not None:
228            self.body_template = body_template
229            self.body_template_obj = Template(body_template)
230        if json_formatter is not None:
231            self._json_formatter = json_formatter
232
233        if self.empty_body:
234            del self.content_type
235            del self.content_length
236
237    def __str__(self):
238        return self.detail or self.explanation
239
240    def _json_formatter(self, status, body, title, environ):
241        return {'message': body,
242                'code': status,
243                'title': self.title}
244
245    def prepare(self, environ):
246        if not self.body and not self.empty_body:
247            html_comment = ''
248            comment = self.comment or ''
249            accept_value = environ.get('HTTP_ACCEPT', '')
250            accept = MIMEAccept(accept_value)
251            # Attempt to match text/html or application/json, if those don't
252            # match, we will fall through to defaulting to text/plain
253            match = accept.best_match(['text/html', 'application/json'])
254
255            if match == 'text/html':
256                self.content_type = 'text/html'
257                escape = _html_escape
258                page_template = self.html_template_obj
259                br = '<br/>'
260                if comment:
261                    html_comment = '<!-- %s -->' % escape(comment)
262            elif match == 'application/json':
263                self.content_type = 'application/json'
264                self.charset = None
265                escape = _no_escape
266                br = '\n'
267                if comment:
268                    html_comment = escape(comment)
269
270                class JsonPageTemplate(object):
271                    def __init__(self, excobj):
272                        self.excobj = excobj
273
274                    def substitute(self, status, body):
275                        jsonbody = self.excobj._json_formatter(
276                            status=status,
277                            body=body, title=self.excobj.title,
278                            environ=environ)
279                        return json.dumps(jsonbody)
280
281                page_template = JsonPageTemplate(self)
282            else:
283                self.content_type = 'text/plain'
284                escape = _no_escape
285                page_template = self.plain_template_obj
286                br = '\n'
287                if comment:
288                    html_comment = escape(comment)
289            args = {
290                'br': br,
291                'explanation': escape(self.explanation),
292                'detail': escape(self.detail or ''),
293                'comment': escape(comment),
294                'html_comment': html_comment,
295                }
296            body_tmpl = self.body_template_obj
297            if HTTPException.body_template_obj is not body_tmpl:
298                # Custom template; add headers to args
299                for k, v in environ.items():
300                    if (not k.startswith('wsgi.')) and ('.' in k):
301                        # omit custom environ variables, stringifying them may
302                        # trigger code that should not be executed here; see
303                        # https://github.com/Pylons/pyramid/issues/239
304                        continue
305                    args[k] = escape(v)
306                for k, v in self.headers.items():
307                    args[k.lower()] = escape(v)
308            body = body_tmpl.substitute(args)
309            page = page_template.substitute(status=self.status, body=body)
310            if isinstance(page, text_type):
311                page = page.encode(self.charset if self.charset else 'UTF-8')
312            self.app_iter = [page]
313            self.body = page
314
315    @property
316    def wsgi_response(self):
317        # bw compat only
318        return self
319
320    exception = wsgi_response # bw compat only
321
322    def __call__(self, environ, start_response):
323        # differences from webob.exc.WSGIHTTPException
324        #
325        # - does not try to deal with HEAD requests
326        #
327        # - does not manufacture a new response object when generating
328        #   the default response
329        #
330        self.prepare(environ)
331        return Response.__call__(self, environ, start_response)
332
333WSGIHTTPException = HTTPException # b/c post 1.5
334
335class HTTPError(HTTPException):
336    """
337    base class for exceptions with status codes in the 400s and 500s
338
339    This is an exception which indicates that an error has occurred,
340    and that any work in progress should not be committed.
341    """
342
343class HTTPRedirection(HTTPException):
344    """
345    base class for exceptions with status codes in the 300s (redirections)
346
347    This is an abstract base class for 3xx redirection.  It indicates
348    that further action needs to be taken by the user agent in order
349    to fulfill the request.  It does not necessarly signal an error
350    condition.
351    """
352
353class HTTPSuccessful(HTTPException):
354    """
355    Base class for exceptions with status codes in the 200s (successful
356    responses)
357    """
358
359############################################################
360## 2xx success
361############################################################
362
363class HTTPOk(HTTPSuccessful):
364    """
365    subclass of :class:`~HTTPSuccessful`
366
367    Indicates that the request has succeeded.
368
369    code: 200, title: OK
370    """
371    code = 200
372    title = 'OK'
373
374class HTTPCreated(HTTPSuccessful):
375    """
376    subclass of :class:`~HTTPSuccessful`
377
378    This indicates that request has been fulfilled and resulted in a new
379    resource being created.
380
381    code: 201, title: Created
382    """
383    code = 201
384    title = 'Created'
385
386class HTTPAccepted(HTTPSuccessful):
387    """
388    subclass of :class:`~HTTPSuccessful`
389
390    This indicates that the request has been accepted for processing, but the
391    processing has not been completed.
392
393    code: 202, title: Accepted
394    """
395    code = 202
396    title = 'Accepted'
397    explanation = 'The request is accepted for processing.'
398
399class HTTPNonAuthoritativeInformation(HTTPSuccessful):
400    """
401    subclass of :class:`~HTTPSuccessful`
402
403    This indicates that the returned metainformation in the entity-header is
404    not the definitive set as available from the origin server, but is
405    gathered from a local or a third-party copy.
406
407    code: 203, title: Non-Authoritative Information
408    """
409    code = 203
410    title = 'Non-Authoritative Information'
411
412class HTTPNoContent(HTTPSuccessful):
413    """
414    subclass of :class:`~HTTPSuccessful`
415
416    This indicates that the server has fulfilled the request but does
417    not need to return an entity-body, and might want to return updated
418    metainformation.
419
420    code: 204, title: No Content
421    """
422    code = 204
423    title = 'No Content'
424    empty_body = True
425
426class HTTPResetContent(HTTPSuccessful):
427    """
428    subclass of :class:`~HTTPSuccessful`
429
430    This indicates that the server has fulfilled the request and
431    the user agent SHOULD reset the document view which caused the
432    request to be sent.
433
434    code: 205, title: Reset Content
435    """
436    code = 205
437    title = 'Reset Content'
438    empty_body = True
439
440class HTTPPartialContent(HTTPSuccessful):
441    """
442    subclass of :class:`~HTTPSuccessful`
443
444    This indicates that the server has fulfilled the partial GET
445    request for the resource.
446
447    code: 206, title: Partial Content
448    """
449    code = 206
450    title = 'Partial Content'
451
452## FIXME: add 207 Multi-Status (but it's complicated)
453
454############################################################
455## 3xx redirection
456############################################################
457
458class _HTTPMove(HTTPRedirection):
459    """
460    redirections which require a Location field
461
462    Since a 'Location' header is a required attribute of 301, 302, 303,
463    305 and 307 (but not 304), this base class provides the mechanics to
464    make this easy.
465
466    You must provide a ``location`` keyword argument.
467    """
468    # differences from webob.exc._HTTPMove:
469    #
470    # - ${location} isn't wrapped in an <a> tag in body
471    #
472    # - location keyword arg defaults to ''
473    #
474    # - location isn't prepended with req.path_url when adding it as
475    #   a header
476    #
477    # - ``location`` is first keyword (and positional) argument
478    #
479    # - ``add_slash`` argument is no longer accepted:  code that passes
480    #   add_slash argument to the constructor will receive an exception.
481    explanation = 'The resource has been moved to'
482    body_template_obj = Template('''\
483${explanation} ${location}; you should be redirected automatically.
484${detail}
485${html_comment}''')
486
487    def __init__(self, location='', detail=None, headers=None, comment=None,
488                 body_template=None, **kw):
489        if location is None:
490            raise ValueError("HTTP redirects need a location to redirect to.")
491        super(_HTTPMove, self).__init__(
492            detail=detail, headers=headers, comment=comment,
493            body_template=body_template, location=location, **kw)
494
495class HTTPMultipleChoices(_HTTPMove):
496    """
497    subclass of :class:`~_HTTPMove`
498
499    This indicates that the requested resource corresponds to any one
500    of a set of representations, each with its own specific location,
501    and agent-driven negotiation information is being provided so that
502    the user can select a preferred representation and redirect its
503    request to that location.
504
505    code: 300, title: Multiple Choices
506    """
507    code = 300
508    title = 'Multiple Choices'
509
510class HTTPMovedPermanently(_HTTPMove):
511    """
512    subclass of :class:`~_HTTPMove`
513
514    This indicates that the requested resource has been assigned a new
515    permanent URI and any future references to this resource SHOULD use
516    one of the returned URIs.
517
518    code: 301, title: Moved Permanently
519    """
520    code = 301
521    title = 'Moved Permanently'
522
523class HTTPFound(_HTTPMove):
524    """
525    subclass of :class:`~_HTTPMove`
526
527    This indicates that the requested resource resides temporarily under
528    a different URI.
529
530    code: 302, title: Found
531    """
532    code = 302
533    title = 'Found'
534    explanation = 'The resource was found at'
535
536# This one is safe after a POST (the redirected location will be
537# retrieved with GET):
538class HTTPSeeOther(_HTTPMove):
539    """
540    subclass of :class:`~_HTTPMove`
541
542    This indicates that the response to the request can be found under
543    a different URI and SHOULD be retrieved using a GET method on that
544    resource.
545
546    code: 303, title: See Other
547    """
548    code = 303
549    title = 'See Other'
550
551class HTTPNotModified(HTTPRedirection):
552    """
553    subclass of :class:`~HTTPRedirection`
554
555    This indicates that if the client has performed a conditional GET
556    request and access is allowed, but the document has not been
557    modified, the server SHOULD respond with this status code.
558
559    code: 304, title: Not Modified
560    """
561    # FIXME: this should include a date or etag header
562    code = 304
563    title = 'Not Modified'
564    empty_body = True
565
566class HTTPUseProxy(_HTTPMove):
567    """
568    subclass of :class:`~_HTTPMove`
569
570    This indicates that the requested resource MUST be accessed through
571    the proxy given by the Location field.
572
573    code: 305, title: Use Proxy
574    """
575    # Not a move, but looks a little like one
576    code = 305
577    title = 'Use Proxy'
578    explanation = (
579        'The resource must be accessed through a proxy located at')
580
581class HTTPTemporaryRedirect(_HTTPMove):
582    """
583    subclass of :class:`~_HTTPMove`
584
585    This indicates that the requested resource resides temporarily
586    under a different URI.
587
588    code: 307, title: Temporary Redirect
589    """
590    code = 307
591    title = 'Temporary Redirect'
592
593############################################################
594## 4xx client error
595############################################################
596
597class HTTPClientError(HTTPError):
598    """
599    base class for the 400s, where the client is in error
600
601    This is an error condition in which the client is presumed to be
602    in-error.  This is an expected problem, and thus is not considered
603    a bug.  A server-side traceback is not warranted.  Unless specialized,
604    this is a '400 Bad Request'
605    """
606    pass
607
608class HTTPBadRequest(HTTPClientError):
609    """
610    subclass of :class:`~HTTPClientError`
611
612    This indicates that the body or headers failed validity checks,
613    preventing the server from being able to continue processing.
614
615    code: 400, title: Bad Request
616    """
617    code = 400
618    title = 'Bad Request'
619    explanation = ('The server could not comply with the request since '
620                   'it is either malformed or otherwise incorrect.')
621
622class HTTPUnauthorized(HTTPClientError):
623    """
624    subclass of :class:`~HTTPClientError`
625
626    This indicates that the request requires user authentication.
627
628    code: 401, title: Unauthorized
629    """
630    code = 401
631    title = 'Unauthorized'
632    explanation = (
633        'This server could not verify that you are authorized to '
634        'access the document you requested.  Either you supplied the '
635        'wrong credentials (e.g., bad password), or your browser '
636        'does not understand how to supply the credentials required.')
637
638class HTTPPaymentRequired(HTTPClientError):
639    """
640    subclass of :class:`~HTTPClientError`
641
642    code: 402, title: Payment Required
643    """
644    code = 402
645    title = 'Payment Required'
646    explanation = ('Access was denied for financial reasons.')
647
648class HTTPForbidden(HTTPClientError):
649    """
650    subclass of :class:`~HTTPClientError`
651
652    This indicates that the server understood the request, but is
653    refusing to fulfill it.
654
655    code: 403, title: Forbidden
656
657    Raise this exception within :term:`view` code to immediately return the
658    :term:`forbidden view` to the invoking user.  Usually this is a basic
659    ``403`` page, but the forbidden view can be customized as necessary.  See
660    :ref:`changing_the_forbidden_view`.  A ``Forbidden`` exception will be
661    the ``context`` of a :term:`Forbidden View`.
662
663    This exception's constructor treats two arguments specially.  The first
664    argument, ``detail``, should be a string.  The value of this string will
665    be used as the ``message`` attribute of the exception object.  The second
666    special keyword argument, ``result`` is usually an instance of
667    :class:`pyramid.security.Denied` or :class:`pyramid.security.ACLDenied`
668    each of which indicates a reason for the forbidden error.  However,
669    ``result`` is also permitted to be just a plain boolean ``False`` object
670    or ``None``.  The ``result`` value will be used as the ``result``
671    attribute of the exception object.  It defaults to ``None``.
672
673    The :term:`Forbidden View` can use the attributes of a Forbidden
674    exception as necessary to provide extended information in an error
675    report shown to a user.
676    """
677    # differences from webob.exc.HTTPForbidden:
678    #
679    # - accepts a ``result`` keyword argument
680    #
681    # - overrides constructor to set ``self.result``
682    #
683    # differences from older ``pyramid.exceptions.Forbidden``:
684    #
685    # - ``result`` must be passed as a keyword argument.
686    #
687    code = 403
688    title = 'Forbidden'
689    explanation = ('Access was denied to this resource.')
690    def __init__(self, detail=None, headers=None, comment=None,
691                 body_template=None, result=None, **kw):
692        HTTPClientError.__init__(self, detail=detail, headers=headers,
693                                 comment=comment, body_template=body_template,
694                                 **kw)
695        self.result = result
696
697class HTTPNotFound(HTTPClientError):
698    """
699    subclass of :class:`~HTTPClientError`
700
701    This indicates that the server did not find anything matching the
702    Request-URI.
703
704    code: 404, title: Not Found
705
706    Raise this exception within :term:`view` code to immediately
707    return the :term:`Not Found View` to the invoking user.  Usually
708    this is a basic ``404`` page, but the Not Found View can be
709    customized as necessary.  See :ref:`changing_the_notfound_view`.
710
711    This exception's constructor accepts a ``detail`` argument
712    (the first argument), which should be a string.  The value of this
713    string will be available as the ``message`` attribute of this exception,
714    for availability to the :term:`Not Found View`.
715    """
716    code = 404
717    title = 'Not Found'
718    explanation = ('The resource could not be found.')
719
720class HTTPMethodNotAllowed(HTTPClientError):
721    """
722    subclass of :class:`~HTTPClientError`
723
724    This indicates that the method specified in the Request-Line is
725    not allowed for the resource identified by the Request-URI.
726
727    code: 405, title: Method Not Allowed
728    """
729    # differences from webob.exc.HTTPMethodNotAllowed:
730    #
731    # - body_template_obj uses ${br} instead of <br />
732    code = 405
733    title = 'Method Not Allowed'
734    body_template_obj = Template('''\
735The method ${REQUEST_METHOD} is not allowed for this resource. ${br}${br}
736${detail}''')
737
738class HTTPNotAcceptable(HTTPClientError):
739    """
740    subclass of :class:`~HTTPClientError`
741
742    This indicates the resource identified by the request is only
743    capable of generating response entities which have content
744    characteristics not acceptable according to the accept headers
745    sent in the request.
746
747    code: 406, title: Not Acceptable
748    """
749    # differences from webob.exc.HTTPNotAcceptable:
750    #
751    # - "template" attribute left off (useless, bug in webob?)
752    code = 406
753    title = 'Not Acceptable'
754
755class HTTPProxyAuthenticationRequired(HTTPClientError):
756    """
757    subclass of :class:`~HTTPClientError`
758
759    This is similar to 401, but indicates that the client must first
760    authenticate itself with the proxy.
761
762    code: 407, title: Proxy Authentication Required
763    """
764    code = 407
765    title = 'Proxy Authentication Required'
766    explanation = ('Authentication with a local proxy is needed.')
767
768class HTTPRequestTimeout(HTTPClientError):
769    """
770    subclass of :class:`~HTTPClientError`
771
772    This indicates that the client did not produce a request within
773    the time that the server was prepared to wait.
774
775    code: 408, title: Request Timeout
776    """
777    code = 408
778    title = 'Request Timeout'
779    explanation = ('The server has waited too long for the request to '
780                   'be sent by the client.')
781
782class HTTPConflict(HTTPClientError):
783    """
784    subclass of :class:`~HTTPClientError`
785
786    This indicates that the request could not be completed due to a
787    conflict with the current state of the resource.
788
789    code: 409, title: Conflict
790    """
791    code = 409
792    title = 'Conflict'
793    explanation = ('There was a conflict when trying to complete '
794                   'your request.')
795
796class HTTPGone(HTTPClientError):
797    """
798    subclass of :class:`~HTTPClientError`
799
800    This indicates that the requested resource is no longer available
801    at the server and no forwarding address is known.
802
803    code: 410, title: Gone
804    """
805    code = 410
806    title = 'Gone'
807    explanation = ('This resource is no longer available.  No forwarding '
808                   'address is given.')
809
810class HTTPLengthRequired(HTTPClientError):
811    """
812    subclass of :class:`~HTTPClientError`
813
814    This indicates that the server refuses to accept the request
815    without a defined Content-Length.
816
817    code: 411, title: Length Required
818    """
819    code = 411
820    title = 'Length Required'
821    explanation = ('Content-Length header required.')
822
823class HTTPPreconditionFailed(HTTPClientError):
824    """
825    subclass of :class:`~HTTPClientError`
826
827    This indicates that the precondition given in one or more of the
828    request-header fields evaluated to false when it was tested on the
829    server.
830
831    code: 412, title: Precondition Failed
832    """
833    code = 412
834    title = 'Precondition Failed'
835    explanation = ('Request precondition failed.')
836
837class HTTPRequestEntityTooLarge(HTTPClientError):
838    """
839    subclass of :class:`~HTTPClientError`
840
841    This indicates that the server is refusing to process a request
842    because the request entity is larger than the server is willing or
843    able to process.
844
845    code: 413, title: Request Entity Too Large
846    """
847    code = 413
848    title = 'Request Entity Too Large'
849    explanation = ('The body of your request was too large for this server.')
850
851class HTTPRequestURITooLong(HTTPClientError):
852    """
853    subclass of :class:`~HTTPClientError`
854
855    This indicates that the server is refusing to service the request
856    because the Request-URI is longer than the server is willing to
857    interpret.
858
859    code: 414, title: Request-URI Too Long
860    """
861    code = 414
862    title = 'Request-URI Too Long'
863    explanation = ('The request URI was too long for this server.')
864
865class HTTPUnsupportedMediaType(HTTPClientError):
866    """
867    subclass of :class:`~HTTPClientError`
868
869    This indicates that the server is refusing to service the request
870    because the entity of the request is in a format not supported by
871    the requested resource for the requested method.
872
873    code: 415, title: Unsupported Media Type
874    """
875    # differences from webob.exc.HTTPUnsupportedMediaType:
876    #
877    # - "template_obj" attribute left off (useless, bug in webob?)
878    code = 415
879    title = 'Unsupported Media Type'
880
881class HTTPRequestRangeNotSatisfiable(HTTPClientError):
882    """
883    subclass of :class:`~HTTPClientError`
884
885    The server SHOULD return a response with this status code if a
886    request included a Range request-header field, and none of the
887    range-specifier values in this field overlap the current extent
888    of the selected resource, and the request did not include an
889    If-Range request-header field.
890
891    code: 416, title: Request Range Not Satisfiable
892    """
893    code = 416
894    title = 'Request Range Not Satisfiable'
895    explanation = ('The Range requested is not available.')
896
897class HTTPExpectationFailed(HTTPClientError):
898    """
899    subclass of :class:`~HTTPClientError`
900
901    This indidcates that the expectation given in an Expect
902    request-header field could not be met by this server.
903
904    code: 417, title: Expectation Failed
905    """
906    code = 417
907    title = 'Expectation Failed'
908    explanation = ('Expectation failed.')
909
910class HTTPUnprocessableEntity(HTTPClientError):
911    """
912    subclass of :class:`~HTTPClientError`
913
914    This indicates that the server is unable to process the contained
915    instructions.
916
917    May be used to notify the client that their JSON/XML is well formed, but
918    not correct for the current request.
919
920    See RFC4918 section 11 for more information.
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
963    request to be conditional.
964
965    Its typical use is to avoid the "lost update" problem, where a client
966    GETs a resource's state, modifies it, and PUTs it back to the server,
967    when meanwhile a third party has modified the state on the server,
968    leading to a conflict.  By requiring requests to be conditional, the
969    server can assure that clients are working with the correct copies.
970
971    RFC 6585.3
972
973    code: 428, title: Precondition Required
974    """
975    code = 428
976    title = 'Precondition Required'
977    explanation = (
978        'The origin server requires the request to be conditional.')
979
980class HTTPTooManyRequests(HTTPClientError):
981    """
982    subclass of :class:`~HTTPClientError`
983
984    This indicates that the user has sent too many
985    requests in a given amount of time ("rate limiting").
986
987    RFC 6585.4
988
989    code: 429, title: Too Many Requests
990    """
991    code = 429
992    title = 'Too Many Requests'
993    explanation = (
994        'The action could not be performed because there were too '
995        'many requests by the client.')
996
997class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
998    """
999    subclass of :class:`~HTTPClientError`
1000
1001    This indicates that the server is unwilling to process
1002    the request because its header fields are too large.  The request MAY
1003    be resubmitted after reducing the size of the request header fields.
1004
1005    RFC 6585.5
1006
1007    code: 431, title: Request Header Fields Too Large
1008    """
1009    code = 431
1010    title = 'Request Header Fields Too Large'
1011    explanation = (
1012        'The requests header fields were too large.')
1013
1014############################################################
1015## 5xx Server Error
1016############################################################
1017#  Response status codes beginning with the digit "5" indicate cases in
1018#  which the server is aware that it has erred or is incapable of
1019#  performing the request. Except when responding to a HEAD request, the
1020#  server SHOULD include an entity containing an explanation of the error
1021#  situation, and whether it is a temporary or permanent condition. User
1022#  agents SHOULD display any included entity to the user. These response
1023#  codes are applicable to any request method.
1024
1025class HTTPServerError(HTTPError):
1026    """
1027    base class for the 500s, where the server is in-error
1028
1029    This is an error condition in which the server is presumed to be
1030    in-error.  Unless specialized, this is a '500 Internal Server Error'.
1031    """
1032    pass
1033
1034class HTTPInternalServerError(HTTPServerError):
1035    code = 500
1036    title = 'Internal Server Error'
1037    explanation = (
1038        'The server has either erred or is incapable of performing '
1039        'the requested operation.')
1040
1041class HTTPNotImplemented(HTTPServerError):
1042    """
1043    subclass of :class:`~HTTPServerError`
1044
1045    This indicates that the server does not support the functionality
1046    required to fulfill the request.
1047
1048    code: 501, title: Not Implemented
1049    """
1050    # differences from webob.exc.HTTPNotAcceptable:
1051    #
1052    # - "template" attr left off (useless, bug in webob?)
1053    code = 501
1054    title = 'Not Implemented'
1055
1056class HTTPBadGateway(HTTPServerError):
1057    """
1058    subclass of :class:`~HTTPServerError`
1059
1060    This indicates that the server, while acting as a gateway or proxy,
1061    received an invalid response from the upstream server it accessed
1062    in attempting to fulfill the request.
1063
1064    code: 502, title: Bad Gateway
1065    """
1066    code = 502
1067    title = 'Bad Gateway'
1068    explanation = ('Bad gateway.')
1069
1070class HTTPServiceUnavailable(HTTPServerError):
1071    """
1072    subclass of :class:`~HTTPServerError`
1073
1074    This indicates that the server is currently unable to handle the
1075    request due to a temporary overloading or maintenance of the server.
1076
1077    code: 503, title: Service Unavailable
1078    """
1079    code = 503
1080    title = 'Service Unavailable'
1081    explanation = ('The server is currently unavailable. '
1082                   'Please try again at a later time.')
1083
1084class HTTPGatewayTimeout(HTTPServerError):
1085    """
1086    subclass of :class:`~HTTPServerError`
1087
1088    This indicates that the server, while acting as a gateway or proxy,
1089    did not receive a timely response from the upstream server specified
1090    by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
1091    (e.g. DNS) it needed to access in attempting to complete the request.
1092
1093    code: 504, title: Gateway Timeout
1094    """
1095    code = 504
1096    title = 'Gateway Timeout'
1097    explanation = ('The gateway has timed out.')
1098
1099class HTTPVersionNotSupported(HTTPServerError):
1100    """
1101    subclass of :class:`~HTTPServerError`
1102
1103    This indicates that the server does not support, or refuses to
1104    support, the HTTP protocol version that was used in the request
1105    message.
1106
1107    code: 505, title: HTTP Version Not Supported
1108    """
1109    code = 505
1110    title = 'HTTP Version Not Supported'
1111    explanation = ('The HTTP version is not supported.')
1112
1113class HTTPInsufficientStorage(HTTPServerError):
1114    """
1115    subclass of :class:`~HTTPServerError`
1116
1117    This indicates that the server does not have enough space to save
1118    the resource.
1119
1120    code: 507, title: Insufficient Storage
1121    """
1122    code = 507
1123    title = 'Insufficient Storage'
1124    explanation = ('There was not enough space to save the resource')
1125
1126def exception_response(status_code, **kw):
1127    """Creates an HTTP exception based on a status code. Example::
1128
1129        raise exception_response(404) # raises an HTTPNotFound exception.
1130
1131    The values passed as ``kw`` are provided to the exception's constructor.
1132    """
1133    exc = status_map[status_code](**kw)
1134    return exc
1135
1136def default_exceptionresponse_view(context, request):
1137    if not isinstance(context, Exception):
1138        # backwards compat for an exception response view registered via
1139        # config.set_notfound_view or config.set_forbidden_view
1140        # instead of as a proper exception view
1141        context = request.exception or context
1142    return context # assumed to be an IResponse
1143
1144status_map = {}
1145code = None
1146for name, value in list(globals().items()):
1147    if (
1148            isinstance(value, class_types) and
1149            issubclass(value, HTTPException) and
1150            not name.startswith('_')
1151    ):
1152        code = getattr(value, 'code', None)
1153        if code:
1154            status_map[code] = value
1155del name, value, code
1156