1# -*- coding: utf-8 -*-
2"""
3    werkzeug.exceptions
4    ~~~~~~~~~~~~~~~~~~~
5
6    This module implements a number of Python exceptions you can raise from
7    within your views to trigger a standard non-200 response.
8
9
10    Usage Example
11    -------------
12
13    ::
14
15        from werkzeug.wrappers import BaseRequest
16        from werkzeug.wsgi import responder
17        from werkzeug.exceptions import HTTPException, NotFound
18
19        def view(request):
20            raise NotFound()
21
22        @responder
23        def application(environ, start_response):
24            request = BaseRequest(environ)
25            try:
26                return view(request)
27            except HTTPException as e:
28                return e
29
30
31    As you can see from this example those exceptions are callable WSGI
32    applications.  Because of Python 2.4 compatibility those do not extend
33    from the response objects but only from the python exception class.
34
35    As a matter of fact they are not Werkzeug response objects.  However you
36    can get a response object by calling ``get_response()`` on a HTTP
37    exception.
38
39    Keep in mind that you have to pass an environment to ``get_response()``
40    because some errors fetch additional information from the WSGI
41    environment.
42
43    If you want to hook in a different exception page to say, a 404 status
44    code, you can add a second except for a specific subclass of an error::
45
46        @responder
47        def application(environ, start_response):
48            request = BaseRequest(environ)
49            try:
50                return view(request)
51            except NotFound, e:
52                return not_found(request)
53            except HTTPException, e:
54                return e
55
56
57    :copyright: 2007 Pallets
58    :license: BSD-3-Clause
59"""
60import sys
61from datetime import datetime
62
63from ._compat import implements_to_string
64from ._compat import integer_types
65from ._compat import iteritems
66from ._compat import text_type
67from ._internal import _get_environ
68from .utils import escape
69
70
71@implements_to_string
72class HTTPException(Exception):
73    """Baseclass for all HTTP exceptions.  This exception can be called as WSGI
74    application to render a default error page or you can catch the subclasses
75    of it independently and render nicer error messages.
76    """
77
78    code = None
79    description = None
80
81    def __init__(self, description=None, response=None):
82        super(HTTPException, self).__init__()
83        if description is not None:
84            self.description = description
85        self.response = response
86
87    @classmethod
88    def wrap(cls, exception, name=None):
89        """Create an exception that is a subclass of the calling HTTP
90        exception and the ``exception`` argument.
91
92        The first argument to the class will be passed to the
93        wrapped ``exception``, the rest to the HTTP exception. If
94        ``e.args`` is not empty and ``e.show_exception`` is ``True``,
95        the wrapped exception message is added to the HTTP error
96        description.
97
98        .. versionchanged:: 0.15.5
99            The ``show_exception`` attribute controls whether the
100            description includes the wrapped exception message.
101
102        .. versionchanged:: 0.15.0
103            The description includes the wrapped exception message.
104        """
105
106        class newcls(cls, exception):
107            _description = cls.description
108            show_exception = False
109
110            def __init__(self, arg=None, *args, **kwargs):
111                super(cls, self).__init__(*args, **kwargs)
112
113                if arg is None:
114                    exception.__init__(self)
115                else:
116                    exception.__init__(self, arg)
117
118            @property
119            def description(self):
120                if self.show_exception:
121                    return "{}\n{}: {}".format(
122                        self._description, exception.__name__, exception.__str__(self)
123                    )
124
125                return self._description
126
127            @description.setter
128            def description(self, value):
129                self._description = value
130
131        newcls.__module__ = sys._getframe(1).f_globals.get("__name__")
132        name = name or cls.__name__ + exception.__name__
133        newcls.__name__ = newcls.__qualname__ = name
134        return newcls
135
136    @property
137    def name(self):
138        """The status name."""
139        from .http import HTTP_STATUS_CODES
140
141        return HTTP_STATUS_CODES.get(self.code, "Unknown Error")
142
143    def get_description(self, environ=None):
144        """Get the description."""
145        return u"<p>%s</p>" % escape(self.description).replace("\n", "<br>")
146
147    def get_body(self, environ=None):
148        """Get the HTML body."""
149        return text_type(
150            (
151                u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
152                u"<title>%(code)s %(name)s</title>\n"
153                u"<h1>%(name)s</h1>\n"
154                u"%(description)s\n"
155            )
156            % {
157                "code": self.code,
158                "name": escape(self.name),
159                "description": self.get_description(environ),
160            }
161        )
162
163    def get_headers(self, environ=None):
164        """Get a list of headers."""
165        return [("Content-Type", "text/html; charset=utf-8")]
166
167    def get_response(self, environ=None):
168        """Get a response object.  If one was passed to the exception
169        it's returned directly.
170
171        :param environ: the optional environ for the request.  This
172                        can be used to modify the response depending
173                        on how the request looked like.
174        :return: a :class:`Response` object or a subclass thereof.
175        """
176        from .wrappers.response import Response
177
178        if self.response is not None:
179            return self.response
180        if environ is not None:
181            environ = _get_environ(environ)
182        headers = self.get_headers(environ)
183        return Response(self.get_body(environ), self.code, headers)
184
185    def __call__(self, environ, start_response):
186        """Call the exception as WSGI application.
187
188        :param environ: the WSGI environment.
189        :param start_response: the response callable provided by the WSGI
190                               server.
191        """
192        response = self.get_response(environ)
193        return response(environ, start_response)
194
195    def __str__(self):
196        code = self.code if self.code is not None else "???"
197        return "%s %s: %s" % (code, self.name, self.description)
198
199    def __repr__(self):
200        code = self.code if self.code is not None else "???"
201        return "<%s '%s: %s'>" % (self.__class__.__name__, code, self.name)
202
203
204class BadRequest(HTTPException):
205    """*400* `Bad Request`
206
207    Raise if the browser sends something to the application the application
208    or server cannot handle.
209    """
210
211    code = 400
212    description = (
213        "The browser (or proxy) sent a request that this server could "
214        "not understand."
215    )
216
217
218class ClientDisconnected(BadRequest):
219    """Internal exception that is raised if Werkzeug detects a disconnected
220    client.  Since the client is already gone at that point attempting to
221    send the error message to the client might not work and might ultimately
222    result in another exception in the server.  Mainly this is here so that
223    it is silenced by default as far as Werkzeug is concerned.
224
225    Since disconnections cannot be reliably detected and are unspecified
226    by WSGI to a large extent this might or might not be raised if a client
227    is gone.
228
229    .. versionadded:: 0.8
230    """
231
232
233class SecurityError(BadRequest):
234    """Raised if something triggers a security error.  This is otherwise
235    exactly like a bad request error.
236
237    .. versionadded:: 0.9
238    """
239
240
241class BadHost(BadRequest):
242    """Raised if the submitted host is badly formatted.
243
244    .. versionadded:: 0.11.2
245    """
246
247
248class Unauthorized(HTTPException):
249    """*401* ``Unauthorized``
250
251    Raise if the user is not authorized to access a resource.
252
253    The ``www_authenticate`` argument should be used to set the
254    ``WWW-Authenticate`` header. This is used for HTTP basic auth and
255    other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate`
256    to create correctly formatted values. Strictly speaking a 401
257    response is invalid if it doesn't provide at least one value for
258    this header, although real clients typically don't care.
259
260    :param description: Override the default message used for the body
261        of the response.
262    :param www-authenticate: A single value, or list of values, for the
263        WWW-Authenticate header.
264
265    .. versionchanged:: 0.15.3
266        If the ``www_authenticate`` argument is not set, the
267        ``WWW-Authenticate`` header is not set.
268
269    .. versionchanged:: 0.15.3
270        The ``response`` argument was restored.
271
272    .. versionchanged:: 0.15.1
273        ``description`` was moved back as the first argument, restoring
274         its previous position.
275
276    .. versionchanged:: 0.15.0
277        ``www_authenticate`` was added as the first argument, ahead of
278        ``description``.
279    """
280
281    code = 401
282    description = (
283        "The server could not verify that you are authorized to access"
284        " the URL requested. You either supplied the wrong credentials"
285        " (e.g. a bad password), or your browser doesn't understand"
286        " how to supply the credentials required."
287    )
288
289    def __init__(self, description=None, response=None, www_authenticate=None):
290        HTTPException.__init__(self, description, response)
291
292        if www_authenticate is not None:
293            if not isinstance(www_authenticate, (tuple, list)):
294                www_authenticate = (www_authenticate,)
295
296        self.www_authenticate = www_authenticate
297
298    def get_headers(self, environ=None):
299        headers = HTTPException.get_headers(self, environ)
300        if self.www_authenticate:
301            headers.append(
302                ("WWW-Authenticate", ", ".join([str(x) for x in self.www_authenticate]))
303            )
304        return headers
305
306
307class Forbidden(HTTPException):
308    """*403* `Forbidden`
309
310    Raise if the user doesn't have the permission for the requested resource
311    but was authenticated.
312    """
313
314    code = 403
315    description = (
316        "You don't have the permission to access the requested"
317        " resource. It is either read-protected or not readable by the"
318        " server."
319    )
320
321
322class NotFound(HTTPException):
323    """*404* `Not Found`
324
325    Raise if a resource does not exist and never existed.
326    """
327
328    code = 404
329    description = (
330        "The requested URL was not found on the server. If you entered"
331        " the URL manually please check your spelling and try again."
332    )
333
334
335class MethodNotAllowed(HTTPException):
336    """*405* `Method Not Allowed`
337
338    Raise if the server used a method the resource does not handle.  For
339    example `POST` if the resource is view only.  Especially useful for REST.
340
341    The first argument for this exception should be a list of allowed methods.
342    Strictly speaking the response would be invalid if you don't provide valid
343    methods in the header which you can do with that list.
344    """
345
346    code = 405
347    description = "The method is not allowed for the requested URL."
348
349    def __init__(self, valid_methods=None, description=None):
350        """Takes an optional list of valid http methods
351        starting with werkzeug 0.3 the list will be mandatory."""
352        HTTPException.__init__(self, description)
353        self.valid_methods = valid_methods
354
355    def get_headers(self, environ=None):
356        headers = HTTPException.get_headers(self, environ)
357        if self.valid_methods:
358            headers.append(("Allow", ", ".join(self.valid_methods)))
359        return headers
360
361
362class NotAcceptable(HTTPException):
363    """*406* `Not Acceptable`
364
365    Raise if the server can't return any content conforming to the
366    `Accept` headers of the client.
367    """
368
369    code = 406
370
371    description = (
372        "The resource identified by the request is only capable of"
373        " generating response entities which have content"
374        " characteristics not acceptable according to the accept"
375        " headers sent in the request."
376    )
377
378
379class RequestTimeout(HTTPException):
380    """*408* `Request Timeout`
381
382    Raise to signalize a timeout.
383    """
384
385    code = 408
386    description = (
387        "The server closed the network connection because the browser"
388        " didn't finish the request within the specified time."
389    )
390
391
392class Conflict(HTTPException):
393    """*409* `Conflict`
394
395    Raise to signal that a request cannot be completed because it conflicts
396    with the current state on the server.
397
398    .. versionadded:: 0.7
399    """
400
401    code = 409
402    description = (
403        "A conflict happened while processing the request. The"
404        " resource might have been modified while the request was being"
405        " processed."
406    )
407
408
409class Gone(HTTPException):
410    """*410* `Gone`
411
412    Raise if a resource existed previously and went away without new location.
413    """
414
415    code = 410
416    description = (
417        "The requested URL is no longer available on this server and"
418        " there is no forwarding address. If you followed a link from a"
419        " foreign page, please contact the author of this page."
420    )
421
422
423class LengthRequired(HTTPException):
424    """*411* `Length Required`
425
426    Raise if the browser submitted data but no ``Content-Length`` header which
427    is required for the kind of processing the server does.
428    """
429
430    code = 411
431    description = (
432        "A request with this method requires a valid <code>Content-"
433        "Length</code> header."
434    )
435
436
437class PreconditionFailed(HTTPException):
438    """*412* `Precondition Failed`
439
440    Status code used in combination with ``If-Match``, ``If-None-Match``, or
441    ``If-Unmodified-Since``.
442    """
443
444    code = 412
445    description = (
446        "The precondition on the request for the URL failed positive evaluation."
447    )
448
449
450class RequestEntityTooLarge(HTTPException):
451    """*413* `Request Entity Too Large`
452
453    The status code one should return if the data submitted exceeded a given
454    limit.
455    """
456
457    code = 413
458    description = "The data value transmitted exceeds the capacity limit."
459
460
461class RequestURITooLarge(HTTPException):
462    """*414* `Request URI Too Large`
463
464    Like *413* but for too long URLs.
465    """
466
467    code = 414
468    description = (
469        "The length of the requested URL exceeds the capacity limit for"
470        " this server. The request cannot be processed."
471    )
472
473
474class UnsupportedMediaType(HTTPException):
475    """*415* `Unsupported Media Type`
476
477    The status code returned if the server is unable to handle the media type
478    the client transmitted.
479    """
480
481    code = 415
482    description = (
483        "The server does not support the media type transmitted in the request."
484    )
485
486
487class RequestedRangeNotSatisfiable(HTTPException):
488    """*416* `Requested Range Not Satisfiable`
489
490    The client asked for an invalid part of the file.
491
492    .. versionadded:: 0.7
493    """
494
495    code = 416
496    description = "The server cannot provide the requested range."
497
498    def __init__(self, length=None, units="bytes", description=None):
499        """Takes an optional `Content-Range` header value based on ``length``
500        parameter.
501        """
502        HTTPException.__init__(self, description)
503        self.length = length
504        self.units = units
505
506    def get_headers(self, environ=None):
507        headers = HTTPException.get_headers(self, environ)
508        if self.length is not None:
509            headers.append(("Content-Range", "%s */%d" % (self.units, self.length)))
510        return headers
511
512
513class ExpectationFailed(HTTPException):
514    """*417* `Expectation Failed`
515
516    The server cannot meet the requirements of the Expect request-header.
517
518    .. versionadded:: 0.7
519    """
520
521    code = 417
522    description = "The server could not meet the requirements of the Expect header"
523
524
525class ImATeapot(HTTPException):
526    """*418* `I'm a teapot`
527
528    The server should return this if it is a teapot and someone attempted
529    to brew coffee with it.
530
531    .. versionadded:: 0.7
532    """
533
534    code = 418
535    description = "This server is a teapot, not a coffee machine"
536
537
538class UnprocessableEntity(HTTPException):
539    """*422* `Unprocessable Entity`
540
541    Used if the request is well formed, but the instructions are otherwise
542    incorrect.
543    """
544
545    code = 422
546    description = (
547        "The request was well-formed but was unable to be followed due"
548        " to semantic errors."
549    )
550
551
552class Locked(HTTPException):
553    """*423* `Locked`
554
555    Used if the resource that is being accessed is locked.
556    """
557
558    code = 423
559    description = "The resource that is being accessed is locked."
560
561
562class FailedDependency(HTTPException):
563    """*424* `Failed Dependency`
564
565    Used if the method could not be performed on the resource
566    because the requested action depended on another action and that action failed.
567    """
568
569    code = 424
570    description = (
571        "The method could not be performed on the resource because the"
572        " requested action depended on another action and that action"
573        " failed."
574    )
575
576
577class PreconditionRequired(HTTPException):
578    """*428* `Precondition Required`
579
580    The server requires this request to be conditional, typically to prevent
581    the lost update problem, which is a race condition between two or more
582    clients attempting to update a resource through PUT or DELETE. By requiring
583    each client to include a conditional header ("If-Match" or "If-Unmodified-
584    Since") with the proper value retained from a recent GET request, the
585    server ensures that each client has at least seen the previous revision of
586    the resource.
587    """
588
589    code = 428
590    description = (
591        "This request is required to be conditional; try using"
592        ' "If-Match" or "If-Unmodified-Since".'
593    )
594
595
596class _RetryAfter(HTTPException):
597    """Adds an optional ``retry_after`` parameter which will set the
598    ``Retry-After`` header. May be an :class:`int` number of seconds or
599    a :class:`~datetime.datetime`.
600    """
601
602    def __init__(self, description=None, response=None, retry_after=None):
603        super(_RetryAfter, self).__init__(description, response)
604        self.retry_after = retry_after
605
606    def get_headers(self, environ=None):
607        headers = super(_RetryAfter, self).get_headers(environ)
608
609        if self.retry_after:
610            if isinstance(self.retry_after, datetime):
611                from .http import http_date
612
613                value = http_date(self.retry_after)
614            else:
615                value = str(self.retry_after)
616
617            headers.append(("Retry-After", value))
618
619        return headers
620
621
622class TooManyRequests(_RetryAfter):
623    """*429* `Too Many Requests`
624
625    The server is limiting the rate at which this user receives
626    responses, and this request exceeds that rate. (The server may use
627    any convenient method to identify users and their request rates).
628    The server may include a "Retry-After" header to indicate how long
629    the user should wait before retrying.
630
631    :param retry_after: If given, set the ``Retry-After`` header to this
632        value. May be an :class:`int` number of seconds or a
633        :class:`~datetime.datetime`.
634
635    .. versionchanged:: 1.0
636        Added ``retry_after`` parameter.
637    """
638
639    code = 429
640    description = "This user has exceeded an allotted request count. Try again later."
641
642
643class RequestHeaderFieldsTooLarge(HTTPException):
644    """*431* `Request Header Fields Too Large`
645
646    The server refuses to process the request because the header fields are too
647    large. One or more individual fields may be too large, or the set of all
648    headers is too large.
649    """
650
651    code = 431
652    description = "One or more header fields exceeds the maximum size."
653
654
655class UnavailableForLegalReasons(HTTPException):
656    """*451* `Unavailable For Legal Reasons`
657
658    This status code indicates that the server is denying access to the
659    resource as a consequence of a legal demand.
660    """
661
662    code = 451
663    description = "Unavailable for legal reasons."
664
665
666class InternalServerError(HTTPException):
667    """*500* `Internal Server Error`
668
669    Raise if an internal server error occurred.  This is a good fallback if an
670    unknown error occurred in the dispatcher.
671
672    .. versionchanged:: 1.0.0
673        Added the :attr:`original_exception` attribute.
674    """
675
676    code = 500
677    description = (
678        "The server encountered an internal error and was unable to"
679        " complete your request. Either the server is overloaded or"
680        " there is an error in the application."
681    )
682
683    def __init__(self, description=None, response=None, original_exception=None):
684        #: The original exception that caused this 500 error. Can be
685        #: used by frameworks to provide context when handling
686        #: unexpected errors.
687        self.original_exception = original_exception
688        super(InternalServerError, self).__init__(
689            description=description, response=response
690        )
691
692
693class NotImplemented(HTTPException):
694    """*501* `Not Implemented`
695
696    Raise if the application does not support the action requested by the
697    browser.
698    """
699
700    code = 501
701    description = "The server does not support the action requested by the browser."
702
703
704class BadGateway(HTTPException):
705    """*502* `Bad Gateway`
706
707    If you do proxying in your application you should return this status code
708    if you received an invalid response from the upstream server it accessed
709    in attempting to fulfill the request.
710    """
711
712    code = 502
713    description = (
714        "The proxy server received an invalid response from an upstream server."
715    )
716
717
718class ServiceUnavailable(_RetryAfter):
719    """*503* `Service Unavailable`
720
721    Status code you should return if a service is temporarily
722    unavailable.
723
724    :param retry_after: If given, set the ``Retry-After`` header to this
725        value. May be an :class:`int` number of seconds or a
726        :class:`~datetime.datetime`.
727
728    .. versionchanged:: 1.0
729        Added ``retry_after`` parameter.
730    """
731
732    code = 503
733    description = (
734        "The server is temporarily unable to service your request due"
735        " to maintenance downtime or capacity problems. Please try"
736        " again later."
737    )
738
739
740class GatewayTimeout(HTTPException):
741    """*504* `Gateway Timeout`
742
743    Status code you should return if a connection to an upstream server
744    times out.
745    """
746
747    code = 504
748    description = "The connection to an upstream server timed out."
749
750
751class HTTPVersionNotSupported(HTTPException):
752    """*505* `HTTP Version Not Supported`
753
754    The server does not support the HTTP protocol version used in the request.
755    """
756
757    code = 505
758    description = (
759        "The server does not support the HTTP protocol version used in the request."
760    )
761
762
763default_exceptions = {}
764__all__ = ["HTTPException"]
765
766
767def _find_exceptions():
768    for _name, obj in iteritems(globals()):
769        try:
770            is_http_exception = issubclass(obj, HTTPException)
771        except TypeError:
772            is_http_exception = False
773        if not is_http_exception or obj.code is None:
774            continue
775        __all__.append(obj.__name__)
776        old_obj = default_exceptions.get(obj.code, None)
777        if old_obj is not None and issubclass(obj, old_obj):
778            continue
779        default_exceptions[obj.code] = obj
780
781
782_find_exceptions()
783del _find_exceptions
784
785
786class Aborter(object):
787    """When passed a dict of code -> exception items it can be used as
788    callable that raises exceptions.  If the first argument to the
789    callable is an integer it will be looked up in the mapping, if it's
790    a WSGI application it will be raised in a proxy exception.
791
792    The rest of the arguments are forwarded to the exception constructor.
793    """
794
795    def __init__(self, mapping=None, extra=None):
796        if mapping is None:
797            mapping = default_exceptions
798        self.mapping = dict(mapping)
799        if extra is not None:
800            self.mapping.update(extra)
801
802    def __call__(self, code, *args, **kwargs):
803        if not args and not kwargs and not isinstance(code, integer_types):
804            raise HTTPException(response=code)
805        if code not in self.mapping:
806            raise LookupError("no exception for %r" % code)
807        raise self.mapping[code](*args, **kwargs)
808
809
810def abort(status, *args, **kwargs):
811    """Raises an :py:exc:`HTTPException` for the given status code or WSGI
812    application.
813
814    If a status code is given, it will be looked up in the list of
815    exceptions and will raise that exception.  If passed a WSGI application,
816    it will wrap it in a proxy WSGI exception and raise that::
817
818       abort(404)  # 404 Not Found
819       abort(Response('Hello World'))
820
821    """
822    return _aborter(status, *args, **kwargs)
823
824
825_aborter = Aborter()
826
827#: An exception that is used to signal both a :exc:`KeyError` and a
828#: :exc:`BadRequest`. Used by many of the datastructures.
829BadRequestKeyError = BadRequest.wrap(KeyError)
830