Example #1
0
def validate_since():
    """Validate the current Last-Modified against If-Modified-Since headers.

    If no code has set the Last-Modified response header, then no validation
    will be performed.
    """
    response = cherrypy.serving.response
    lastmod = response.headers.get('Last-Modified')
    if lastmod:
        status, reason, msg = _httputil.valid_status(response.status)

        request = cherrypy.serving.request

        since = request.headers.get('If-Unmodified-Since')
        if since and since != lastmod:
            if (status >= 200 and status <= 299) or status == 412:
                raise cherrypy.HTTPError(412)

        since = request.headers.get('If-Modified-Since')
        if since and since == lastmod:
            if (status >= 200 and status <= 299) or status == 304:
                if request.method in ('GET', 'HEAD'):
                    raise cherrypy.HTTPRedirect([], 304)
                else:
                    raise cherrypy.HTTPError(412)
    def finalize(self):
        try:
            code, reason, _ = httputil.valid_status(self.status)
        except ValueError:
            raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])

        headers = self.headers
        self.output_status = ntob(str(code), 'ascii') + ntob(' ') + headers.encode(reason)
        if self.stream:
            if dict.get(headers, 'Content-Length') is None:
                dict.pop(headers, 'Content-Length', None)
        elif code < 200 or code in (204, 205, 304):
            dict.pop(headers, 'Content-Length', None)
            self.body = ntob('')
        elif dict.get(headers, 'Content-Length') is None:
            content = self.collapse_body()
            dict.__setitem__(headers, 'Content-Length', len(content))
        self.header_list = h = headers.output()
        cookie = self.cookie.output()
        if cookie:
            for line in cookie.split('\n'):
                if line.endswith('\r'):
                    line = line[:-1]
                name, value = line.split(': ', 1)
                if isinstance(name, unicodestr):
                    name = name.encode('ISO-8859-1')
                if isinstance(value, unicodestr):
                    value = headers.encode(value)
                h.append((name, value))
Example #3
0
    def __init__(self, status = 500, message = None):
        self.status = status
        try:
            self.code, self.reason, defaultmsg = _httputil.valid_status(status)
        except ValueError as x:
            raise self.__class__(500, x.args[0])

        if self.code < 400 or self.code > 599:
            raise ValueError('status must be between 400 and 599.')
        self._message = message or defaultmsg
        CherryPyException.__init__(self, status, message)
Example #4
0
def get_error_page(status, **kwargs):
    """Return an HTML page, containing a pretty error response.
    
    status should be an int or a str.
    kwargs will be interpolated into the page template.
    """
    import cherrypy
    
    try:
        code, reason, message = _httputil.valid_status(status)
    except ValueError, x:
        raise cherrypy.HTTPError(500, x.args[0])
Example #5
0
def _etag_match(status, etagval, match, nomatch):
    """Match ETag value against any If-Match / If-None-Match headers."""
    # Execute conditions only for status 2xx. We only handle GET/HEAD
    # requests here, it makes no sense to try to do this for PUT etc.
    # as they need to be handled as request pre-condition, not in the
    # streaming out part here.
    if cherrypy.request.method in ('GET', 'HEAD'):
        status, reason, msg = httputil.valid_status(status)
        if status >= 200 and status <= 299:
            if match and ("*" in match or etagval in match):
                raise cherrypy.HTTPError(412, "Precondition on ETag %s failed" % etagval)
            if nomatch and ("*" in nomatch or etagval in nomatch):
                raise cherrypy.HTTPRedirect([], 304)
Example #6
0
    def __init__(self, status=500, message=None):
        self.status = status
        try:
            self.code, self.reason, defaultmsg = _httputil.valid_status(status)
        except ValueError:
            raise self.__class__(500, _exc_info()[1].args[0])

        if self.code < 400 or self.code > 599:
            raise ValueError("status must be between 400 and 599.")

        # See http://www.python.org/dev/peps/pep-0352/
        # self.message = message
        self._message = message or defaultmsg
        CherryPyException.__init__(self, status, message)
