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))
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)
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])
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)
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)
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)
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
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))
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)
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)
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))
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
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')
def test_valid_status(status, expected_status): """Check valid int, string and http_client-constants statuses processing.""" assert httputil.valid_status(status) == expected_status
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])
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])
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')
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)
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))
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)
def test_valid_status(status, expected_status): """Check valid int, string and http.client-constants statuses processing.""" assert httputil.valid_status(status) == expected_status