Example #1
0
def _try_decode(subject, charsets):
    for charset in charsets[:-1]:
        try:
            return tonative(subject, charset)
        except ValueError:
            pass
    return tonative(subject, charsets[-1])
Example #2
0
def _try_decode_header(header, charset):
    global FALLBACK_CHARSET

    for enc in (charset, FALLBACK_CHARSET):
        try:
            return tonative(ntob(tonative(header, 'latin1'), 'latin1'), enc)
        except ValueError as ve:
            last_err = ve
    else:
        raise last_err
Example #3
0
def _try_decode_header(header, charset):
    global FALLBACK_CHARSET

    for enc in (charset, FALLBACK_CHARSET):
        try:
            return tonative(ntob(tonative(header, 'latin1'), 'latin1'), enc)
        except ValueError as ve:
            last_err = ve
    else:
        raise last_err
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:
        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] = tonative(_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 #5
0
    def __init__(self, urls, status=None, encoding=None):
        if isinstance(urls, text_or_bytes):
            urls = [urls]

        abs_urls = []
        for url in urls:
            url = tonative(url, encoding or self.encoding)

            # Note that urljoin will "do the right thing" whether url is:
            #  1. a complete URL with host (e.g. "http://www.example.com/test")
            #  2. a URL relative to root (e.g. "/dummy")
            #  3. a URL relative to the current path
            # Note that any query string in cherrypy.request is discarded.
            url = urllib.parse.urljoin(cherrypy.url(), url)
            abs_urls.append(url)
        self.urls = abs_urls

        status = (
            int(status)
            if status is not None
            else self.default_status
        )
        if not 300 <= status <= 399:
            raise ValueError('status must be between 300 and 399.')

        CherryPyException.__init__(self, abs_urls, status)
Example #6
0
    def __init__(self, urls, status=None, encoding=None):
        if isinstance(urls, text_or_bytes):
            urls = [urls]

        abs_urls = []
        for url in urls:
            url = tonative(url, encoding or self.encoding)

            # Note that urljoin will "do the right thing" whether url is:
            #  1. a complete URL with host (e.g. "http://www.example.com/test")
            #  2. a URL relative to root (e.g. "/dummy")
            #  3. a URL relative to the current path
            # Note that any query string in cherrypy.request is discarded.
            url = urllib.parse.urljoin(cherrypy.url(), url)
            abs_urls.append(url)
        self.urls = abs_urls

        status = (
            int(status)
            if status is not None
            else self.default_status
        )
        if not 300 <= status <= 399:
            raise ValueError('status must be between 300 and 399.')

        CherryPyException.__init__(self, abs_urls, status)
Example #7
0
    def __init__(self, urls, status=None, encoding=None):
        import cherrypy
        request = cherrypy.serving.request

        if isinstance(urls, basestring):
            urls = [urls]

        abs_urls = []
        for url in urls:
            url = tonative(url, encoding or self.encoding)

            # Note that urljoin will "do the right thing" whether url is:
            #  1. a complete URL with host (e.g. "http://www.example.com/test")
            #  2. a URL relative to root (e.g. "/dummy")
            #  3. a URL relative to the current path
            # Note that any query string in cherrypy.request is discarded.
            url = _urljoin(cherrypy.url(), url)
            abs_urls.append(url)
        self.urls = abs_urls

        # RFC 2616 indicates a 301 response code fits our goal; however,
        # browser support for 301 is quite messy. Do 302/303 instead. See
        # http://www.alanflavell.org.uk/www/post-redirect.html
        if status is None:
            if request.protocol >= (1, 1):
                status = 303
            else:
                status = 302
        else:
            status = int(status)
            if status < 300 or status > 399:
                raise ValueError("status must be between 300 and 399.")

        self.status = status
        CherryPyException.__init__(self, abs_urls, status)
Example #8
0
    def __init__(self, urls, status=None, encoding=None):
        request = cherrypy.serving.request

        if isinstance(urls, text_or_bytes):
            urls = [urls]

        abs_urls = []
        for url in urls:
            url = tonative(url, encoding or self.encoding)

            # Note that urljoin will "do the right thing" whether url is:
            #  1. a complete URL with host (e.g. "http://www.example.com/test")
            #  2. a URL relative to root (e.g. "/dummy")
            #  3. a URL relative to the current path
            # Note that any query string in cherrypy.request is discarded.
            url = urllib.parse.urljoin(cherrypy.url(), url)
            abs_urls.append(url)
        self.urls = abs_urls

        # RFC 2616 indicates a 301 response code fits our goal; however,
        # browser support for 301 is quite messy. Do 302/303 instead. See
        # http://www.alanflavell.org.uk/www/post-redirect.html
        if status is None:
            if request.protocol >= (1, 1):
                status = 303
            else:
                status = 302
        else:
            status = int(status)
            if status < 300 or status > 399:
                raise ValueError('status must be between 300 and 399.')

        self.status = status
        CherryPyException.__init__(self, abs_urls, status)
Example #9
0
    def test_unicode(self):
        url = ntou("/static/Слава Україні.html", 'utf-8')
        # quote function requires str
        url = tonative(url, 'utf-8')
        url = urllib.parse.quote(url)
        self.getPage(url)

        expected = ntou("Героям Слава!", 'utf-8')
        self.assertInBody(expected)
def _try_decode_map_values(param_map, charset):
    global FALLBACK_CHARSET

    for enc in (charset, FALLBACK_CHARSET):
        try:
            return {k: tonative(v, enc) for k, v in param_map.items()}
        except ValueError as ve:
            last_err = ve
    else:
        raise last_err
Example #11
0
    def test_unicode(self):
        ensure_unicode_filesystem()
        self.unicode_file()
        url = ntou('/static/Слава Україні.html', 'utf-8')
        # quote function requires str
        url = tonative(url, 'utf-8')
        url = urllib.parse.quote(url)
        self.getPage(url)

        expected = ntou('Героям Слава!', 'utf-8')
        self.assertInBody(expected)
Example #12
0
    def test_unicode(self):
        ensure_unicode_filesystem()
        with self.unicode_file():
            url = ntou('/static/Слава Україні.html', 'utf-8')
            # quote function requires str
            url = tonative(url, 'utf-8')
            url = urllib.parse.quote(url)
            self.getPage(url)

            expected = ntou('Героям Слава!', 'utf-8')
            self.assertInBody(expected)
def error_handler(status, message, traceback, version):
    _ = traceback, version
    log.warning("Engine error: %s: %s", status, message)
    response = cherrypy.serving.response
    response.status = 303
    response.headers["Content-Type"] = "text/html;charset=utf-8"
    response.headers["Location"] = urllib.parse.urljoin(
        cherrypy.url(), tonative(
            cherrypy.config["engine.settings"]["endpoints"]["access_denied"], "utf-8"
        )
    )
    data = "This resource can be found at <a href=%s>%s</a>." % (
        saxutils.quoteattr(cherrypy.response.headers["Location"]),
        html.escape(cherrypy.response.headers["Location"], quote=False)
    )
    response.headers.pop("Content-Length", None)
    return data
Example #14
0
    def __init__(self, urls, status=None, encoding=None):
        import cherrypy
        request = cherrypy.serving.request

        if isinstance(urls, text_or_bytes):
            urls = [urls]

        self.urls = [tonative(url, encoding or self.encoding) for url in urls]

        # RFC 2616 indicates a 301 response code fits our goal; however,
        # browser support for 301 is quite messy. Do 302/303 instead. See
        # http://www.alanflavell.org.uk/www/post-redirect.html
        if status is None:
            if request.protocol >= (1, 1):
                status = 303
            else:
                status = 302
        else:
            status = int(status)
            if status < 300 or status > 399:
                raise ValueError('status must be between 300 and 399.')

        self.status = status
        CherryPyException.__init__(self, self.urls, status)
Example #15
0
    def __init__(self, urls, status=None, encoding=None):
        import cherrypy
        request = cherrypy.serving.request

        if isinstance(urls, text_or_bytes):
            urls = [urls]

        self.urls = [tonative(url, encoding or self.encoding) for url in urls]

        # RFC 2616 indicates a 301 response code fits our goal; however,
        # browser support for 301 is quite messy. Do 302/303 instead. See
        # http://www.alanflavell.org.uk/www/post-redirect.html
        if status is None:
            if request.protocol >= (1, 1):
                status = 303
            else:
                status = 302
        else:
            status = int(status)
            if status < 300 or status > 399:
                raise ValueError('status must be between 300 and 399.')

        self.status = status
        CherryPyException.__init__(self, self.urls, status)
Example #16
0
 def upload(self):
     if not cherrypy.request.method == 'POST':
         raise AssertionError("'POST' != request.method %r" %
                              cherrypy.request.method)
     return "thanks for '%s'" % tonative(cherrypy.request.body.read())
Example #17
0
def _get_response_status(resp):
    try:
        response_status = resp.output_status
    except AttributeError:
        response_status = resp.status
    return tonative(response_status, 'utf-8')
Example #18
0
    def on_start_resource(self, **kwargs):
        """
        Checks if the current request requires authentication
        if it requires authentication it will look for the token
        if the token doesn't exist, it will raise a redirect
        error landing on the authentication page.
        """
        default_args = {
            "required": False,
            "login": False
        }
        kwargs.update(default_args)

        if kwargs['required'] is False and kwargs['login'] is False:
            # Authoirzation not required to access this url
            return

        token = None
        try:
            if 'Authorization' in cherrypy.request.headers:
                token = cherrypy.request.headers['Authorization']
            elif 'Authorization' in cherrypy.request.cookie:
                token = cherrypy.request.cookie['Authorization']
        except:
            logging.exception("Couldn't acquire a token")

        if token is not None and token.startswith("Bearer"):
            logging.debug("Attempting token authorisation")
            try:
                self.auth_mech.verifyToken(token)
            except AuthenticationFailure as e:
                raise cherrypy.HTTPRedirect(self.auth_url)
            except RenewToken as e:
                # Set the new token on the response headers
                cherrypy.response.headers['Authorization'] = 'Bearer ' + e.token.decode("utf-8")
                cherrypy.response.cookie['Authorization'] = 'Bearer ' + e.token.decode("utf-8")
            return


        # Get the username/password from URL (authorization basic)
        accept_charset='utf-8'
        fallback_charset = 'ISO-8859-1'

        if kwargs['login'] is True and token is not None and token.startswith("Basic"):
            # split() error, base64.decodestring() error
            msg = 'Bad Request'
            with cherrypy.HTTPError.handle((ValueError, binascii.Error), 400, msg):
                scheme, params = token.split(' ', 1)
                charsets = accept_charset, fallback_charset
                decoded_params = base64.b64decode(params.encode('ascii'))
                decoded_params = _try_decode(decoded_params, charsets)
                decoded_params = ntou(decoded_params)
                decoded_params = unicodedata.normalize('NFC', decoded_params)
                decoded_params = tonative(decoded_params)
                username, password = decoded_params.split(':', 1)

                if self.auth_mech.checkpass(username, password):
                    token = self.auth_mech.generateToken(username)
                    cherrypy.request.login = username
                    cherrypy.response.headers['Authorization'] = 'Bearer ' + token.decode("utf-8")
                    cherrypy.response.cookie['Authorization'] = 'Bearer ' + token.decode("utf-8")
                    return  # successful authentication


        if self.auth_url is not None:
            raise cherrypy.HTTPRedirect(self.auth_url)

        #
        # Check if the request was a JSON/API request or not
        #
        #
        if cherrypy.request.headers['Accept'] != 'application/json':
            # If we're a browser set the WWW-Authenticate header
            charset = accept_charset.upper()
            charset_declaration = (
                ('charset="%s"' % charset)
                if charset != fallback_charset
                else ''
            )
            cherrypy.response.headers['www-authenticate'] = ('Basic %s' % (charset))

        raise cherrypy.HTTPError(401, 'You are not authorized to access this resource')
Example #19
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 #20
0
 def upload(self):
     if not cherrypy.request.method == 'POST':
         raise AssertionError("'POST' != request.method %r" %
                              cherrypy.request.method)
     return "thanks for '%s'" % tonative(cherrypy.request.body.read())
Example #21
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 = tonative(open(error_page, 'rb').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 basic_auth(realm, checkpassword, debug=False, accept_charset='utf-8'):
    """A CherryPy tool which hooks at before_handler to perform
    HTTP Basic Access Authentication, as specified in :rfc:`2617`
    and :rfc:`7617`.

    If the request has an 'authorization' header with a 'Basic' scheme, this
    tool attempts to authenticate the credentials supplied in that header.  If
    the request has no 'authorization' header, or if it does but the scheme is
    not 'Basic', or if authentication fails, the tool sends a 401 response with
    a 'WWW-Authenticate' Basic header.

    realm
        A string containing the authentication realm.

    checkpassword
        A callable which checks the authentication credentials.
        Its signature is checkpassword(realm, username, password). where
        username and password are the values obtained from the request's
        'authorization' header.  If authentication succeeds, checkpassword
        returns True, else it returns False.

    """

    fallback_charset = 'ISO-8859-1'

    if '"' in realm:
        raise ValueError('Realm cannot contain the " (quote) character.')
    request = cherrypy.serving.request

    auth_header = request.headers.get('authorization')
    if auth_header is not None:
        # split() error, base64.decodestring() error
        msg = 'Bad Request'
        with cherrypy.HTTPError.handle((ValueError, binascii.Error), 400, msg):
            scheme, params = auth_header.split(' ', 1)
            if scheme.lower() == 'basic':
                decoded_params = base64_decode(params)
                decoded_params = ntob(decoded_params)

                last_err = None
                for charset in (accept_charset, fallback_charset):
                    try:
                        decoded_params = tonative(decoded_params, charset)
                        break
                    except ValueError as ve:
                        last_err = ve
                else:
                    raise last_err

                decoded_params = ntou(decoded_params)
                decoded_params = unicodedata.normalize('NFC', decoded_params)
                decoded_params = tonative(decoded_params)
                username, password = decoded_params.split(':', 1)
                if checkpassword(realm, username, password):
                    if debug:
                        cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
                    request.login = username
                    return  # successful authentication

    charset = accept_charset.upper()
    charset_declaration = ((', charset="%s"' %
                            charset) if charset != fallback_charset else '')
    # Respond with 401 status and a WWW-Authenticate header
    cherrypy.serving.response.headers['www-authenticate'] = (
        'Basic realm="%s"%s' % (realm, charset_declaration))
    raise cherrypy.HTTPError(401,
                             'You are not authorized to access that resource')