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