Example #7
0
def generic_error_handler(status, message, traceback, version):
    """error_page.default"""

    response = cherrypy.response
    response.headers['Content-Type'] = "application/json"
    response.headers.pop('Content-Length', None)

    code, reason, _ = cphttputil.valid_status(status)
    result = {"code": code, "reason": reason, "message": message}
    if hasattr(cherrypy.request, "params"):
        params = cherrypy.request.params
        if "debug" in params and params["debug"]:
            result["traceback"] = traceback
    return json.dumps(result)
Example #8
0
def get_error_page(status, **kwargs):
    """Return an HTML page, containing a pretty error response.

    status should be an int or a str.
    kwargs will be interpolated into the page template.
    """
    import cherrypy

    try:
        code, reason, message = _httputil.valid_status(status)
    except ValueError:
        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])

    # We can't use setdefault here, because some
    # callers send None for kwarg values.
    if kwargs.get('status') is None:
        kwargs['status'] = "%s %s" % (code, reason)
    if kwargs.get('message') is None:
        kwargs['message'] = message
    if kwargs.get('traceback') is None:
        kwargs['traceback'] = ''
    if kwargs.get('version') is None:
        kwargs['version'] = cherrypy.__version__

    for k, v in iteritems(kwargs):
        if v is None:
            kwargs[k] = ""
        else:
            kwargs[k] = _escape(kwargs[k])

    # Use a custom template or callable for the error page?
    pages = cherrypy.serving.request.error_page
    error_page = pages.get(code) or pages.get('default')
    if error_page:
        try:
            if hasattr(error_page, '__call__'):
                return error_page(**kwargs)
            else:
                data = open(error_page, 'rb').read()
                return tonative(data) % kwargs
        except:
            e = _format_exception(*_exc_info())[-1]
            m = kwargs['message']
            if m:
                m += "<br />"
            m += "In addition, the custom error page failed:\n<br />%s" % e
            kwargs['message'] = m

    return _HTTPErrorTemplate % kwargs
Example #9
0
    def finalize(self):
        """Transform headers (and cookies) into self.header_list. (Core)"""
        try:
            code, reason, _ = httputil.valid_status(self.status)
        except ValueError:
            raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])

        headers = self.headers

        self.status = "%s %s" % (code, reason)
        self.output_status = ntob(str(code), 'ascii') + \
            ntob(" ") + headers.encode(reason)

        if self.stream:
            # The upshot: wsgiserver will chunk the response if
            # you pop Content-Length (or set it explicitly to None).
            # Note that lib.static sets C-L to the file's st_size.
            if dict.get(headers, 'Content-Length') is None:
                dict.pop(headers, 'Content-Length', None)
        elif code < 200 or code in (204, 205, 304):
            # "All 1xx (informational), 204 (no content),
            # and 304 (not modified) responses MUST NOT
            # include a message-body."
            dict.pop(headers, 'Content-Length', None)
            self.body = ntob("")
        else:
            # Responses which are not streamed should have a Content-Length,
            # but allow user code to set Content-Length if desired.
            if dict.get(headers, 'Content-Length') is None:
                content = self.collapse_body()
                dict.__setitem__(headers, 'Content-Length', len(content))

        # Transform our header dict into a list of tuples.
        self.header_list = h = headers.output()

        cookie = self.cookie.output()
        if cookie:
            for line in cookie.split("\n"):
                if line.endswith("\r"):
                    # Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
                    line = line[:-1]
                name, value = line.split(": ", 1)
                if isinstance(name, unicodestr):
                    name = name.encode("ISO-8859-1")
                if isinstance(value, unicodestr):
                    value = headers.encode(value)
                h.append((name, value))
Example #10
0
def generic_json_error_handler(status, message, traceback, version,
                               errors=None):
    """error_page.default"""

    response = cherrypy.response
    response.headers['Content-Type'] = 'application/json'
    response.headers.pop('Content-Length', None)

    code, reason, _ = _httputil.valid_status(status)
    result = {'code': code, 'reason': reason, 'message': message}
    if errors is not None:
        result['errors'] = errors
    if hasattr(cherrypy.request, 'params'):
        params = cherrypy.request.params
        if 'debug' in params and params['debug']:
            result['traceback'] = traceback
    return json.dumps(result)
Example #11
0
def validate_since():
    response = cherrypy.serving.response
    lastmod = response.headers.get('Last-Modified')
    if lastmod:
        status, reason, msg = _httputil.valid_status(response.status)
        request = cherrypy.serving.request
        since = request.headers.get('If-Unmodified-Since')
        if since and since != lastmod:
            if status >= 200 and status <= 299 or status == 412:
                raise cherrypy.HTTPError(412)
        since = request.headers.get('If-Modified-Since')
        if since and since == lastmod:
            if status >= 200 and status <= 299 or status == 304:
                if request.method in ('GET', 'HEAD'):
                    raise cherrypy.HTTPRedirect([], 304)
                else:
                    raise cherrypy.HTTPError(412)
Example #12
0
def validate_etags(autotags = False, debug = False):
    response = cherrypy.serving.response
    if hasattr(response, 'ETag'):
        return
    status, reason, msg = _httputil.valid_status(response.status)
    etag = response.headers.get('ETag')
    if etag:
        if debug:
            cherrypy.log('ETag already set: %s' % etag, 'TOOLS.ETAGS')
    elif not autotags:
        if debug:
            cherrypy.log('Autotags off', 'TOOLS.ETAGS')
    elif status != 200:
        if debug:
            cherrypy.log('Status not 200', 'TOOLS.ETAGS')
    else:
        etag = response.collapse_body()
        etag = '"%s"' % md5(etag).hexdigest()
        if debug:
            cherrypy.log('Setting ETag: %s' % etag, 'TOOLS.ETAGS')
        response.headers['ETag'] = etag
    response.ETag = etag
    if debug:
        cherrypy.log('Status: %s' % status, 'TOOLS.ETAGS')
    if status >= 200 and status <= 299:
        request = cherrypy.serving.request
        conditions = request.headers.elements('If-Match') or []
        conditions = [ str(x) for x in conditions ]
        if debug:
            cherrypy.log('If-Match conditions: %s' % repr(conditions), 'TOOLS.ETAGS')
        if conditions and not (conditions == ['*'] or etag in conditions):
            raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did not match %r' % (etag, conditions))
        conditions = request.headers.elements('If-None-Match') or []
        conditions = [ str(x) for x in conditions ]
        if debug:
            cherrypy.log('If-None-Match conditions: %s' % repr(conditions), 'TOOLS.ETAGS')
        if conditions == ['*'] or etag in conditions:
            if debug:
                cherrypy.log('request.method: %s' % request.method, 'TOOLS.ETAGS')
            if request.method in ('GET', 'HEAD'):
                raise cherrypy.HTTPRedirect([], 304)
            else:
                raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r matched %r' % (etag, conditions))
Example #13
0
def get_error_page(status, **kwargs):
    import cherrypy
    try:
        code, reason, message = _httputil.valid_status(status)
    except ValueError as x:
        raise cherrypy.HTTPError(500, x.args[0])

    if kwargs.get('status') is None:
        kwargs['status'] = '%s %s' % (code, reason)
    if kwargs.get('message') is None:
        kwargs['message'] = message
    if kwargs.get('traceback') is None:
        kwargs['traceback'] = ''
    if kwargs.get('version') is None:
        kwargs['version'] = cherrypy.__version__
    for k, v in iteritems(kwargs):
        if v is None:
            kwargs[k] = ''
        else:
            kwargs[k] = _escape(kwargs[k])

    pages = cherrypy.serving.request.error_page
    error_page = pages.get(code) or pages.get('default')
    if error_page:
        try:
            if hasattr(error_page, '__call__'):
                return error_page(**kwargs)
            return open(error_page, 'rb').read() % kwargs
        except:
            e = _format_exception(*_exc_info())[-1]
            m = kwargs['message']
            if m:
                m += '<br />'
            m += 'In addition, the custom error page failed:\n<br />%s' % e
            kwargs['message'] = m

    return _HTTPErrorTemplate % kwargs
