download.cgi -- Organize a download directory


RULES file example syntax:

  printfile MyHtmlTopStuff.html
  print <h2>tar files:</h2>
  list .*\.tar\.(gz|bz2)
  print <h2>zip files:</h2>
  list .*\.zip
  ignore *~


Typically this can be installed by simply copying it to the directory it should serve and renaming it to index.cgi (e.g. /var/www/my-server/download/index.cgi) . Make sure to make it executable and make sure to create a RULES file for it to read.

You may need to set the ExecCGI option in an apache .htaccess file or httpd.conf file as well:

  Options +ExecCGI

In addition, if your server doesn't support the .cgi extension, make sure this line is uncommented in your httpd.conf file:

  AddHandler cgi-script .cgi


The script works by first reading in the RULES. file and caching the results. Each line is expected to be a comment (prefixed by a #), a blank line or a configuration token (described in the next section) followed by argument(s) to the end of the line.

The download.cgi script will then read in the directory in which it was placed and process each file according to the ordered set of rules loaded. The first matching rule will win and the output will be generated based on that rule.


There are a few different types of syntax lines can go into the RULES file. Per typical configuration files, lines starting with a # will be ignore as a comment.

Note: Configuration lines must not start with white-space, as this will be used to add optional configuration tokens to the rules in the future and the code already treats white-space starting lines differently.

Rule Options

Rule options can be created by prefixing a line with a white-space character. Thus, the following is a valid single rule definition that adds the "versionspaces" option to the rule:

    list .*.rpm
        versionspaces 1


printfile FILE

The printefile directive takes a single argument and simply dumps that file out. It's functionally equivelent to a "include" statement.

print TEXT

The print token simply prints the rest of the line to the output page. It is useful especially for quick header syntax above a list.


This is the real power behind the download.cgi script. This allows you to group files in a directory by regular expression matching. The list will be printed using HTML <ul> and <li> style tags [future versions will allow for a more flexible output style].

The list will be sorted by version numbers as best as possible. The first number after a - will be considered the start of a version number and high version numbers will be sorted to higher in the displayed list (so 1.7.1 will be above 1.7). The version sorting algorithm treats .preN and .rcN suffixes differently so that 1.7.1.pre1 will be sorted below 1.7.1. [future versions will allow for a more flexible output style].

Note: make sure you realize that a regular expression is required and typical unix globbing is not supported (yet). IE, "*.tar.gz" is not a valid argument.

Extra options:

versionspaces 1

This adds a verical space between files of different versions. This is most useful for grouping file sets together such as multilpe RPMs that make up a single version set.

    list mypackage.*.rpm
        versionspaces 1
versionheaders 1

This adds version headers ahead of each section with different versions so the results look something like:

    + 1.3
      + dnssec-tools-1.3.rpm
      + dnssec-tools-libs-1.3.rpm
    + 1.2
      + dnssec-tools-1.2.rpm
      + dnssec-tools-libs-1.2.rpm
suffixes LIST

This binds multiple suffixes together so that all similar file types end up on the same line. For example, if you distribute both .tar.gz files as well as .zip and maybe .tar.gz.md5 and .zip.md5, then the following line:

    list mypackage.*(zip|tar.gz)
       suffixes .tar.gz .zip .tar.gz.md5 .zip.md5

Will offer all downloads on a single lien that will look roughly like:

      + dnssec-tools-1.2.tar.gz | [.zip] [.tar.gz.md5] [.zip.md5]

(assuming all the files were available, otherwise the missing ones are excluded)

showdates 1

This will add the date for the last modification time of the file. If this is desired for all lists, use the 'global' property to set this globally.


This lets you set global parameters that affect all the rules. For example, you can have versionspaces turned on for all rules by putting this at the top of the file:

    global versionspaces 1
    global versionheaders 1
name NAME

This lets you name sections of the output for showing/hiding using the buttonbar token.

Sub-options for this include:

level N

Each named entry will get a <div> wrapper with a CSS classname of dcgiLevelN attached to it. This is useful for creating hierarchical sets of CSS-designable sections. Deeper levels of N will nested within higher ones. Additionally the buttonbar entries will be grouped into <span> sections as well so they can be structured using CSS.

hide 1

If the hide sub-token is specified (and is non-zero) then this section will default to being hidden.

hideunless STRING

This lets the entry be hidden by default unless the browser's usage-agent matches a particular string. This is most useful when STRING contains things like "Linux", "Windows" and "Macintosh" so that only sections are shown that match the operating system of the user.

h1 TITLE, h2 TITLE, h3 TITLE, ... hN TITLE, ...

This is a convenience token that translates the results into the equivalent of:

  name TITLE
    level N
    [any other specified options]
  print <a name="gotoTITLE" />
  print <hN>TITLE</hN>
buttonbar 1

This token can be placed in the output and a bar of buttons that toggle on/off sections of the page will be created.

Because this makes use of jquery, you'll need to add a source line to the html header for pulling in the jquery code from somewher. Such as:

  print <script type="text/javascript" src=""></script>
maxlevel N

If the maxlevel token is applied to the buttonbar line, then no buttons at a deth greater than N will be printed. This is useful when you have a big hierarchy and the buttons get too messy with all of them showing up showing.

ignore REGEXP

This allows files to be ignored so that error messages about unknown files don't get printed to the web server's error log.


This will likely only work with apache as the script expects the SCRIPT_FILENAME environment variable to be set, which may be an apache-ism.

The output is rather plain unless some CSS rules are applied. See the download-style.css file in the example directory for a starting set of CSS rules to add to the results.


See the example directory for an example rule set and files to test with. Start by looking at the RULES file. If you want to test the directory, place it in a web server, copy the download.cgi script into it (I suggest naming it index.cgi so the web server will automatically pick it up as an index) and then point your web browser at it.


The following features would be 'nice to haves:'

 - sort by various other methods
 - URL prefix other than current
 - generic list formatting mechanism 
 - hover notes
 - caching of data for speed (based on directory modification time)


Wes Hardaker <opensource AT hardakers DOT net>


Copyright (c) 2010-2011 Wes Hardaker

All rights reserved. This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.