Styling Apache directory listings

In this post you will find how to make stylish listing generated by the Apache server. This is not just another CSS solution. The trick is in configuring Apache to generate XML directory list and to use browser (IE, FF, Opera, Safari …) to transform XML to the HTML. You will have full freedom to modify and style auto generated directory listings.

Contents
  1. Edit httpd.conf
  2. Create header.html file
  3. Set execute permission to the header.html
  4. Create stylesheet.xsl
  5. Conclusion

1. Edit httpd.conf
Open httpd.conf file and edit IndexOptions and HeaderName directive.

# create XHTML table 
IndexOptions XHTML HTMLtable
#  avoid duplicated <html><body> and no column sorting
IndexOptions SuppressHTMLPreamble SuppressColumnSorting
# place folders first
IndexOptions FoldersFirst
# add text/xml HTTP header
IndexOptions Type=text/xml  
# define absolute path to the header file
HeaderName /header.html

If you want to have current directory displayed as a page title, please add +Includes to the Options directive – this will enable SSI, but Apache will not parse files until you define SSI files. SSI files can be marked by extension like shtml or by execute bit. In this example, I marked SSI files with execution bit. Don’t forget to set execute permission to the SSI file or it will not be processed.

# parse SSI directives in files with the execute bit set
XBitHack on

Check if directory listing is enabled by Indexes option in Options directive.

# The Options directive is both complicated and important. Please see
# http://httpd.apache.org/docs/2.2/mod/core.html#options
# for more information.
Options Indexes ...

2. Create header.html file
header.html contains beginning of the XML listing. The rest of XML will be generated by Apache. XML has defined stylesheet and defined nbsp entity because Apache writes nbsp in emtpy table cells. Last line is a SSI directive, and if you need any other server variable, just wrap it within XML tag.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>
<?xml-stylesheet type="text/xsl" href="/stylesheet.xsl"?>
<html><body>
<request_uri><!--#echo var="REQUEST_URI" --></request_uri>  

3. Set execute permission to the header.html
It is important to set execute permission to the header.html because Apache will process header file and execute line with REQUEST_URI. If you don’t need “current directory” info or any other dynamics in header file, you can skip this step and omit +Includes / XBitHack in httpd.conf. On the other hand, if Apache “knows” PHP, and you aren’t satisfied with static header.html, you can write PHP header file and you should name it header.php

<? print '<?xml version="1.0" encoding="utf-8"?>' ?>
<? print '<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>' ?>
<? print '<?xml-stylesheet type="text/xsl" href="/stylesheet.xsl"?>' ?> 
<html><body>
<request_uri><?= $_SERVER['REQUEST_URI'] ?></request_uri> 

For PHP header file, you don’t need +Includes / XBitHack in httpd.conf because Apache has defined PHP interpreter to handle files with a php extension.

4. Create stylesheet.xsl
Here is a simple stylesheet.xsl to process dynamically generated XML. Please save stylesheet.xsl to the document root or change path in the header file. After directory request, Apache will respond with XML. Browser will start to process XML and make request for stylesheet.xsl file. With requested XML and stylesheet.xsl, browser will render HTML page. If you click on View page source, you will not see finished HTML, but XML generated by Apache.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"
            doctype-public="-//W3C//DTD HTML 4.01//EN"
            doctype-system="http://www.w3.org/TR/html4/strict.dtd" />

	<xsl:template match="/html/body">
	  <html>
	    <head>
	    	<title>Directory: <xsl:value-of select="request_uri"/></title>
				<style>
					body         {font-family: sans-serif; text-align: center;}
					table        {margin: 0px auto; border-collapse: collapse;}
					td           {border: 1px solid lightgrey;}
					a, a:visited {color: #ff6600; text-decoration: none;}
					a:hover      {text-decoration: underline;}
				</style>
			</head>
			<body>
				<h2>Directory: <xsl:value-of select="request_uri"/></h2>
				<table>
					<!-- define table column width -->
					<colgroup>
						<col width="50"/>  <!-- icon -->
						<col width="400"/> <!-- link -->
						<col width="180"/> <!-- last modified -->
						<col width="50"/>  <!-- size -->
					</colgroup>
					<!-- process 'tr' rows with td only (skip th rows) -->
					<xsl:apply-templates select="table/tr[td]"/>
				</table>
			</body>
	  </html>
	</xsl:template>
	
	<!-- print table rows -->
	<xsl:template match="tr">
		<tr>
			<td align="center"><xsl:copy-of select="td[position()=1]/*"/></td>
			<td align="left"><xsl:copy-of select="td[position()=2]/a"/></td>
			<td align="center"><xsl:value-of select="td[position()=3]"/></td>
			<td align="right"><xsl:value-of select="td[position()=4]"/></td>
		</tr>
	</xsl:template>
</xsl:stylesheet>

5. Conclusion
Described procedure may seem complicated, but it all boils down to the two files and a couple of lines in the httpd.conf file. First file is header file – static HTML or HTML with SSI or PHP (depends what you need). And the second file is XSL. Place files inside document root and make absolute reference to them in HeaderName directive and in header file. After files are created and Apache is configured, you should have a styled directory listing page.

I hope you might find this post useful.
Cheers!

25 thoughts on “Styling Apache directory listings”

  1. How do I get the “root-style” for directories within my root-files-directory, Did I miss something?
    And is it possible to have icons for mime-types? Thy are linked in the sites source, but do not appear on the site itself.

    Apart from that, very nice! And thanks in advance!

  2. @dirch – You have to set Indexes in Options directive, otherwise Apache will return “HTTP 403 – Forbidden” instead of directory list. More about Options can be read on Options Directive. Mime-type icons should be displayed by default after enabling directory listing. In Fedora distribution, icons are located in /var/www/icons and the icon mapping is defined in httpd.conf by AddIcon* directives.

  3. Thanks for your reply!

    To be more precise, and maybe you find my mistake, here is the link to my files directory http://mjhp.net/files/ . As you can see there are no icons. In the source i saw that they are expected to be in /icons, so i’ve created that one and copied a set of icons there (of course they are readable by everyone).

    My .htaccess looks like that:

    Options +Indexes

    IndexOptions FancyIndexing
    IndexOptions XHTML
    IndexOptions HTMLtable
    IndexOptions SuppressHTMLPreamble
    IndexOptions SuppressColumnSorting
    IndexOptions FoldersFirst
    IndexOptions IconsAreLinks
    IndexOptions NameWidth=*
    IndexOptions Type=text/xml

    IndexIgnore ..
    HeaderName HEADER.html

    The other files (stylesheet an HEADER.html are copied from your site.

    ah, the server is running FreeBSD 7.1-STABLE

    Thanks!

  4. @dirch – Error was in stylesheet.xsl in “tr” template. Please look at the first xsl:copy-of line (responsible for the column with icons).

    Before todays update, line was looked like:

    <td align="center"><xsl:copy-of select="td[position()=1]/img"/></td>
    

    and that was wrong. So, if you need icon and link, you should please replace “img” with “*” (as I updated stylesheet.xsl in this post)

    <td align="center"><xsl:copy-of select="td[position()=1]/*"/></td>
    

    Or if you need only icon, then you can write “a/img”.

    <td align="center"><xsl:copy-of select="td[position()=1]/a/img"/></td>
    

    Thank you for pointing to the icon problem.
    Bye!

  5. It’s me who has to thank you!

    My other “problem” looks like that http://mjhp.net/files/nostyle/. As you can see there is no style at all. Of course i could put my header and all that into every directory i create, but maybe there is another option to have the same style in the root directory and all subdirectories.

  6. @dirch – I think you have to set absolute path to the HEADER.html file. Instead of:

    HeaderName HEADER.html

    try with:

    HeaderName /files/HEADER.html

    Hope this will solve problem and all subdirectories will be styled as root dir.
    ;)

  7. Thats it. Sometimes i wonder how dumb me can be… Thanks a lot!
    btw: really nice site, keep up the good work!

  8. @Michael – PHP is server side technology and it can not be used in stylesheet.xsl. On the other hand, If you mean to dynamically create stylesheet.xsl with PHP (and force content-type to application/xml) – yes it could be possible. Few years ago I generated CSS file with PHP on the same way so it should be possible with XSL as well.

  9. Following your instructions, it worked in past days. I got error says “Error loading stylesheet: An unknown error has occurred (804b000e)”. There is no information about thin in /var/log/httpd/error_log. I googled this, someone said it might be caused by server performance. Can you please help?

    Thanks.

  10. @Michael ZHANG – The error is related to the loading problem. With the error description, browser should display full path of the problematic XSL file. Open new window and try to load XSL file descibed on error page. My guess is that XSL will be unreachable. Instead of absolute XSL path in XML document please try with relative path. You can find a lot of similar problems with sitemap.xml …

  11. I cut-n-pasted everthing into files and it works for the most part. However, request_uri is empty. That is, no directory name is displayed. Did I miss something?

  12. @jimmo – Apache writes directory name to the second column. You can verify if this is your case as well. Here are steps to follow:
    1) open rendered page with browser
    2) view page source (it should be generated XML)
    3) save page source to the text.xml file
    4) comment / delete line with defined XSL stylesheet (stylesheet is defined in XML header)

    <?xml-stylesheet type="text/xsl" href="/path/to/stylesheet.xsl"?>
    

    5) open modified text.xml with browser

    Now browser should nicely display generated HTML/XML file by Apache. In each TR node, directory name should be displayed in second TD:

    <tr>
        <td valign="top">
            <img src="/icons/folder.gif" alt="[DIR]"/>
        </td>
        <td>
            <a href="auth/">auth/</a>
        </td>
        <td align="right">12-Mar-2012 17:43</td>
        <td align="right">-</td>
        <td></td>
    </tr>
    

    If your HTML/XML is in different format, then you should modify stylesheet.xsl – but that should not be the case.

  13. Thanks for the reply. I verfied that things were being presented as you described. However, no directory name at the top of the page. That is, request_uri was empty. I am embarrassed to say it was pretty much a newbie mistaked. The !–#echo construct requires that the Includes option is present. Without it, !–#echo doesn’t work so request_uri is empty!

  14. @jimmo – I’m glad you solved the problem and thank you for feedback – it will be useful for others.

  15. Here is code that I’m using. It contains sort columns in the first row. Cheers!

    <!-- main table -->
    <table>
        <!-- define table column width -->
        <colgroup>
            <col width="50"/>  <!-- icon -->
            <col width="400"/> <!-- link -->
            <col width="180"/> <!-- last modified -->
            <col width="50"/>  <!-- size -->
        </column>
        <tr>
            <!-- icon (empty column) -->
            <td style="border: 0px"></td>
            <!-- link (name) -->
            <td align="left" style="border: 0px">
                <a href="{table/tr[position()=1]/th[position()=2]/a/@href}">Name</a>
            </td>
            <!-- last modified -->
            <td align="center" style="border: 0px">
                <a href="{table/tr[position()=1]/th[position()=3]/a/@href}">Time</a>
            </td>
            <!-- size -->
            <td align="center" style="border: 0px">
                <a href="{table/tr[position()=1]/th[position()=4]/a/@href}">Size</a>
            </td>
        </tr>
        <!-- process 'tr' rows with td only (skip th rows)  -->
        <xsl:apply-templates select="table/tr[td]"/>
    </table>
    
  16. Amazing tutorial. I only have 2 problems. The UTF-8 encouding doesn’t allow the displaying of characters õüäö, but it should.

    Lõputöö.docx
    is displayed as:
    Lõputöö.docx

    And I can’t seem to get an icon working for “parent directory”.
    Worked before, when using only css + html.

    Htaccess entry:
    AddIcon (IMG,/img/dl/ms/parent.png) ..

    Every other icon assigned seems to be working.
    http://oi41.tinypic.com/358x4aw.jpg

    Please help.

  17. Hello again,

    Actually quite stupid of me, I accidentally had the indexoption for utf 8 encoding removed. Got that one working, but still struggling with the parent directory icon not displaying, had to set a “default icon” for it to display before parent directory and of course all other unknown formats, which is not a permanent solution (atleast I hope).

    Is it also possible to rename what it displays as “parent directory”? in estonian it would be “ülemkaust”. Would be great, if you could help me! Thanks ahead!

Leave a Comment