Example #14
0
def get_error_page(status, errors=None, **kwargs):
    """Return an HTML page, containing a pretty error response.

    status should be an int or a str.
    kwargs will be interpolated into the page template.
    """
    try:
        code, reason, message = _httputil.valid_status(status)
    except ValueError:
        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])

    # We can't use setdefault here, because some
    # callers send None for kwarg values.
    if kwargs.get('status') is None:
        kwargs['status'] = '%s %s' % (code, reason)
    if kwargs.get('message') is None:
        kwargs['message'] = message
    if kwargs.get('traceback') is None:
        kwargs['traceback'] = ''
    if kwargs.get('version') is None:
        kwargs['version'] = cherrypy.__version__

    for k, v in kwargs.items():
        if v is None:
            kwargs[k] = ''
        else:
            kwargs[k] = _escape(kwargs[k])

    # Use a custom template or callable for the error page?
    pages = cherrypy.serving.request.error_page
    error_page = pages.get(code) or pages.get('default')

    # Default template, can be overridden below.
    template = _HTTPErrorTemplate
    if error_page:
        try:
            if hasattr(error_page, '__call__'):
                # The caller function may be setting headers manually,
                # so we delegate to it completely. We may be returning
                # an iterator as well as a string here.
                #
                # We *must* make sure any content is not unicode.
                result = error_page(errors=errors, **kwargs)
                if cherrypy.lib.is_iterator(result):
                    return UTF8StreamEncoder(result)
                elif isinstance(result, str):  # str is OK for Python3
                    return result.encode('utf-8')
                else:
                    if not isinstance(result, bytes):
                        raise ValueError(
                            'error page function did not '
                            'return a bytestring, unicodestring or an '
                            'iterator - returned object of type {}.'
                            .format(type(result).__name__))
                    return result
            else:
                # Load the template from this path.
                template = tonative(open(error_page, 'rb').read())
        except:
            e = _format_exception(*_exc_info())[-1]
            m = kwargs['message']
            if m:
                m += '<br />'
            m += 'In addition, the custom error page failed:\n<br />%s' % e
            kwargs['message'] = m

    response = cherrypy.serving.response
    response.headers['Content-Type'] = 'text/html;charset=utf-8'
    result = template % kwargs
    return result.encode('utf-8')
Example #15
0
def test_valid_status(status, expected_status):
    """Check valid int, string and http_client-constants
    statuses processing."""
    assert httputil.valid_status(status) == expected_status
Example #16
0
 def __init__(self, status=500, message=None):
     self.status = status
     try:
         self.code, self.reason, defaultmsg = _httputil.valid_status(status)
     except ValueError, x:
         raise self.__class__(500, x.args[0])
Example #17
0
 def finalize(self):
     """Transform headers (and cookies) into self.header_list. (Core)"""
     try:
         code, reason, _ = httputil.valid_status(self.status)
     except ValueError, x:
         raise cherrypy.HTTPError(500, x.args[0])
Example #18
0
def get_error_page(status, **kwargs):
    """Return an HTML page, containing a pretty error response.

    status should be an int or a str.
    kwargs will be interpolated into the page template.
    """
    try:
        code, reason, message = _httputil.valid_status(status)
    except ValueError:
        raise cherrypy.HTTPError(500, _exc_info()[1].args[0])

    # We can't use setdefault here, because some
    # callers send None for kwarg values.
    if kwargs.get('status') is None:
        kwargs['status'] = '%s %s' % (code, reason)
    if kwargs.get('message') is None:
        kwargs['message'] = message
    if kwargs.get('traceback') is None:
        kwargs['traceback'] = ''
    if kwargs.get('version') is None:
        kwargs['version'] = cherrypy.__version__

    for k, v in six.iteritems(kwargs):
        if v is None:
            kwargs[k] = ''
        else:
            kwargs[k] = escape_html(kwargs[k])

    # Use a custom template or callable for the error page?
    pages = cherrypy.serving.request.error_page
    error_page = pages.get(code) or pages.get('default')

    # Default template, can be overridden below.
    template = _HTTPErrorTemplate
    if error_page:
        try:
            if hasattr(error_page, '__call__'):
                # The caller function may be setting headers manually,
                # so we delegate to it completely. We may be returning
                # an iterator as well as a string here.
                #
                # We *must* make sure any content is not unicode.
                result = error_page(**kwargs)
                if cherrypy.lib.is_iterator(result):
                    from cherrypy.lib.encoding import UTF8StreamEncoder
                    return UTF8StreamEncoder(result)
                elif isinstance(result, six.text_type):
                    return result.encode('utf-8')
                else:
                    if not isinstance(result, bytes):
                        raise ValueError(
                            'error page function did not '
                            'return a bytestring, six.text_type or an '
                            'iterator - returned object of type %s.' %
                            (type(result).__name__))
                    return result
            else:
                # Load the template from this path.
                template = io.open(error_page, newline='').read()
        except Exception:
            e = _format_exception(*_exc_info())[-1]
            m = kwargs['message']
            if m:
                m += '<br />'
            m += 'In addition, the custom error page failed:\n<br />%s' % e
            kwargs['message'] = m

    response = cherrypy.serving.response
    response.headers['Content-Type'] = 'text/html;charset=utf-8'
    result = template % kwargs
    return result.encode('utf-8')
