An Apache directory index that renders symlinks as symlinks.
dirlister.py generates a directory listing for a filesystem subtree,
rendering symbolic links explicitly as name -> target, distinguishing
dir / file / broken / outside targets, and refusing to follow links whose
target escapes the served root.
Pure stdlib, no third-party dependencies.
- Shows each symlink as
name -> target, with the target colour-coded by kind (directory, file, broken, or pointing outside the served root). - Both the link name and its target are navigable when the target resolves inside the served root; escaping or broken targets are shown but not linked.
- Symlinks may climb with
..as long as they resolve back inside the document root; anything that escapes is labelledoutsideand not followed. - VersionSort + case-insensitive ordering (so
file-2sorts beforefile-10). - Per-type icons ported from
mod_autoindexAddIcon, served from Apache's standard/icons/alias. - Hides dotfiles and other ignored names (mirrors
IndexIgnore).
Set via environment (Apache SetEnv) or by editing the constants at the top
of the script:
| Variable | Default | Purpose |
|---|---|---|
DIRLISTER_ROOT |
auto | Filesystem root this lister may expose. |
DIRLISTER_URL_PREFIX |
auto | URL prefix that maps to DIRLISTER_ROOT. |
DIRLISTER_ICON_BASE |
/icons/ |
URL prefix for icon images (set "" to disable). |
DIRLISTER_DEBUG |
unset | Set to "1" to emit request/config diagnostics. |
When neither DIRLISTER_ROOT nor DIRLISTER_URL_PREFIX is set, the pair is
detected per request from Apache's CONTEXT_DOCUMENT_ROOT / CONTEXT_PREFIX,
which name whichever Alias the requested URL fell under. A path served
straight out of the document root reports the document root and an empty
prefix, so the default behaviour is unchanged — but a directory reached
through an Alias also resolves correctly, with no configuration. Set the
variables explicitly only to serve a subtree narrower than the Alias target.
ALLOW_ESCAPING_LINKS (a constant in the script, default False) controls
whether links whose target lies outside the served root are made followable
or merely labelled.
Deploy as a CGI script under mod_cgid / mod_cgi — the __main__ block
bridges the WSGI app to CGI via wsgiref — or import the application
callable directly under mod_wsgi.
Run the lister as a CGI script and wire it in as the DirectoryIndex, so it
replaces mod_autoindex for any directory without an index file:
# These belong at server scope (load once).
LoadModule cgid_module modules/mod_cgid.so
<VirtualHost *:80>
ServerName files.example.com
DocumentRoot /srv/www
# Make the lister runnable as CGI at a stable URL.
ScriptAlias /dirlister.cgi /usr/local/lib/dirlister/dirlister.py
# Confine the lister to the document root.
SetEnv DIRLISTER_ROOT /srv/www
<Directory /srv/www>
# Replace the built-in autoindex; FollowSymLinks lets Apache serve
# the symlinked content the lister links to.
Options -Indexes +FollowSymLinks
# A directory request with no index file is internally redirected to
# the lister; the original directory path arrives in REDIRECT_URL.
DirectoryIndex /dirlister.cgi
Require all granted
</Directory>
</VirtualHost>An Alias maps a URL prefix onto a directory that need not sit under the
document root, so the URL path cannot be appended to DocumentRoot to find
the directory. The lister reads the alias from CONTEXT_DOCUMENT_ROOT /
CONTEXT_PREFIX, so this needs no extra configuration:
<VirtualHost *:443>
ServerName files.example.com
DocumentRoot /var/www/htdocs
ScriptAlias /dirlister.cgi /usr/local/lib/dirlister/dirlister.py
# /lts/dists/ is served from a directory named lts
Alias /lts/dists /var/www/htdocs/lts
<Directory /var/www/htdocs/lts>
Options -Indexes +FollowSymLinks
DirectoryIndex /dirlister.cgi
Require all granted
</Directory>
</VirtualHost>Note that a DirectoryIndex request reaches the lister through an internal
redirect, which re-runs the configuration against /dirlister.cgi. Two
consequences are worth knowing:
- The unprefixed
CONTEXT_*variables then describe the lister's ownScriptAlias, not the listed directory. The originals survive asREDIRECT_CONTEXT_*, which is what the lister reads when it seesREDIRECT_URL. SetEnv DIRLISTER_ROOTplaced inside a<Directory>or<Location>block likewise arrives asREDIRECT_DIRLISTER_ROOT. The lister accepts either spelling, so per-directorySetEnvworks — but only a vhost-scopeSetEnvarrives unprefixed, and it applies to every path in the vhost.
Set DIRLISTER_DEBUG to 1 to dump the request variables actually received
and see which ones carry the alias, rather than guessing.
Point WSGIScriptAlias at the file and mod_wsgi imports application
directly. Mount it on the prefix you want listed (it only handles directory
requests; let Apache serve files normally):
LoadModule wsgi_module modules/mod_wsgi.so
<VirtualHost *:80>
ServerName files.example.com
DocumentRoot /srv/www
SetEnv DIRLISTER_ROOT /srv/www
WSGIScriptAlias /browse /usr/local/lib/dirlister/dirlister.py
<Directory /usr/local/lib/dirlister>
Require all granted
</Directory>
</VirtualHost>The serving root is resolved per request, so it works correctly under both
CGI (which sets DOCUMENT_ROOT as a real env var) and mod_wsgi (which
exposes SetEnv values and DOCUMENT_ROOT in the WSGI environ instead).
- The serving root is resolved per request, so a single
mod_wsgiworker never pins every request to a stale root. - Every requested path and every symlink target is run through
os.path.realpathand checked for containment within the served root, so..traversal and symlink escapes are refused. DIRLISTER_DEBUGis gated on the exact string"1", so the natural "off" values ("0","false") do not accidentally enable the diagnostics dump (which leaks paths and the environment — never enable it in production).- Malformed or unreadable paths return
400/403/404rather than surfacing a500/ traceback. - Symlink targets are only made navigable when they resolve inside the root.
See LICENSE.