#!/usr/bin/env python # -*- coding: utf-8 -*- """ | This file is part of the web2py Web Framework | Copyrighted by Massimo Di Pierro | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) HTTP statuses helpers -------------------------------------------- """ import re from gluon._compat import iteritems, to_bytes, unicodeT __all__ = ["HTTP", "redirect"] defined_status = { 200: "OK", 201: "CREATED", 202: "ACCEPTED", 203: "NON-AUTHORITATIVE INFORMATION", 204: "NO CONTENT", 205: "RESET CONTENT", 206: "PARTIAL CONTENT", 301: "MOVED PERMANENTLY", 302: "FOUND", 303: "SEE OTHER", 304: "NOT MODIFIED", 305: "USE PROXY", 307: "TEMPORARY REDIRECT", 400: "BAD REQUEST", 401: "UNAUTHORIZED", 402: "PAYMENT REQUIRED", 403: "FORBIDDEN", 404: "NOT FOUND", 405: "METHOD NOT ALLOWED", 406: "NOT ACCEPTABLE", 407: "PROXY AUTHENTICATION REQUIRED", 408: "REQUEST TIMEOUT", 409: "CONFLICT", 410: "GONE", 411: "LENGTH REQUIRED", 412: "PRECONDITION FAILED", 413: "REQUEST ENTITY TOO LARGE", 414: "REQUEST-URI TOO LONG", 415: "UNSUPPORTED MEDIA TYPE", 416: "REQUESTED RANGE NOT SATISFIABLE", 417: "EXPECTATION FAILED", 422: "UNPROCESSABLE ENTITY", 429: "TOO MANY REQUESTS", 451: "UNAVAILABLE FOR LEGAL REASONS", # http://www.451unavailable.org/ 500: "INTERNAL SERVER ERROR", 501: "NOT IMPLEMENTED", 502: "BAD GATEWAY", 503: "SERVICE UNAVAILABLE", 504: "GATEWAY TIMEOUT", 505: "HTTP VERSION NOT SUPPORTED", 509: "BANDWIDTH LIMIT EXCEEDED", } regex_status = re.compile("^\d{3} [0-9A-Z ]+$") regex_header_newlines = re.compile(r"[\r\n]") class HTTP(Exception): """Raises an HTTP response Args: status: usually an integer. If it's a well known status code, the ERROR message will be automatically added. A string can also be passed as `510 Foo Bar` and in that case the status code and the error message will be parsed accordingly body: what to return as body. If left as is, will return the error code and the status message in the body itself cookies: pass cookies along (usually not needed) headers: pass headers as usual dict mapping """ def __init__(self, status, body="", cookies=None, **headers): self.status = status self.body = body self.headers = {} for k, v in iteritems(headers): if isinstance(v, list): self.headers[k] = [ regex_header_newlines.sub("", str(item)) for item in v ] elif v is not None: self.headers[k] = regex_header_newlines.sub("", str(v)) self.cookies2headers(cookies) def cookies2headers(self, cookies): if cookies and len(cookies) > 0: self.headers["Set-Cookie"] = [ str(cookie)[11:] for cookie in cookies.values() ] def to(self, responder, env=None): env = env or {} status = self.status headers = self.headers if status in defined_status: status = "%d %s" % (status, defined_status[status]) elif isinstance(status, int): status = "%d UNKNOWN ERROR" % status else: status = str(status) if not regex_status.match(status): status = "500 %s" % (defined_status[500]) headers.setdefault("Content-Type", "text/html; charset=UTF-8") body = self.body if status[:1] == "4": if not body: body = status if isinstance(body, (str, bytes, bytearray)): if isinstance(body, unicodeT): body = to_bytes(body) # This must be done before len headers["Content-Length"] = len(body) rheaders = [] for k, v in iteritems(headers): if isinstance(v, list): rheaders += [(k, str(item)) for item in v] else: rheaders.append((k, str(v))) responder(status, rheaders) if env.get("request_method", "") == "HEAD": return [to_bytes("")] elif isinstance(body, (str, bytes, bytearray)): if isinstance(body, unicodeT): body = to_bytes(body) return [body] elif hasattr(body, "__iter__"): return body else: body = str(body) if isinstance(body, unicodeT): body = to_bytes(body) return [body] @property def message(self): """ compose a message describing this exception "status defined_status [web2py_error]" message elements that are not defined are omitted """ msg = "%(status)s" if self.status in defined_status: msg = "%(status)s %(defined_status)s" if "web2py_error" in self.headers: msg += " [%(web2py_error)s]" return msg % dict( status=self.status, defined_status=defined_status.get(self.status), web2py_error=self.headers.get("web2py_error"), ) def __str__(self): """stringify me""" return self.message def redirect(location="", how=303, client_side=False, headers=None): """Raises a redirect (303) Args: location: the url where to redirect how: what HTTP status code to use when redirecting client_side: if set to True, it triggers a reload of the entire page when the fragment has been loaded as a component headers: dictionary for request headers """ headers = headers or {} if location: from gluon.globals import current loc = location.replace("\r", "%0D").replace("\n", "%0A") if client_side and current.request.ajax: headers["web2py-redirect-location"] = loc raise HTTP(200, **headers) else: headers["Location"] = loc raise HTTP( how, 'You are being redirected here' % loc, **headers ) else: from gluon.globals import current if client_side and current.request.ajax: headers["web2py-component-command"] = "window.location.reload(true)" raise HTTP(200, **headers)