Example #19
0
 def __init__(self, status=500, message=None):
     self.status = status
     try:
         self.code, self.reason, defaultmsg = _httputil.valid_status(status)
     except ValueError, x:
         raise self.__class__(500, x.args[0])
Example #20
0
def test_invalid_status(status_code, error_msg):
    """Check that invalid status cause certain errors."""
    with pytest.raises(ValueError) as excinfo:
        httputil.valid_status(status_code)

    assert error_msg in str(excinfo)
Example #21
0
def validate_etags(autotags=False, debug=False):
    """Validate the current ETag against If-Match, If-None-Match headers.

    If autotags is True, an ETag response-header value will be provided
    from an MD5 hash of the response body (unless some other code has
    already provided an ETag header). If False (the default), the ETag
    will not be automatic.

    WARNING: the autotags feature is not designed for URL's which allow
    methods other than GET. For example, if a POST to the same URL returns
    no content, the automatic ETag will be incorrect, breaking a fundamental
    use for entity tags in a possibly destructive fashion. Likewise, if you
    raise 304 Not Modified, the response body will be empty, the ETag hash
    will be incorrect, and your application will break.
    See :rfc:`2616` Section 14.24.
    """
    response = cherrypy.serving.response

    # Guard against being run twice.
    if hasattr(response, 'ETag'):
        return

    status, reason, msg = _httputil.valid_status(response.status)

    etag = response.headers.get('ETag')

    # Automatic ETag generation. See warning in docstring.
    if etag:
        if debug:
            cherrypy.log('ETag already set: %s' % etag, 'TOOLS.ETAGS')
    elif not autotags:
        if debug:
            cherrypy.log('Autotags off', 'TOOLS.ETAGS')
    elif status != 200:
        if debug:
            cherrypy.log('Status not 200', 'TOOLS.ETAGS')
    else:
        etag = response.collapse_body()
        etag = '"%s"' % md5(etag).hexdigest()
        if debug:
            cherrypy.log('Setting ETag: %s' % etag, 'TOOLS.ETAGS')
        response.headers['ETag'] = etag

    response.ETag = etag

    # "If the request would, without the If-Match header field, result in
    # anything other than a 2xx or 412 status, then the If-Match header
    # MUST be ignored."
    if debug:
        cherrypy.log('Status: %s' % status, 'TOOLS.ETAGS')
    if status >= 200 and status <= 299:
        request = cherrypy.serving.request

        conditions = request.headers.elements('If-Match') or []
        conditions = [str(x) for x in conditions]
        if debug:
            cherrypy.log('If-Match conditions: %s' % repr(conditions),
                         'TOOLS.ETAGS')
        if conditions and not (conditions == ['*'] or etag in conditions):
            raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did '
                                     'not match %r' % (etag, conditions))

        conditions = request.headers.elements('If-None-Match') or []
        conditions = [str(x) for x in conditions]
        if debug:
            cherrypy.log('If-None-Match conditions: %s' % repr(conditions),
                         'TOOLS.ETAGS')
        if conditions == ['*'] or etag in conditions:
            if debug:
                cherrypy.log('request.method: %s' %
                             request.method, 'TOOLS.ETAGS')
            if request.method in ('GET', 'HEAD'):
                raise cherrypy.HTTPRedirect([], 304)
            else:
                raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r '
                                         'matched %r' % (etag, conditions))
