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