diff --git a/tools/sky_server b/tools/sky_server index 5308b2de9ee4d81e26ee5f1c7a0b8f26da024342..bd40138855effbc00a1b9b2a17965075cc6803fb 100755 --- a/tools/sky_server +++ b/tools/sky_server @@ -6,6 +6,7 @@ import argparse import os import cherrypy +import staticdirindex BUILD_DIRECTORY = 'out' @@ -16,6 +17,25 @@ SKY_ROOT = os.path.join(SRC_ROOT, 'sky') GEN_ROOT = os.path.join(SRC_ROOT, BUILD_DIRECTORY, CONFIG_DIRECTORY, 'gen') +# FIXME: This should be replaced by just json and inflated into DOM client-side. +def skydir(section="", dir="", path="", **kwargs): + url = "%s%s" % (cherrypy.request.headers.get('Host', ''), + cherrypy.request.path_info) + sky = "" + sky += "" + sky += '
Listing for: ' + url +'
' + for _, dir_names, file_names in os.walk(path.rstrip(r"\/")): + for dir_name in sorted(dir_names): + sky += "%s/\n" % (dir_name, dir_name) + + del dir_names[:] # limit to one level + + for file_name in sorted(file_names): + sky += "%s\n" % (file_name, file_name) + return sky + "
" + + # FIXME: This doesn't yet support directory listings. We'll do something like: # http://tools.cherrypy.org/wiki/staticdirindex # but have it spit .sky instead of HTML @@ -43,6 +63,7 @@ def main(): '/': { 'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.abspath(args.app_path), + 'tools.staticdir.indexlister': skydir, }, '/mojo': { 'tools.staticdir.on': True, diff --git a/tools/staticdirindex.py b/tools/staticdirindex.py new file mode 100644 index 0000000000000000000000000000000000000000..35eded7fbf10e59d23f0c8f800c90cb9c9df528a --- /dev/null +++ b/tools/staticdirindex.py @@ -0,0 +1,120 @@ +# From http://tools.cherrypy.org/wiki/staticdirindex +# CherryPy code is covered under a BSD License: +# https://bitbucket.org/cherrypy/cherrypy/src/697c7af588b8/cherrypy/LICENSE.txt + +import os +import re +import stat +import urllib +import cherrypy +from cherrypy.lib import cptools, http + +# Undercover kludge to wrap staticdir +from cherrypy.lib.static import staticdir + +def staticdirindex(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 )