Example #22
0
def test_invalid_status(status_code, error_msg):
    """Check that invalid status cause certain errors."""
    with pytest.raises(ValueError, match=error_msg):
        httputil.valid_status(status_code)
Example #23
0
def test_valid_status(status, expected_status):
    """Check valid int, string and http.client-constants
    statuses processing."""
    assert httputil.valid_status(status) == expected_status
Example #24
0
def test_invalid_status(status_code, error_msg):
    """Check that invalid status cause certain errors."""
    with pytest.raises(ValueError) as excinfo:
        httputil.valid_status(status_code)

    assert error_msg in str(excinfo)
Example #25
0
 def finalize(self):
     """Transform headers (and cookies) into self.header_list. (Core)"""
     try:
         code, reason, _ = httputil.valid_status(self.status)
     except ValueError, x:
         raise cherrypy.HTTPError(500, x.args[0])
Example #26
0
def validate_etags(autotags=False, debug=False):
    """Validate the current ETag against If-Match, If-None-Match headers.

    If autotags is True, an ETag response-header value will be provided
    from an MD5 hash of the response body (unless some other code has
    already provided an ETag header). If False (the default), the ETag
    will not be automatic.

    WARNING: the autotags feature is not designed for URL's which allow
    methods other than GET. For example, if a POST to the same URL returns
    no content, the automatic ETag will be incorrect, breaking a fundamental
    use for entity tags in a possibly destructive fashion. Likewise, if you
    raise 304 Not Modified, the response body will be empty, the ETag hash
    will be incorrect, and your application will break.
    See :rfc:`2616` Section 14.24.
    """
    response = cherrypy.serving.response

    # Guard against being run twice.
    if hasattr(response, 'ETag'):
        return

    status, reason, msg = _httputil.valid_status(response.status)

    etag = response.headers.get('ETag')

    # Automatic ETag generation. See warning in docstring.
    if etag:
        if debug:
            cherrypy.log('ETag already set: %s' % etag, 'TOOLS.ETAGS')
    elif not autotags:
        if debug:
            cherrypy.log('Autotags off', 'TOOLS.ETAGS')
    elif status != 200:
        if debug:
            cherrypy.log('Status not 200', 'TOOLS.ETAGS')
    else:
        etag = response.collapse_body()
        etag = '"%s"' % md5(etag).hexdigest()
        if debug:
            cherrypy.log('Setting ETag: %s' % etag, 'TOOLS.ETAGS')
        response.headers['ETag'] = etag

    response.ETag = etag

    # "If the request would, without the If-Match header field, result in
    # anything other than a 2xx or 412 status, then the If-Match header
    # MUST be ignored."
    if debug:
        cherrypy.log('Status: %s' % status, 'TOOLS.ETAGS')
    if status >= 200 and status <= 299:
        request = cherrypy.serving.request

        conditions = request.headers.elements('If-Match') or []
        conditions = [str(x) for x in conditions]
        if debug:
            cherrypy.log('If-Match conditions: %s' % repr(conditions),
                         'TOOLS.ETAGS')
        if conditions and not (conditions == ['*'] or etag in conditions):
            raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did '
                                     'not match %r' % (etag, conditions))

        conditions = request.headers.elements('If-None-Match') or []
        conditions = [str(x) for x in conditions]
        if debug:
            cherrypy.log('If-None-Match conditions: %s' % repr(conditions),
                         'TOOLS.ETAGS')
        if conditions == ['*'] or etag in conditions:
            if debug:
                cherrypy.log('request.method: %s' %
                             request.method, 'TOOLS.ETAGS')
            if request.method in ('GET', 'HEAD'):
                raise cherrypy.HTTPRedirect([], 304)
            else:
                raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r '
                                         'matched %r' % (etag, conditions))