Skip to content

Instantly share code, notes, and snippets.

@wilhelmy
Last active June 5, 2023 10:44
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save wilhelmy/5a59b8eea26974a468c9 to your computer and use it in GitHub Desktop.
Save wilhelmy/5a59b8eea26974a468c9 to your computer and use it in GitHub Desktop.
make nginx dirlistings look like lighttpd's through the magic of xslt-transforming xml dirlistings. I don't even.
<?xml version="1.0"?>
<!--
dirlist.xslt - transform nginx's into lighttpd look-alike dirlistings
I'm currently switching over completely from lighttpd to nginx. If you come
up with a prettier stylesheet or other improvements, please tell me :)
-->
<!--
Copyright (c) 2016 by Moritz Wilhelmy <mw@barfooze.de>
All rights reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted providing that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE fnord [<!ENTITY nbsp "&#160;">]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" xmlns:func="http://exslt.org/functions" xmlns:str="http://exslt.org/strings" version="1.0" exclude-result-prefixes="xhtml" extension-element-prefixes="func str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.1//EN" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" indent="no" media-type="application/xhtml+xml"/>
<xsl:strip-space elements="*" />
<xsl:template name="size">
<!-- transform a size in bytes into a human readable representation -->
<xsl:param name="bytes"/>
<xsl:choose>
<xsl:when test="$bytes &lt; 1000"><xsl:value-of select="$bytes" />B</xsl:when>
<xsl:when test="$bytes &lt; 1048576"><xsl:value-of select="format-number($bytes div 1024, '0.0')" />K</xsl:when>
<xsl:when test="$bytes &lt; 1073741824"><xsl:value-of select="format-number($bytes div 1048576, '0.0')" />M</xsl:when>
<xsl:otherwise><xsl:value-of select="format-number(($bytes div 1073741824), '0.00')" />G</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="timestamp">
<!-- transform an ISO 8601 timestamp into a human readable representation -->
<xsl:param name="iso-timestamp" />
<xsl:value-of select="concat(substring($iso-timestamp, 0, 11), ' ', substring($iso-timestamp, 12, 5))" />
</xsl:template>
<xsl:template match="directory">
<tr>
<td class="n"><a href="{str:encode-uri(current(),true())}/"><xsl:value-of select="."/></a>/</td>
<td class="m"><xsl:call-template name="timestamp"><xsl:with-param name="iso-timestamp" select="@mtime" /></xsl:call-template></td>
<td class="s">- &nbsp;</td>
<td class="t">Directory</td>
</tr>
</xsl:template>
<xsl:template match="file">
<tr>
<td class="n"><a href="{str:encode-uri(current(),true())}"><xsl:value-of select="." /></a></td>
<td class="m"><xsl:call-template name="timestamp"><xsl:with-param name="iso-timestamp" select="@mtime" /></xsl:call-template></td>
<td class="s"><xsl:call-template name="size"><xsl:with-param name="bytes" select="@size" /></xsl:call-template></td>
<td class="t">File</td>
</tr>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<style type="text/css">a, a:active {text-decoration: none; color: blue;}
a:visited {color: #48468F;}
a:hover, a:focus {text-decoration: underline; color: red;}
body {background-color: #F5F5F5;}
h2 {margin-bottom: 12px;}
table {margin-left: 12px;}
th, td { font: 90% monospace; text-align: left;}
th { font-weight: bold; padding-right: 14px; padding-bottom: 3px;}
td {padding-right: 14px;}
td.s, th.s {text-align: right;}
div.list { background-color: white; border-top: 1px solid #646464; border-bottom: 1px solid #646464; padding-top: 10px; padding-bottom: 14px;}
div.foot { font: 90% monospace; color: #787878; padding-top: 4px;}</style>
<title>Index of <xsl:value-of select="$path"/></title>
</head>
<body>
<h2>Index of <xsl:value-of select="$path"/></h2>
<div class="list">
<table summary="Directory Listing" cellpadding="0" cellspacing="0">
<thead>
<tr><th class="n">Name</th><th class="m">Last Modified</th><th class="s">Size</th><th class="t">Type</th></tr>
</thead>
<!-- uncomment the following block to enable totals -->
<!--
<tfoot>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td colspan="4">
<xsl:value-of select="count(//directory)"/> directories,
<xsl:value-of select="count(//file)"/> files,
<xsl:call-template name="size"><xsl:with-param name="bytes" select="sum(//file/@size)" /></xsl:call-template> total
</td>
</tr>
</tfoot>
-->
<tbody>
<tr><td class="n"><a href="../">Parent Directory</a>/</td><td class="m">&nbsp;</td><td class="s">- &nbsp;</td><td class="t">Directory</td></tr>
<xsl:apply-templates />
</tbody>
</table>
</div>
<div class="foot">nginx</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
server {
...
root /opt/www;
location / {
try_files $uri @autoindex;
}
location @autoindex {
autoindex on;
autoindex_format xml;
xslt_string_param path $uri;
xslt_stylesheet xslt/dirlist.xslt;
}
}
@wilhelmy
Copy link
Author

By the way, I intentionally kept the lighttpd directory index page layout, which means you can re-use any CSS you might have written for lighttpd. Just copy-paste it into the CSS section of the XSLT file.

And in case you want to summarize how many files, directories and bytes you have, you can use something like this somewhere in the page template:

<xsl:value-of select="count(//directory)"/> directories,
<xsl:value-of select="count(//file)"/> files,
<xsl:value-of select="sum(//file/@size)"/> Bytes total

Edit: I've just inserted that too, just uncomment the corresponding block if you want to enable it. It's disabled by default because it doesn't really add much value and lighttpd doesn't have it.

@Ralith
Copy link

Ralith commented Feb 6, 2017

Using single quotes in the nginx config--i.e. path="$uri"--allows this to work on paths that contain apostrophes.

@Phernost
Copy link

Phernost commented Aug 21, 2018

Changing the quote style still doesn't fix the problem for URIs with qoutes in them. In fact colons will also break it, since the inline parameter parser will think there are multiple parameters. This is a known bug in the parser for inline parameters. Using xslt_string_param instead of an inline parameter is the only way to correctly handle dynamic variables.

xslt_string_param path $uri;
xslt_stylesheet xslt/dirlist.xslt;

@wilhelmy
Copy link
Author

Thanks, fixed?

@sebschrader
Copy link

The stylesheet does not escape file names properly, which leads to broken links if file names contain e.g. a colon. This change fixes it for me:

--- a/dirlist.xslt        2019-01-07 16:17:27.422834078 +0000
+++ b/dirlist.xslt        2019-01-07 16:19:00.149016433 +0000
@@ -32,7 +32,7 @@
    POSSIBILITY OF SUCH DAMAGE.
 -->
 <!DOCTYPE fnord [<!ENTITY nbsp "&#160;">]>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" xmlns:func="http://exslt.org/functions" version="1.0" exclude-result-prefixes="xhtml" extension-element-prefixes="func">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" xmlns:func="http://exslt.org/functions" xmlns:str="http://exslt.org/strings" version="1.0" exclude-result-prefixes="xhtml" extension-element-prefixes="func str">
   <xsl:output method="xml" version="1.0" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.1//EN" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" indent="no" media-type="application/xhtml+xml"/>
   <xsl:strip-space elements="*" />

@@ -56,7 +56,7 @@

   <xsl:template match="directory">
     <tr>
-      <td class="n"><a href="{current()}/"><xsl:value-of select="."/></a>/</td>
+      <td class="n"><a href="{str:encode-uri(current(),true())}/"><xsl:value-of select="."/></a>/</td>
       <td class="m"><xsl:call-template name="timestamp"><xsl:with-param name="iso-timestamp" select="@mtime" /></xsl:call-template></td>
       <td class="s">- &nbsp;</td>
       <td class="t">Directory</td>
@@ -65,7 +65,7 @@

   <xsl:template match="file">
     <tr>
-      <td class="n"><a href="{current()}"><xsl:value-of select="." /></a></td>
+      <td class="n"><a href="{str:encode-uri(current(),true())}"><xsl:value-of select="." /></a></td>
       <td class="m"><xsl:call-template name="timestamp"><xsl:with-param name="iso-timestamp" select="@mtime" /></xsl:call-template></td>
       <td class="s"><xsl:call-template name="size"><xsl:with-param name="bytes" select="@size" /></xsl:call-template></td>
       <td class="t">File</td>

@wilhelmy
Copy link
Author

Thanks, merged.

@bsmouse
Copy link

bsmouse commented Sep 1, 2020

How do display the localtime?

@wilhelmy
Copy link
Author

wilhelmy commented Sep 1, 2020

No idea, sorry. Sounds complicated if the time isn't already part of the input, since XSLT is supposedly purely functional.

@abdus
Copy link

abdus commented May 16, 2021

Thanks for writing this template, @wilhelmy!
I enhanced the style and functionalities a bit. It looks something like this:
1
in case anyone's interested, here's the repo

@wilhelmy
Copy link
Author

@abdus Awesome, thanks :)
I'm relatively CSS-illiterate so I was secretly hoping for someone to help me out for years.

@abdus
Copy link

abdus commented May 21, 2021

@wilhelmy, you are welcome!
Your XSL template saved my time. It's a win-win for both of us, I guess :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment