# from http://tools.cherrypy.org/wiki/staticdirindex
import os
import re
import stat
import urllib
import cherrypy
from cherrypy.lib import cptools, httputil as http

# Undercover kludge to wrap staticdir
from cherrypy.lib.static import staticdir


def staticdirindex(  # noqa: C901
    section,
    dir,
    root="",
    match="",
    content_types=None,
    index="",
    indexlistermatch="",
    indexlister=None,
    **kwargs
):
    """Serve a directory index listing for a dir.

    Compatibility alert: staticdirindex is built on and is dependent on
    staticdir and its configurations.  staticdirindex only works effectively
    in locations where staticdir is also configured.  staticdirindex is
    coded to allow easy integration with staticdir, if demand warrants.

    indexlister must be configured, or no function is performed.
    indexlister should be a callable that accepts the following parameters:
        section: same as for staticdir (and implicitly calculated for it)
        dir: same as for staticdir, but already combined with root
        path: combination of section and dir

        Other parameters that are configured for staticdirindex will be passed
        on to indexlister.

    Should use priorty > than that of staticdir, so that only directories not
    served by staticdir, call staticdirindex.
    """
    # first call old staticdir, and see if it does anything
    sdret = staticdir(section, dir, root, match, content_types, index)
    if sdret:
        return True

    # if not, then see if we are configured to do anything
    if indexlister is None:
        return False

    req = cherrypy.request
    response = cherrypy.response

    match = indexlistermatch

    # N.B. filename ending in a slash or not does not imply a directory
    # the following block of code directly copied from static.py staticdir
    if match and not re.search(match, cherrypy.request.path_info):
        return False

    # Allow the use of '~' to refer to a user's home directory.
    dir = os.path.expanduser(dir)

    # If dir is relative, make absolute using "root".
    if not os.path.isabs(dir):
        if not root:
            msg = "Static dir requires an absolute dir (or root)."
            raise ValueError(msg)
        dir = os.path.join(root, dir)

    # Determine where we are in the object tree relative to 'section'
    # (where the static tool was defined).
    if section == 'global':
        section = "/"
    section = section.rstrip(r"\/")
    branch = cherrypy.request.path_info[len(section) + 1 :]
    branch = urllib.unquote(branch.lstrip(r"\/"))

    # If branch is "", filename will end in a slash
    filename = os.path.join(dir, branch)

    # There's a chance that the branch pulled from the URL might
    # have ".." or similar uplevel attacks in it. Check that the final
    # filename is a child of dir.
    if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
        raise cherrypy.HTTPError(403)  # Forbidden
    # the above block of code directly copied from static.py staticdir
    # N.B. filename ending in a slash or not does not imply a directory

    # Check if path is a directory.

    path = filename
    # The following block of code copied from static.py serve_file

    # If path is relative, users should fix it by making path absolute.
    # That is, CherryPy should not guess where the application root is.
    # It certainly should *not* use cwd (since CP may be invoked from a
    # variety of paths). If using tools.static, you can make your relative
    # paths become absolute by supplying a value for "tools.static.root".
    if not os.path.isabs(path):
        raise ValueError("'%s' is not an absolute path." % path)

    try:
        st = os.stat(path)
    except OSError:
        # The above block of code copied from static.py serve_file

        return False

    if stat.S_ISDIR(st.st_mode):

        # Set the Last-Modified response header, so that
        # modified-since validation code can work.
        response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime)
        cptools.validate_since()
        response.body = indexlister(section=section, dir=dir, path=path, **kwargs)
        response.headers['Content-Type'] = 'text/html'
        req.is_index = True
        return True

    return False


# Replace the real staticdir with our version
cherrypy.tools.staticdir = cherrypy._cptools.HandlerTool(staticdirindex)
