From 74c3c310e6790130de2a135692476aecd8ab3614 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 26 Feb 2017 00:07:07 +0100 Subject: [PATCH] Fix --- ngx-fancyindex/.gitignore | 4 + ngx-fancyindex/.travis.yml | 34 + ngx-fancyindex/CHANGELOG.md | 111 ++ ngx-fancyindex/HACKING.md | 24 + ngx-fancyindex/LICENSE | 20 + ngx-fancyindex/README.rst | 273 ++++ ngx-fancyindex/config | 20 + ngx-fancyindex/nginx-0.6-support.patch | 23 + ngx-fancyindex/ngx_http_fancyindex_module.c | 1470 +++++++++++++++++++ ngx-fancyindex/t/00-build-artifacts.test | 22 + ngx-fancyindex/t/01-smoke-hasindex.test | 7 + ngx-fancyindex/t/02-smoke-indexisfancy.test | 11 + ngx-fancyindex/t/build-and-run | 25 + ngx-fancyindex/t/has-index.test | 7 + ngx-fancyindex/t/has-index/index.html | 10 + ngx-fancyindex/t/nginx.conf | 25 + ngx-fancyindex/t/preamble | 84 ++ ngx-fancyindex/t/run | 67 + ngx-fancyindex/template.awk | 52 + ngx-fancyindex/template.h | 103 ++ ngx-fancyindex/template.html | 102 ++ 21 files changed, 2494 insertions(+) create mode 100644 ngx-fancyindex/.gitignore create mode 100644 ngx-fancyindex/.travis.yml create mode 100644 ngx-fancyindex/CHANGELOG.md create mode 100644 ngx-fancyindex/HACKING.md create mode 100644 ngx-fancyindex/LICENSE create mode 100644 ngx-fancyindex/README.rst create mode 100644 ngx-fancyindex/config create mode 100644 ngx-fancyindex/nginx-0.6-support.patch create mode 100644 ngx-fancyindex/ngx_http_fancyindex_module.c create mode 100644 ngx-fancyindex/t/00-build-artifacts.test create mode 100644 ngx-fancyindex/t/01-smoke-hasindex.test create mode 100644 ngx-fancyindex/t/02-smoke-indexisfancy.test create mode 100755 ngx-fancyindex/t/build-and-run create mode 100644 ngx-fancyindex/t/has-index.test create mode 100644 ngx-fancyindex/t/has-index/index.html create mode 100644 ngx-fancyindex/t/nginx.conf create mode 100644 ngx-fancyindex/t/preamble create mode 100755 ngx-fancyindex/t/run create mode 100755 ngx-fancyindex/template.awk create mode 100644 ngx-fancyindex/template.h create mode 100644 ngx-fancyindex/template.html diff --git a/ngx-fancyindex/.gitignore b/ngx-fancyindex/.gitignore new file mode 100644 index 0000000..67d2c48 --- /dev/null +++ b/ngx-fancyindex/.gitignore @@ -0,0 +1,4 @@ +*.sw[op] +/t/*.sh +/t/*.out +/t/*.err diff --git a/ngx-fancyindex/.travis.yml b/ngx-fancyindex/.travis.yml new file mode 100644 index 0000000..d523468 --- /dev/null +++ b/ngx-fancyindex/.travis.yml @@ -0,0 +1,34 @@ +language: c + +compiler: + - clang + - gcc + +env: + # Mainline + - NGINX=1.11.3 + - NGINX=1.11.3 DYNAMIC=1 + # Stable + - NGINX=1.10.1 + - NGINX=1.10.1 DYNAMIC=1 + # Other configurations + - NGINX=1.9.15 + - NGINX=1.9.15 DYNAMIC=1 + - NGINX=1.8.1 + # Also, the oldest supported version + # TODO: Fails building because it won't find IOV_MAX from header. + #- NGINX=0.7.69 + +sudo: false + +addons: + apt: + packages: + - libpcre3-dev + - libssl-dev + +cache: ccache + +script: + - echo "NGINX=${NGINX} ${DYNAMIC:+DYNAMIC=${DYNAMIC}}" + - t/build-and-run ${NGINX} ${DYNAMIC} diff --git a/ngx-fancyindex/CHANGELOG.md b/ngx-fancyindex/CHANGELOG.md new file mode 100644 index 0000000..9d76ae0 --- /dev/null +++ b/ngx-fancyindex/CHANGELOG.md @@ -0,0 +1,111 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## [Unreleased] + +## [0.4.1] - 2016-08-18 +### Added +- New `fancyindex_directories_first` configuration directive (enabled by + default), which allows setting whether directories are sorted before other + files. (Patch by Luke Zapart <>.) + +### Fixed +- Fix index files not working when the fancyindex module is in use (#46). + + +## [0.4.0] - 2016-06-08 +### Added +- The module can now be built as a [dynamic + module](https://www.nginx.com/resources/wiki/extending/converting/). + (Patch by Róbert Nagy <>.) +- New configuration directive `fancyindex_show_path`, which allows hiding the + `

` header which contains the current path. + (Patch by Thomas P. <>.) + +### Changed +- Directory and file links in listings now have a title="..." attribute. + (Patch by `@janglapuk` <>.) + +### Fixed +- Fix for hung requests when the module is used along with `ngx_pagespeed`. + (Patch by Otto van der Schaaf <>.) + + +## [0.3.6] - 2016-01-26 +### Added +- New feature: Allow filtering out symbolic links using the + `fancyindex_hide_symlinks` configuration directive. (Idea and prototype + patch by Thomas Wemm.) +- New feature: Allow specifying the format of timestamps using the + `fancyindex_time_format` configuration directive. (Idea suggested by Xiao + Meng <>). + +### Changed +- Listings in top-level directories will not generate a "Parent Directory" + link as first element of the listing. (Patch by Thomas P.) + +### Fixed +- Fix propagation and overriding of the `fancyindex_css_href` setting inside + nested locations. +- Minor changes in the code to allow building cleanly under Windows with + Visual Studio 2013. (Patch by Y. Yuan <>). + + +## [0.3.5] - 2015-02-19 +### Added +- New feature: Allow setting the default sort criterion using the + `fancyindex_default_sort` configuration directive. (Patch by + Алексей Урбанский). +- New feature: Allow changing the maximum length of file names, using + the `fancyindex_name_length` configuration directive. (Patch by + Martin Herkt). + +### Changed +- Renames `NEWS.rst` to `CHANGELOG.md`, which follows the recommendations + from [Keep a Change Log](http://keepachangelog.com/). +- Configuring Nginx without the `http_addition_module` will generate a + warning during configuration, as it is needed for the `fancyindex_footer` + and `fancyindex_header` directives. + + +## [0.3.4] - 2014-09-03 + +### Added +- Viewport is now defined in the generated HTML, which works better + for mobile devices. + +### Changed +- Even-odd row styling moved to the CSS using :nth-child(). This + makes the HTML served to clients smaller. + + +## [0.3.3] - 2013-10-25 + +### Added +- New feature: table headers in the default template are now clickable + to set the sorting criteria and direction of the index entries. + (https://github.com/aperezdc/ngx-fancyindex/issues/7) + + +## [0.3.2] - 2013-06-05 + +### Fixed +- Solved a bug that would leave certain clients stalled forever. +- Improved handling of subrequests for non-builtin headers/footers. + + +## [0.3.1] - 2011-04-04 + +### Added +- `NEWS.rst` file, to act as change log. + + +[Unreleased]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.1...HEAD +[0.4.1]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.6...v0.4.0 +[0.3.6]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.5...v0.3.6 +[0.3.5]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.4...v0.3.5 +[0.3.4]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3...v0.3.1 diff --git a/ngx-fancyindex/HACKING.md b/ngx-fancyindex/HACKING.md new file mode 100644 index 0000000..0748517 --- /dev/null +++ b/ngx-fancyindex/HACKING.md @@ -0,0 +1,24 @@ +# Fancy Index module Hacking HOW-TO + +## How to modify the template + +The template is in the `template.html` file. Note that comment markers are +used to control how the `template.awk` Awk script generates the C header +which gets ultimately included in the compiled object code. Comment markers +have the `` format. Here `identifier` must be +a valid C identifier. All the text following the marker until the next +marker will be flattened into a C string. + +If the identifier is `NONE` (capitalized) the text from that marker up to +the next marker will be discarded. + + +## Regenerating the C header + +You will need Awk. I hope any decent implementation will do, but the GNU one +is known to work flawlessly. Just do: + + $ awk -f template.awk template.html > template.h + +If your copy of `awk` is not the GNU implementation, you will need to +install it and use `gawk` instead in the command line above. diff --git a/ngx-fancyindex/LICENSE b/ngx-fancyindex/LICENSE new file mode 100644 index 0000000..9fd66ee --- /dev/null +++ b/ngx-fancyindex/LICENSE @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. diff --git a/ngx-fancyindex/README.rst b/ngx-fancyindex/README.rst new file mode 100644 index 0000000..93a8206 --- /dev/null +++ b/ngx-fancyindex/README.rst @@ -0,0 +1,273 @@ +======================== +Nginx Fancy Index module +======================== + +.. image:: https://travis-ci.org/aperezdc/ngx-fancyindex.svg?branch=master + :target: https://travis-ci.org/aperezdc/ngx-fancyindex + :alt: Build Status + +.. contents:: + +The Fancy Index module makes possible the generation of file listings, like +the built-in `autoindex `__ +module does, but adding a touch of style. This is possible because the module +module allows a certain degree of customization of the generated content: + +* Custom headers. Either local or stored remotely. +* Custom footers. Either local or stored remotely. +* Add you own CSS style rules. +* Allow choosing to sort elements by name (default), modification time, or + size; both ascending (default), or descending. + +This module is designed to work with Nginx_, a high performance open source web +server written by `Igor Sysoev `__. + + +Requirements +============ + +You will need the sources for Nginx_. Any version starting from the 0.7 +series onwards will work. Note that the modules *might* compile with +versions in the 0.6 series by applying ``nginx-0.6-support.patch``, but this +is unsupported (YMMV). + +In order to use the fancyindex_header_ and fancyindex_footer_ directives +you will also need the `ngx_http_addition_module `_ +built into Nginx. + + +Building +======== + +1. Unpack the Nginx_ sources:: + + $ gunzip -c nginx-?.?.?.tar.gz | tar -xvf - + +2. Unpack the sources for the fancy indexing module:: + + $ gunzip -c nginx-fancyindex-?.?.?.tar.gz | tar -xvf - + +3. Change to the directory which contains the Nginx_ sources, run the + configuration script with the desired options and be sure to put an + ``--add-module`` flag pointing to the directory which contains the source + of the fancy indexing module:: + + $ cd nginx-?.?.? + $ ./configure --add-module=../nginx-fancyindex-?.?.? \ + [--with-http_addition_module] [extra desired options] + + Since version 0.4.0, the module can also be built as a + `dynamic module `_, + using ``--add-dynamic-module=…`` instead. + +4. Build and install the software:: + + $ make + + And then, as ``root``:: + + # make install + +5. Configure Nginx_ by using the modules' configuration directives_. + + +Example +======= + +You can test the default built-in style by adding the following lines into +a ``server`` section in your Nginx_ configuration file:: + + location / { + fancyindex on; # Enable fancy indexes. + fancyindex_exact_size off; # Output human-readable file sizes. + } + + +Themes +~~~~~~ + +The following themes demonstrate the level of customization which can be +achieved using the module: + +* `Theme `__ by + `@TheInsomniac `__. Uses custom header and + footer. +* `Theme `__ by + nwrd `__. Uses custom header and footer, the + header includes search field to filter by filename using JavaScript + (`demo `__). + + +Directives +========== + +fancyindex +~~~~~~~~~~ +:Syntax: *fancyindex* [*on* | *off*] +:Default: fancyindex off +:Context: http, server, location +:Description: + Enables or disables fancy directory indexes. + +fancyindex_default_sort +~~~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_default_sort* [*name* | *size* | *date* | *name_desc* | *size_desc* | *date_desc*] +:Default: fancyindex_default_sort name +:Context: http, server, location +:Description: + Defines sorting criterion by default. + +fancyindex_directories_first +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_directories_first* [*on* | *off*] +:Default: fancyindex_directories_first on +:Context: http, server, location +:Description: + If enabled (default setting), groups directories together and sorts them + before all regular files. If disabled, directories are sorted together with files. + +fancyindex_css_href +~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_css_href uri* +:Default: fancyindex_css_href "" +:Context: http, server, location +:Description: + Allows inserting a link to a CSS style sheet in generated listings. The + provided *uri* parameter will be inserted as-is in a ```` HTML tag. + The link is inserted after the built-in CSS rules, so you can override the + default styles. + +fancyindex_exact_size +~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_exact_size* [*on* | *off*] +:Default: fancyindex_exact_size on +:Context: http, server, location +:Description: + Defines how to represent file sizes in the directory listing; either + accurately, or rounding off to the kilobyte, the megabyte and the + gigabyte. + +fancyindex_name_length +~~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_name_length length* +:Default: fancyindex_name_length 50 +:Context: http, server, location +:Description: + Defines the maximum file name length limit in bytes. + +fancyindex_footer +~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_footer path* +:Default: fancyindex_footer "" +:Context: http, server, location +:Description: + Specifies which file should be inserted at the foot of directory listings. + If set to an empty string, the default footer supplied by the module will + be sent. + +.. note:: Using this directive needs the ngx_http_addition_module_ built + into Nginx. + +.. warning:: When inserting custom header/footer a subrequest will be + issued so potentially any URL can be used as source for them. Although it + will work with external URLs, only using internal ones is supported. + External URLs are totally untested and using them will make Nginx_ block + while waiting for the subrequest to complete. If you feel like external + header/footer is a must-have for you, please + `let me know `__. + +fancyindex_header +~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_header path* +:Default: fancyindex_header "" +:Context: http, server, location +:Description: + Specifies which file should be inserted at the head of directory listings. + If set to an empty string, the default header supplied by the module will + be sent. + +.. note:: Using this directive needs the ngx_http_addition_module_ built + into Nginx. + +fancyindex_show_path +~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_show_path* [*on* | *off*] +:Default: fancyindex_show_path on +:Context: http, server, location +:Description: + Whether to output or not the path and the closing

tag after the header. + This is useful when you want to handle the path displaying with a PHP script + for example. + +.. warning:: This directive can be turned off only if a custom header is provided + using fancyindex_header. + +fancyindex_ignore +~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_ignore string1 [string2 [... stringN]]* +:Default: No default. +:Context: http, server, location +:Description: + Specifies a list of file names which will be not be shown in generated + listings. If Nginx was built with PCRE support strings are interpreted as + regular expressions. + +fancyindex_hide_symlinks +~~~~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_hide_symlinks* [*on* | *off*] +:Default: fancyindex_hide_symlinks off +:Context: http, server, location +:Description: + When enabled, generated listings will not contain symbolic links. + +fancyindex_localtime +~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_localtime* [*on* | *off*] +:Default: fancyindex_localtime off +:Context: http, server, location +:Description: + Enables showing file times as local time. Default is “off” (GMT time). + +fancyindex_time_format +~~~~~~~~~~~~~~~~~~~~~~ +:Syntax: *fancyindex_time_format* string +:Default: fancyindex_time_format "%Y-%b-%d %H:%M" +:Context: http, server, location +:Description: + Format string used for timestamps. The format specifiers are a subset of + those supported by the `strftime `_ + function, and the behavior is locale-independent (for example, day and month + names are always in English). The supported formats are: + + * ``%a``: Abbreviated name of the day of the week. + * ``%A``: Full name of the day of the week. + * ``%b``: Abbreviated month name. + * ``%B``: Full month name. + * ``%d``: Day of the month as a decimal number (range 01 to 31). + * ``%e``: Like ``%d``, the day of the month as a decimal number, but a + leading zero is replaced by a space. + * ``%F``: Equivalent to ``%Y-%m-%d`` (the ISO 8601 date format). + * ``%H``: Hour as a decimal number using a 24-hour clock (range 00 + to 23). + * ``%I``: Hour as a decimal number using a 12-hour clock (range 01 to 12). + * ``%k``: Hour (24-hour clock) as a decimal number (range 0 to 23); + single digits are preceded by a blank. + * ``%l``: Hour (12-hour clock) as a decimal number (range 1 to 12); single + digits are preceded by a blank. + * ``%m``: Month as a decimal number (range 01 to 12). + * ``%M``: Minute as a decimal number (range 00 to 59). + * ``%p``: Either "AM" or "PM" according to the given time value. + * ``%P``: Like ``%p`` but in lowercase: "am" or "pm". + * ``%r``: Time in a.m. or p.m. notation. Equivalent to ``%I:%M:%S %p``. + * ``%R``: Time in 24-hour notation (``%H:%M``). + * ``%S``: Second as a decimal number (range 00 to 60). + * ``%T``: Time in 24-hour notation (``%H:%M:%S``). + * ``%u``: Day of the week as a decimal, range 1 to 7, Monday being 1. + * ``%w``: Day of the week as a decimal, range 0 to 6, Monday being 0. + * ``%y``: Year as a decimal number without a century (range 00 to 99). + * ``%Y``: Year as a decimal number including the century. + + +.. _nginx: http://nginx.net + +.. vim:ft=rst:spell:spelllang=en: diff --git a/ngx-fancyindex/config b/ngx-fancyindex/config new file mode 100644 index 0000000..4ef3809 --- /dev/null +++ b/ngx-fancyindex/config @@ -0,0 +1,20 @@ +# vim:ft=sh: +ngx_addon_name=ngx_http_fancyindex_module + +if [ "$ngx_module_link" = DYNAMIC ] ; then + ngx_module_type=HTTP + ngx_module_name=ngx_http_fancyindex_module + ngx_module_srcs="$ngx_addon_dir/ngx_http_fancyindex_module.c" + ngx_module_deps="$ngx_addon_dir/template.h" + ngx_module_order="$ngx_module_name ngx_http_autoindex_module" + . auto/module +else + # XXX: Insert fancyindex module *after* index module! + # + HTTP_MODULES=`echo "${HTTP_MODULES}" | sed -e \ + 's/ngx_http_index_module/ngx_http_fancyindex_module ngx_http_index_module/'` + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fancyindex_module.c" + if [ $HTTP_ADDITION != YES ] ; then + echo " - The 'addition' filter is needed for fancyindex_{header,footer}, but it was disabled" + fi +fi diff --git a/ngx-fancyindex/nginx-0.6-support.patch b/ngx-fancyindex/nginx-0.6-support.patch new file mode 100644 index 0000000..e79e0b3 --- /dev/null +++ b/ngx-fancyindex/nginx-0.6-support.patch @@ -0,0 +1,23 @@ +=== modified file 'ngx_http_fancyindex_module.c' +--- ngx_http_fancyindex_module.c 2008-09-11 17:55:52 +0000 ++++ ngx_http_fancyindex_module.c 2008-12-10 01:33:43 +0000 +@@ -383,7 +383,7 @@ + entry->mtime = ngx_de_mtime(&dir); + entry->size = ngx_de_size(&dir); + entry->utf_len = (r->utf8) +- ? ngx_utf8_length(entry->name.data, entry->name.len) ++ ? ngx_utf_length(entry->name.data, entry->name.len) + : len; + } + +@@ -478,8 +478,7 @@ + copy = NGX_HTTP_FANCYINDEX_NAME_LEN + 1; + } + +- b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data, +- copy, entry[i].name.len); ++ b->last = ngx_utf_cpystrn(b->last, entry[i].name.data, copy); + last = b->last; + + } else { + diff --git a/ngx-fancyindex/ngx_http_fancyindex_module.c b/ngx-fancyindex/ngx_http_fancyindex_module.c new file mode 100644 index 0000000..170167d --- /dev/null +++ b/ngx-fancyindex/ngx_http_fancyindex_module.c @@ -0,0 +1,1470 @@ +/* + * ngx_http_fancyindex_module.c + * Copyright © 2007-2016 Adrian Perez + * + * Module used for fancy indexing of directories. Features and differences + * with the stock nginx autoindex module: + * + * - Output is a table instead of a
 element with embedded  links.
+ *  - Header and footer may be added to every generated directory listing.
+ *  - Default header and/or footer are generated if custom ones are not
+ *    configured. Files used for header and footer can only be local path
+ *    names (i.e. you cannot insert the result of a subrequest.)
+ *  - Proper HTML is generated: it should validate both as XHTML 1.0 Strict
+ *    and HTML 4.01.
+ *
+ * Base functionality heavy based upon the stock nginx autoindex module,
+ * which in turn was made by Igor Sysoev, like the majority of nginx.
+ *
+ * Distributed under terms of the BSD license.
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#include "template.h"
+
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+# define ngx_force_inline __attribute__((__always_inline__))
+#else /* !__GNUC__ */
+# define ngx_force_inline
+#endif /* __GNUC__ */
+
+
+static const char *short_weekday[] = {
+    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+};
+static const char *long_weekday[] = {
+    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Sunday",
+};
+static const char *short_month[] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+};
+static const char *long_month[] = {
+    "January", "February", "March", "April", "May", "June", "July",
+    "August", "September", "October", "November", "December",
+};
+
+
+#define DATETIME_FORMATS(F_, t) \
+    F_ ('a',  3, "%3s",  short_weekday[((t)->ngx_tm_wday + 6) % 7]) \
+    F_ ('A',  9, "%s",   long_weekday [((t)->ngx_tm_wday + 6) % 7]) \
+    F_ ('b',  3, "%3s",  short_month[(t)->ngx_tm_mon - 1]         ) \
+    F_ ('B',  9, "%s",   long_month [(t)->ngx_tm_mon - 1]         ) \
+    F_ ('d',  2, "%02d", (t)->ngx_tm_mday                         ) \
+    F_ ('e',  2, "%2d",  (t)->ngx_tm_mday                         ) \
+    F_ ('F', 10, "%d-%02d-%02d",                                    \
+                  (t)->ngx_tm_year,                                 \
+                  (t)->ngx_tm_mon,                                  \
+                  (t)->ngx_tm_mday                                ) \
+    F_ ('H',  2, "%02d", (t)->ngx_tm_hour                         ) \
+    F_ ('I',  2, "%02d", ((t)->ngx_tm_hour % 12) + 1              ) \
+    F_ ('k',  2, "%2d",  (t)->ngx_tm_hour                         ) \
+    F_ ('l',  2, "%2d",  ((t)->ngx_tm_hour % 12) + 1              ) \
+    F_ ('m',  2, "%02d", (t)->ngx_tm_mon                          ) \
+    F_ ('M',  2, "%02d", (t)->ngx_tm_min                          ) \
+    F_ ('p',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "AM" : "PM")  ) \
+    F_ ('P',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "am" : "pm")  ) \
+    F_ ('r', 11, "%02d:%02d:%02d %2s",                              \
+                 ((t)->ngx_tm_hour % 12) + 1,                       \
+                 (t)->ngx_tm_min,                                   \
+                 (t)->ngx_tm_sec,                                   \
+                 (((t)->ngx_tm_hour < 12) ? "AM" : "PM")          ) \
+    F_ ('R',  5, "%02d:%02d", (t)->ngx_tm_hour, (t)->ngx_tm_min   ) \
+    F_ ('S',  2, "%02d", (t)->ngx_tm_sec                          ) \
+    F_ ('T',  8, "%02d:%02d:%02d",                                  \
+                 (t)->ngx_tm_hour,                                  \
+                 (t)->ngx_tm_min,                                   \
+                 (t)->ngx_tm_sec                                  ) \
+    F_ ('u',  1, "%1d", (((t)->ngx_tm_wday + 6) % 7) + 1          ) \
+    F_ ('w',  1, "%1d", ((t)->ngx_tm_wday + 6) % 7                ) \
+    F_ ('y',  2, "%02d", (t)->ngx_tm_year % 100                   ) \
+    F_ ('Y',  4, "%04d", (t)->ngx_tm_year                         )
+
+
+static size_t
+ngx_fancyindex_timefmt_calc_size (const ngx_str_t *fmt)
+{
+#define DATETIME_CASE(letter, fmtlen, fmt, ...) \
+        case letter: result += (fmtlen); break;
+
+    size_t i, result = 0;
+    for (i = 0; i < fmt->len; i++) {
+        if (fmt->data[i] == '%') {
+            if (++i >= fmt->len) {
+                result++;
+                break;
+            }
+            switch (fmt->data[i]) {
+                DATETIME_FORMATS(DATETIME_CASE,)
+                default:
+                    result++;
+            }
+        } else {
+            result++;
+        }
+    }
+    return result;
+
+#undef DATETIME_CASE
+}
+
+
+static u_char*
+ngx_fancyindex_timefmt (u_char *buffer, const ngx_str_t *fmt, const ngx_tm_t *tm)
+{
+#define DATETIME_CASE(letter, fmtlen, fmt, ...) \
+        case letter: buffer = ngx_snprintf(buffer, fmtlen, fmt, ##__VA_ARGS__); break;
+
+    size_t i;
+    for (i = 0; i < fmt->len; i++) {
+        if (fmt->data[i] == '%') {
+            if (++i >= fmt->len) {
+                *buffer++ = '%';
+                break;
+            }
+            switch (fmt->data[i]) {
+                DATETIME_FORMATS(DATETIME_CASE, tm)
+                default:
+                    *buffer++ = fmt->data[i];
+            }
+        } else {
+            *buffer++ = fmt->data[i];
+        }
+    }
+    return buffer;
+
+#undef DATETIME_CASE
+}
+
+
+/**
+ * Configuration structure for the fancyindex module. The configuration
+ * commands defined in the module do fill in the members of this structure.
+ */
+typedef struct {
+    ngx_flag_t enable;       /**< Module is enabled. */
+    ngx_uint_t default_sort; /**< Default sort criterion. */
+    ngx_flag_t dirs_first;   /**< Group directories together first when sorting */
+    ngx_flag_t localtime;    /**< File mtime dates are sent in local time. */
+    ngx_flag_t exact_size;   /**< Sizes are sent always in bytes. */
+    ngx_uint_t name_length;  /**< Maximum length of file names in bytes. */
+    ngx_flag_t hide_symlinks;/**< Hide symbolic links in listings. */
+    ngx_flag_t show_path;    /**< Whether to display or not the path + '' after the header */
+
+    ngx_str_t  header;       /**< File name for header, or empty if none. */
+    ngx_str_t  footer;       /**< File name for footer, or empty if none. */
+    ngx_str_t  css_href;     /**< Link to a CSS stylesheet, or empty if none. */
+    ngx_str_t  time_format;  /**< Format used for file timestamps. */
+
+    ngx_array_t *ignore;     /**< List of files to ignore in listings. */
+} ngx_http_fancyindex_loc_conf_t;
+
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME       0
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE       1
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE       2
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC  3
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC  4
+#define NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC  5
+
+static ngx_conf_enum_t ngx_http_fancyindex_sort_criteria[] = {
+    { ngx_string("name"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME },
+    { ngx_string("size"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE },
+    { ngx_string("date"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE },
+    { ngx_string("name_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC },
+    { ngx_string("size_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC },
+    { ngx_string("date_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC },
+    { ngx_null_string, 0 }
+};
+
+
+#define NGX_HTTP_FANCYINDEX_PREALLOCATE  50
+
+
+/**
+ * Calculates the length of a NULL-terminated string. It is ugly having to
+ * remember to substract 1 from the sizeof result.
+ */
+#define ngx_sizeof_ssz(_s)  (sizeof(_s) - 1)
+
+
+/**
+ * Copy a static zero-terminated string. Useful to output template
+ * string pieces into a temporary buffer.
+ */
+#define ngx_cpymem_ssz(_p, _t) \
+	(ngx_cpymem((_p), (_t), sizeof(_t) - 1))
+
+/**
+ * Copy a ngx_str_t.
+ */
+#define ngx_cpymem_str(_p, _s) \
+	(ngx_cpymem((_p), (_s).data, (_s).len))
+
+/**
+ * Check whether a particular bit is set in a particular value.
+ */
+#define ngx_has_flag(_where, _what) \
+	(((_where) & (_what)) == (_what))
+
+
+
+
+typedef struct {
+    ngx_str_t      name;
+    size_t         utf_len;
+    ngx_uint_t     escape;
+    ngx_uint_t     dir;
+    time_t         mtime;
+    off_t          size;
+} ngx_http_fancyindex_entry_t;
+
+
+
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_dirs_first(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_name_desc(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_size_desc(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_mtime_desc(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_name_asc(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_size_asc(const void *one, const void *two);
+static ngx_int_t ngx_libc_cdecl
+    ngx_http_fancyindex_cmp_entries_mtime_asc(const void *one, const void *two);
+
+static ngx_int_t ngx_http_fancyindex_error(ngx_http_request_t *r,
+    ngx_dir_t *dir, ngx_str_t *name);
+
+static ngx_int_t ngx_http_fancyindex_init(ngx_conf_t *cf);
+
+static void *ngx_http_fancyindex_create_loc_conf(ngx_conf_t *cf);
+
+static char *ngx_http_fancyindex_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+static char *ngx_http_fancyindex_ignore(ngx_conf_t    *cf,
+                                        ngx_command_t *cmd,
+                                        void          *conf);
+
+static uintptr_t
+    ngx_fancyindex_escape_uri(u_char *dst, u_char*src, size_t size);
+
+/*
+ * These are used only once per handler invocation. We can tell GCC to
+ * inline them always, if possible (see how ngx_force_inline is defined
+ * above).
+ */
+static ngx_inline ngx_buf_t*
+    make_header_buf(ngx_http_request_t *r, const ngx_str_t css_href)
+    ngx_force_inline;
+
+static ngx_inline ngx_buf_t*
+    make_footer_buf(ngx_http_request_t *r)
+    ngx_force_inline;
+
+
+
+static ngx_command_t  ngx_http_fancyindex_commands[] = {
+
+    { ngx_string("fancyindex"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, enable),
+      NULL },
+
+    { ngx_string("fancyindex_default_sort"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, default_sort),
+      &ngx_http_fancyindex_sort_criteria },
+
+    { ngx_string("fancyindex_directories_first"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, dirs_first),
+      NULL },
+
+    { ngx_string("fancyindex_localtime"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, localtime),
+      NULL },
+
+    { ngx_string("fancyindex_exact_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, exact_size),
+      NULL },
+
+    { ngx_string("fancyindex_name_length"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, name_length),
+      NULL },
+
+    { ngx_string("fancyindex_header"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, header),
+      NULL },
+
+    { ngx_string("fancyindex_footer"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, footer),
+      NULL },
+
+    { ngx_string("fancyindex_css_href"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, css_href),
+      NULL },
+
+    { ngx_string("fancyindex_ignore"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_fancyindex_ignore,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("fancyindex_hide_symlinks"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, hide_symlinks),
+      NULL },
+
+    { ngx_string("fancyindex_show_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, show_path),
+      NULL },
+
+    { ngx_string("fancyindex_time_format"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fancyindex_loc_conf_t, time_format),
+      NULL },
+
+    ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_fancyindex_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_fancyindex_init,              /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_fancyindex_create_loc_conf,   /* create location configration */
+    ngx_http_fancyindex_merge_loc_conf     /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_fancyindex_module = {
+    NGX_MODULE_V1,
+    &ngx_http_fancyindex_module_ctx,       /* module context */
+    ngx_http_fancyindex_commands,          /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+
+static const ngx_str_t css_href_pre =
+    ngx_string("\n");
+
+
+static uintptr_t
+ngx_fancyindex_escape_uri(u_char *dst, u_char *src, size_t size)
+{
+    /*
+     * The ngx_escape_uri() function will not escape colons or the
+     * ? character, which signals the beginning of the query string.
+     * So we handle those characters ourselves.
+     *
+     * TODO: Get rid of this once ngx_escape_uri() works as expected!
+     */
+
+    u_int escapes = 0;
+    u_char *psrc = src;
+    size_t psize = size;
+
+    while (psize--) {
+        switch (*psrc++) {
+            case ':':
+            case '?':
+                escapes++;
+                break;
+        }
+    }
+
+    if (dst == NULL) {
+        return escapes + ngx_escape_uri(NULL, src, size, NGX_ESCAPE_HTML);
+    }
+    else if (escapes == 0) {
+        /* No need to do extra escaping, avoid the temporary buffer */
+        return ngx_escape_uri(dst, src, size, NGX_ESCAPE_HTML);
+    }
+    else {
+        uintptr_t uescapes = ngx_escape_uri(NULL, src, size, NGX_ESCAPE_HTML);
+        size_t bufsz = size + 2 * uescapes;
+
+        /*
+         * GCC and CLANG both support stack-allocated variable length
+         * arrays. Take advantage of that to avoid a malloc-free cycle.
+         */
+#if defined(__GNUC__) || defined(__clang__)
+        u_char cbuf[bufsz];
+        u_char *buf = cbuf;
+#else  /* __GNUC__ || __clang__ */
+        u_char *buf = (u_char*) malloc(sizeof(u_char) * bufsz);
+#endif /* __GNUC__ || __clang__ */
+
+        ngx_escape_uri(buf, src, size, NGX_ESCAPE_HTML);
+
+        while (bufsz--) {
+            switch (*buf) {
+                case ':':
+                    *dst++ = '%';
+                    *dst++ = '3';
+                    *dst++ = 'A';
+                    break;
+                case '?':
+                    *dst++ = '%';
+                    *dst++ = '3';
+                    *dst++ = 'F';
+                    break;
+                default:
+                    *dst++ = *buf;
+            }
+            buf++;
+        }
+
+#if !defined(__GNUC__) && !defined(__clang__)
+        free(buf);
+#endif /* !__GNUC__ && !__clang__ */
+
+        return escapes + uescapes;
+    }
+}
+
+
+static ngx_inline ngx_buf_t*
+make_header_buf(ngx_http_request_t *r, const ngx_str_t css_href)
+{
+    size_t blen = r->uri.len
+        + ngx_sizeof_ssz(t01_head1)
+        + ngx_sizeof_ssz(t02_head2)
+        + ngx_sizeof_ssz(t03_head3)
+        + ngx_sizeof_ssz(t04_body1)
+        ;
+
+    if (css_href.len) {
+        blen += css_href_pre.len \
+              + css_href.len \
+              + css_href_post.len
+              ;
+    }
+
+    ngx_buf_t *b = ngx_create_temp_buf(r->pool, blen);
+
+    if (b == NULL) goto bailout;
+
+    b->last = ngx_cpymem_ssz(b->last, t01_head1);
+
+    if (css_href.len) {
+        b->last = ngx_cpymem_str(b->last, css_href_pre);
+        b->last = ngx_cpymem_str(b->last, css_href);
+        b->last = ngx_cpymem_str(b->last, css_href_post);
+    }
+
+    b->last = ngx_cpymem_ssz(b->last, t02_head2);
+    b->last = ngx_cpymem_str(b->last, r->uri);
+    b->last = ngx_cpymem_ssz(b->last, t03_head3);
+    b->last = ngx_cpymem_ssz(b->last, t04_body1);
+
+bailout:
+    return b;
+}
+
+
+
+static ngx_inline ngx_buf_t*
+make_footer_buf(ngx_http_request_t *r)
+{
+    /*
+     * TODO: Make this buffer static (i.e. readonly and reusable from
+     * one request to another.
+     */
+    ngx_buf_t *b = ngx_create_temp_buf(r->pool, ngx_sizeof_ssz(t08_foot1));
+
+    if (b == NULL) goto bailout;
+
+    b->last = ngx_cpymem_ssz(b->last, t08_foot1);
+
+bailout:
+    return b;
+}
+
+
+
+static ngx_inline ngx_int_t
+make_content_buf(
+        ngx_http_request_t *r, ngx_buf_t **pb,
+        ngx_http_fancyindex_loc_conf_t *alcf)
+{
+    ngx_http_fancyindex_entry_t *entry;
+
+    ngx_int_t (*sort_cmp_func) (const void*, const void*);
+    const char  *sort_url_args = "";
+
+    off_t        length;
+    size_t       len, root, copy, allocated;
+    u_char      *filename, *last, scale;
+    ngx_tm_t     tm;
+    ngx_array_t  entries;
+    ngx_time_t  *tp;
+    ngx_uint_t   i;
+    ngx_int_t    size;
+    ngx_str_t    path;
+    ngx_dir_t    dir;
+    ngx_buf_t   *b;
+
+    /*
+     * NGX_DIR_MASK_LEN is lesser than NGX_HTTP_FANCYINDEX_PREALLOCATE
+     */
+    if ((last = ngx_http_map_uri_to_path(r, &path, &root,
+                    NGX_HTTP_FANCYINDEX_PREALLOCATE)) == NULL)
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+    allocated = path.len;
+    path.len  = last - path.data - 1;
+    path.data[path.len] = '\0';
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http fancyindex: \"%s\"", path.data);
+
+    if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
+        ngx_int_t rc, err = ngx_errno;
+        ngx_uint_t level;
+
+        if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_NOT_FOUND;
+        } else if (err == NGX_EACCES) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+        } else {
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_log_error(level, r->connection->log, err,
+                ngx_open_dir_n " \"%s\" failed", path.data);
+
+        return rc;
+    }
+
+#if (NGX_SUPPRESS_WARN)
+    /* MSVC thinks 'entries' may be used without having been initialized */
+    ngx_memzero(&entries, sizeof(ngx_array_t));
+#endif /* NGX_SUPPRESS_WARN */
+
+
+    if (ngx_array_init(&entries, r->pool, 40,
+                sizeof(ngx_http_fancyindex_entry_t)) != NGX_OK)
+        return ngx_http_fancyindex_error(r, &dir, &path);
+
+    filename = path.data;
+    filename[path.len] = '/';
+
+    /* Read directory entries and their associated information. */
+    for (;;) {
+        ngx_set_errno(0);
+
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            ngx_int_t err = ngx_errno;
+
+            if (err != NGX_ENOMOREFILES) {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                        ngx_read_dir_n " \"%V\" failed", &path);
+                return ngx_http_fancyindex_error(r, &dir, &path);
+            }
+            break;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http fancyindex file: \"%s\"", ngx_de_name(&dir));
+
+        len = ngx_de_namelen(&dir);
+
+        if (ngx_de_name(&dir)[0] == '.')
+            continue;
+
+        if (alcf->hide_symlinks && ngx_de_is_link (&dir))
+            continue;
+
+#if NGX_PCRE
+        {
+            ngx_str_t str;
+            str.len = len;
+            str.data = ngx_de_name(&dir);
+
+            if (alcf->ignore && ngx_regex_exec_array(alcf->ignore, &str,
+                                                     r->connection->log)
+                != NGX_DECLINED)
+            {
+                continue;
+            }
+        }
+#else /* !NGX_PCRE */
+        if (alcf->ignore) {
+            u_int match_found = 0;
+            ngx_str_t *s = alcf->ignore->elts;
+
+            for (i = 0; i < alcf->ignore->nelts; i++, s++) {
+                if (ngx_strcmp(ngx_de_name(&dir), s->data) == 0) {
+                    match_found = 1;
+                    break;
+                }
+            }
+
+            if (match_found) {
+                continue;
+            }
+        }
+#endif /* NGX_PCRE */
+
+        if (!dir.valid_info) {
+            /* 1 byte for '/' and 1 byte for terminating '\0' */
+            if (path.len + 1 + len + 1 > allocated) {
+                allocated = path.len + 1 + len + 1
+                          + NGX_HTTP_FANCYINDEX_PREALLOCATE;
+
+                if ((filename = ngx_palloc(r->pool, allocated)) == NULL)
+                    return ngx_http_fancyindex_error(r, &dir, &path);
+
+                last = ngx_cpystrn(filename, path.data, path.len + 1);
+                *last++ = '/';
+            }
+
+            ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
+
+            if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
+                ngx_int_t err = ngx_errno;
+
+                if (err != NGX_ENOENT) {
+                    ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+                            ngx_de_info_n " \"%s\" failed", filename);
+                    continue;
+                }
+
+                if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                            ngx_de_link_info_n " \"%s\" failed", filename);
+                    return ngx_http_fancyindex_error(r, &dir, &path);
+                }
+            }
+        }
+
+        if ((entry = ngx_array_push(&entries)) == NULL)
+            return ngx_http_fancyindex_error(r, &dir, &path);
+
+        entry->name.len  = len;
+        entry->name.data = ngx_palloc(r->pool, len + 1);
+        if (entry->name.data == NULL)
+            return ngx_http_fancyindex_error(r, &dir, &path);
+
+        ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
+        entry->escape = 2 * ngx_fancyindex_escape_uri(NULL,
+                                                      ngx_de_name(&dir),
+                                                      len);
+
+        entry->dir     = ngx_de_is_dir(&dir);
+        entry->mtime   = ngx_de_mtime(&dir);
+        entry->size    = ngx_de_size(&dir);
+        entry->utf_len = (r->headers_out.charset.len == 5 &&
+                ngx_strncasecmp(r->headers_out.charset.data, (u_char*) "utf-8", 5) == 0)
+            ?  ngx_utf8_length(entry->name.data, entry->name.len)
+            : len;
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                ngx_close_dir_n " \"%s\" failed", &path);
+    }
+
+    /*
+     * Calculate needed buffer length.
+     */
+    if (alcf->show_path)
+        len = r->uri.len
+          + ngx_sizeof_ssz(t05_body2)
+          + ngx_sizeof_ssz(t06_list1)
+          + ngx_sizeof_ssz(t_parentdir_entry)
+          + ngx_sizeof_ssz(t07_list2)
+          + ngx_fancyindex_timefmt_calc_size (&alcf->time_format) * entries.nelts
+          ;
+   else
+        len = r->uri.len
+          + ngx_sizeof_ssz(t06_list1)
+          + ngx_sizeof_ssz(t_parentdir_entry)
+          + ngx_sizeof_ssz(t07_list2)
+          + ngx_fancyindex_timefmt_calc_size (&alcf->time_format) * entries.nelts
+          ;
+
+    /*
+     * If we are a the root of the webserver (URI =  "/" --> length of 1),
+     * do not display the "Parent Directory" link.
+     */
+    if (r->uri.len == 1) {
+        len -= ngx_sizeof_ssz(t_parentdir_entry);
+    }
+
+    entry = entries.elts;
+    for (i = 0; i < entries.nelts; i++) {
+        /*
+         * Genearated table rows are as follows, unneeded whitespace
+         * is stripped out:
+         *
+         *   
+         *     fname
+         *     sizedate
+         *   
+         */
+        len += ngx_sizeof_ssz("")
+            + entry[i].name.len + entry[i].utf_len
+            + alcf->name_length + ngx_sizeof_ssz(">")
+            + ngx_sizeof_ssz("")
+            + 20 /* File size */
+            + ngx_sizeof_ssz("")    /* Date prefix */
+            + ngx_sizeof_ssz("\n") /* Date suffix */
+            + 2 /* CR LF */
+            ;
+    }
+
+    if ((b = ngx_create_temp_buf(r->pool, len)) == NULL)
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+    /*
+     * Determine the sorting criteria. URL arguments look like:
+     *
+     *    C=x[&O=y]
+     *
+     * Where x={M,S,N} and y={A,D}
+     */
+    if ((r->args.len == 3 || (r->args.len == 7 && r->args.data[3] == '&')) &&
+        r->args.data[0] == 'C' && r->args.data[1] == '=')
+    {
+        /* Determine whether the direction of the sorting */
+        ngx_int_t sort_descending = r->args.len == 7
+                                 && r->args.data[4] == 'O'
+                                 && r->args.data[5] == '='
+                                 && r->args.data[6] == 'D';
+
+        /* Pick the sorting criteria */
+        switch (r->args.data[2]) {
+            case 'M': /* Sort by mtime */
+                if (sort_descending) {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_desc;
+                    if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC)
+                        sort_url_args = "?C=M&O=D";
+                }
+                else {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_asc;
+                    if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE)
+                        sort_url_args = "?C=M&O=A";
+                }
+                break;
+            case 'S': /* Sort by size */
+                if (sort_descending) {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_desc;
+                    if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC)
+                        sort_url_args = "?C=S&O=D";
+                }
+                else {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_asc;
+                        if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE)
+                    sort_url_args = "?C=S&O=A";
+                }
+                break;
+            case 'N': /* Sort by name */
+            default:
+                if (sort_descending) {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_name_desc;
+                    if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC)
+                        sort_url_args = "?C=N&O=D";
+                }
+                else {
+                    sort_cmp_func = ngx_http_fancyindex_cmp_entries_name_asc;
+                    if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME)
+                        sort_url_args = "?C=N&O=A";
+                }
+                break;
+        }
+    }
+    else {
+        switch (alcf->default_sort) {
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_desc;
+                break;
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_asc;
+                break;
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_desc;
+                break;
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_asc;
+                break;
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_name_desc;
+                break;
+            case NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME:
+            default:
+                sort_cmp_func = ngx_http_fancyindex_cmp_entries_name_asc;
+                break;
+        }
+    }
+
+    /* Sort entries, if needed */
+    if (entries.nelts > 1) {
+        /* Use ngx_sort for stability */ 
+        ngx_sort(entry, (size_t) entries.nelts,
+                  sizeof(ngx_http_fancyindex_entry_t),
+                  sort_cmp_func);
+
+        if (alcf->dirs_first)
+        {
+            /* Sort directories first */
+            ngx_sort(entry, (size_t) entries.nelts,
+                      sizeof(ngx_http_fancyindex_entry_t),
+                      ngx_http_fancyindex_cmp_entries_dirs_first);
+        }
+
+    }
+
+    /* Display the path, if needed */
+    if (alcf->show_path){
+        b->last = ngx_cpymem_str(b->last, r->uri);
+        b->last = ngx_cpymem_ssz(b->last, t05_body2);
+    }
+
+    /* Open the  tag */
+    b->last = ngx_cpymem_ssz(b->last, t06_list1);
+
+    tp = ngx_timeofday();
+
+    /* "Parent dir" entry, always first if displayed */
+    if (r->uri.len > 1) {
+        b->last = ngx_cpymem_ssz(b->last,
+                                 ""
+                                 ""
+                                 ""
+                                 ""
+                                 "");
+    }
+
+    /* Entries for directories and files */
+    for (i = 0; i < entries.nelts; i++) {
+        b->last = ngx_cpymem_ssz(b->last, "");
+
+        *b->last++ = CR;
+        *b->last++ = LF;
+    }
+
+    /* Output table bottom */
+    b->last = ngx_cpymem_ssz(b->last, t07_list2);
+
+    *pb = b;
+    return NGX_OK;
+}
+
+
+
+static ngx_int_t
+ngx_http_fancyindex_handler(ngx_http_request_t *r)
+{
+    ngx_http_request_t             *sr;
+    ngx_str_t                      *sr_uri;
+    ngx_str_t                       rel_uri;
+    ngx_int_t                       rc;
+    ngx_http_fancyindex_loc_conf_t *alcf;
+    ngx_chain_t                     out[3] = {
+        { NULL, NULL }, { NULL, NULL}, { NULL, NULL }};
+
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        return NGX_DECLINED;
+    }
+
+    /* TODO: Win32 */
+#if defined(nginx_version) \
+    && ((nginx_version < 7066) \
+        || ((nginx_version > 8000) && (nginx_version < 8038)))
+    if (r->zero_in_uri) {
+        return NGX_DECLINED;
+    }
+#endif
+
+    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+        return NGX_DECLINED;
+    }
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_fancyindex_module);
+
+    if (!alcf->enable) {
+        return NGX_DECLINED;
+    }
+
+    if ((rc = make_content_buf(r, &out[0].buf, alcf)) != NGX_OK)
+        return rc;
+
+    out[0].buf->last_in_chain = 1;
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_type_len  = ngx_sizeof_ssz("text/html");
+    r->headers_out.content_type.len  = ngx_sizeof_ssz("text/html");
+    r->headers_out.content_type.data = (u_char *) "text/html";
+
+    rc = ngx_http_send_header(r);
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
+        return rc;
+
+    if (alcf->header.len > 0) {
+        /* URI is configured, make Nginx take care of with a subrequest. */
+        sr_uri = &alcf->header;
+
+        if (*sr_uri->data != '/') {
+            /* Relative path */
+            rel_uri.len  = r->uri.len + alcf->header.len;
+            rel_uri.data = ngx_palloc(r->pool, rel_uri.len);
+            if (rel_uri.data == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+            ngx_memcpy(ngx_cpymem(rel_uri.data, r->uri.data, r->uri.len),
+                    alcf->header.data, alcf->header.len);
+            sr_uri = &rel_uri;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                "http fancyindex: header subrequest \"%V\"", sr_uri);
+
+        rc = ngx_http_subrequest(r, sr_uri, NULL, &sr, NULL, 0);
+        if (rc == NGX_ERROR || rc == NGX_DONE) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                    "http fancyindex: header subrequest for \"%V\" failed", sr_uri);
+            return rc;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                "http fancyindex: header subrequest status = %i",
+                sr->headers_out.status);
+        /* ngx_http_subrequest returns NGX_OK(0), not NGX_HTTP_OK(200) */
+        if (sr->headers_out.status != NGX_OK) {
+            /*
+             * XXX: Should we write a message to the error log just in case
+             * we get something different from a 404?
+             */
+            goto add_builtin_header;
+        }
+    }
+    else {
+add_builtin_header:
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                "http fancyindex: adding built-in header");
+        /* Make space before */
+        out[1].next = out[0].next;
+        out[1].buf  = out[0].buf;
+        /* Chain header buffer */
+        out[0].next = &out[1];
+        out[0].buf  = make_header_buf(r, alcf->css_href);
+    }
+
+    /* If footer is disabled, chain up footer buffer. */
+    if (alcf->footer.len == 0) {
+        ngx_uint_t last  = (alcf->header.len == 0) ? 2 : 1;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                "http fancyindex: adding built-in footer at %i", last);
+
+        out[last-1].next = &out[last];
+        out[last].buf    = make_footer_buf(r);
+
+        out[last-1].buf->last_in_chain = 0;
+        out[last].buf->last_in_chain   = 1;
+        out[last].buf->last_buf        = 1;
+        /* Send everything with a single call :D */
+        return ngx_http_output_filter(r, &out[0]);
+    }
+
+    /*
+     * If we reach here, we were asked to send a custom footer. We need to:
+     * partially send whatever is referenced from out[0] and then send the
+     * footer as a subrequest. If the subrequest fails, we should send the
+     * standard footer as well.
+     */
+    rc = ngx_http_output_filter(r, &out[0]);
+
+    if (rc != NGX_OK && rc != NGX_AGAIN)
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+    /* URI is configured, make Nginx take care of with a subrequest. */
+    sr_uri = &alcf->footer;
+
+    if (*sr_uri->data != '/') {
+        /* Relative path */
+        rel_uri.len  = r->uri.len + alcf->footer.len;
+        rel_uri.data = ngx_palloc(r->pool, rel_uri.len);
+        if (rel_uri.data == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+        ngx_memcpy(ngx_cpymem(rel_uri.data, r->uri.data, r->uri.len),
+                alcf->footer.data, alcf->footer.len);
+        sr_uri = &rel_uri;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+            "http fancyindex: footer subrequest \"%V\"", sr_uri);
+
+    rc = ngx_http_subrequest(r, sr_uri, NULL, &sr, NULL, 0);
+    if (rc == NGX_ERROR || rc == NGX_DONE) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                "http fancyindex: footer subrequest for \"%V\" failed", sr_uri);
+        return rc;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+            "http fancyindex: header subrequest status = %i",
+            sr->headers_out.status);
+
+    /* see above: ngx_http_subrequest resturns NGX_OK (0) not NGX_HTTP_OK (200) */
+    if (sr->headers_out.status != NGX_OK) {
+        /*
+         * XXX: Should we write a message to the error log just in case
+         * we get something different from a 404?
+         */
+        out[0].next = NULL;
+        out[0].buf  = make_footer_buf(r);
+        out[0].buf->last_in_chain = 1;
+        out[0].buf->last_buf = 1;
+        /* Directly send out the builtin footer */
+        return ngx_http_output_filter(r, &out[0]);
+    }
+
+    return (r != r->main) ? rc : ngx_http_send_special(r, NGX_HTTP_LAST);
+}
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_dirs_first(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    /* move the directories to the start */
+    if (first->dir && !second->dir) {
+        return -1;
+    }
+    if (!first->dir && second->dir) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_name_desc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) ngx_strcmp(second->name.data, first->name.data);
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_size_desc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) (second->size - first->size);
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_mtime_desc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) (second->mtime - first->mtime);
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_name_asc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) ngx_strcmp(first->name.data, second->name.data);
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_size_asc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) (first->size - second->size);
+}
+
+
+static ngx_int_t ngx_libc_cdecl
+ngx_http_fancyindex_cmp_entries_mtime_asc(const void *one, const void *two)
+{
+    ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
+    ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
+
+    return (int) (first->mtime - second->mtime);
+}
+
+
+static ngx_int_t
+ngx_http_fancyindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
+{
+    if (ngx_close_dir(dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                      ngx_close_dir_n " \"%V\" failed", name);
+    }
+
+    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static void *
+ngx_http_fancyindex_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_fancyindex_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fancyindex_loc_conf_t));
+    if (conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    /*
+     * Set by ngx_pcalloc:
+     *    conf->header.len       = 0
+     *    conf->header.data      = NULL
+     *    conf->footer.len       = 0
+     *    conf->footer.data      = NULL
+     *    conf->css_href.len     = 0
+     *    conf->css_href.data    = NULL
+     *    conf->time_format.len  = 0
+     *    conf->time_format.data = NULL
+     */
+    conf->enable        = NGX_CONF_UNSET;
+    conf->default_sort  = NGX_CONF_UNSET_UINT;
+    conf->dirs_first    = NGX_CONF_UNSET;
+    conf->localtime     = NGX_CONF_UNSET;
+    conf->name_length   = NGX_CONF_UNSET_UINT;
+    conf->exact_size    = NGX_CONF_UNSET;
+    conf->ignore        = NGX_CONF_UNSET_PTR;
+    conf->hide_symlinks = NGX_CONF_UNSET;
+    conf->show_path     = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_fancyindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_fancyindex_loc_conf_t *prev = parent;
+    ngx_http_fancyindex_loc_conf_t *conf = child;
+
+    (void) cf; /* unused */
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_uint_value(conf->default_sort, prev->default_sort, NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME);
+    ngx_conf_merge_value(conf->dirs_first, prev->dirs_first, 1);
+    ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
+    ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
+    ngx_conf_merge_value(conf->show_path, prev->show_path, 1);
+    ngx_conf_merge_uint_value(conf->name_length, prev->name_length, 50);
+
+    ngx_conf_merge_str_value(conf->header, prev->header, "");
+    ngx_conf_merge_str_value(conf->footer, prev->footer, "");
+    ngx_conf_merge_str_value(conf->css_href, prev->css_href, "");
+    ngx_conf_merge_str_value(conf->time_format, prev->time_format, "%Y-%b-%d %H:%M");
+
+    ngx_conf_merge_ptr_value(conf->ignore, prev->ignore, NULL);
+    ngx_conf_merge_value(conf->hide_symlinks, prev->hide_symlinks, 0);
+
+    /* Just make sure we haven't disabled the show_path directive without providing a custom header */
+    if (conf->show_path == 0 && conf->header.len == 0)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "FancyIndex : cannot set show_path to off without providing a custom header !");
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char*
+ngx_http_fancyindex_ignore(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_fancyindex_loc_conf_t *alcf = conf;
+    ngx_str_t *value;
+
+    (void) cmd; /* unused */
+
+#if (NGX_PCRE)
+    ngx_uint_t          i;
+    ngx_regex_elt_t    *re;
+    ngx_regex_compile_t rc;
+    u_char              errstr[NGX_MAX_CONF_ERRSTR];
+
+    if (alcf->ignore == NGX_CONF_UNSET_PTR) {
+        alcf->ignore = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
+        if (alcf->ignore == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+    rc.err.data = errstr;
+    rc.err.len  = NGX_MAX_CONF_ERRSTR;
+    rc.pool     = cf->pool;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        re = ngx_array_push(alcf->ignore);
+        if (re == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        rc.pattern = value[i];
+        rc.options = NGX_REGEX_CASELESS;
+
+        if (ngx_regex_compile(&rc) != NGX_OK) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+            return NGX_CONF_ERROR;
+        }
+
+        re->name  = value[i].data;
+        re->regex = rc.regex;
+    }
+
+    return NGX_CONF_OK;
+#else /* !NGX_PCRE */
+    ngx_uint_t i;
+    ngx_str_t *str;
+
+    if (alcf->ignore == NGX_CONF_UNSET_PTR) {
+        alcf->ignore = ngx_array_create(cf->pool, 2, sizeof(ngx_str_t));
+        if (alcf->ignore == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        str = ngx_array_push(alcf->ignore);
+        if (str == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        str->data = value[i].data;
+        str->len  = value[i].len;
+    }
+
+    return NGX_CONF_OK;
+#endif /* NGX_PCRE */
+
+}
+
+
+static ngx_int_t
+ngx_http_fancyindex_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_fancyindex_handler;
+
+    return NGX_OK;
+}
+
+/* vim:et:sw=4:ts=4:
+ */
diff --git a/ngx-fancyindex/t/00-build-artifacts.test b/ngx-fancyindex/t/00-build-artifacts.test
new file mode 100644
index 0000000..b9bc1ac
--- /dev/null
+++ b/ngx-fancyindex/t/00-build-artifacts.test
@@ -0,0 +1,22 @@
+#! /bin/bash
+cat <<---
+This test checks that the built Nginx either has the dynamic fancyindex
+module available, or that it's not there (for static builds).
+--
+
+readonly nginx_path="${PREFIX}/sbin/nginx"
+readonly so_path="${PREFIX}/modules/ngx_http_fancyindex_module.so"
+
+if [[ ! -x ${nginx_path} ]] ; then
+	fail "executable binary not found at '%s'\n" "${nginx_path}"
+fi
+
+if ${DYNAMIC} ; then
+	if [[ ! -r ${so_path} ]] ; then
+		fail "module not found at '%s'\n" "${so_path}"
+	fi
+else
+	if [[ -r ${so_path} ]] ; then
+		fail "module should not exist at '%s'\n" "${so_path}"
+	fi
+fi
diff --git a/ngx-fancyindex/t/01-smoke-hasindex.test b/ngx-fancyindex/t/01-smoke-hasindex.test
new file mode 100644
index 0000000..19706a4
--- /dev/null
+++ b/ngx-fancyindex/t/01-smoke-hasindex.test
@@ -0,0 +1,7 @@
+#! /bin/bash
+cat <<---
+This test fetches the root directory served by Nginx, which has no index file,
+and checks that the output contains something that resembles a directory index.
+--
+nginx_start
+grep 'Index of' <( fetch )
diff --git a/ngx-fancyindex/t/02-smoke-indexisfancy.test b/ngx-fancyindex/t/02-smoke-indexisfancy.test
new file mode 100644
index 0000000..10cc403
--- /dev/null
+++ b/ngx-fancyindex/t/02-smoke-indexisfancy.test
@@ -0,0 +1,11 @@
+#! /bin/bash
+cat <<---
+This test fetches the root directory served by Nginx, which has no index file,
+and checks that the output contains something that resembles the output from
+the fancyindex module.
+--
+nginx_start
+content=$(fetch --with-headers)
+grep 'Index of /' <<< "${content}"  # It is an index
+grep '\'  <<< "${content}"  # It contains a table
+grep '^  Content-Type:[[:space:]]*text/html' <<< "${content}"
diff --git a/ngx-fancyindex/t/build-and-run b/ngx-fancyindex/t/build-and-run
new file mode 100755
index 0000000..68584b7
--- /dev/null
+++ b/ngx-fancyindex/t/build-and-run
@@ -0,0 +1,25 @@
+#! /bin/bash
+set -e
+
+if [[ $# -lt 1 || $# -gt 2 ]] ; then
+	echo "Usage: $0  [1]" 1>&2
+	exit 1
+fi
+
+readonly NGINX=$1
+
+if [[ $2 -eq 1 ]] ; then
+	readonly DYNAMIC=$2
+fi
+
+cd "$(dirname "$0")/.."
+wget -O - http://nginx.org/download/nginx-${NGINX}.tar.gz | tar -xzf -
+rm -rf prefix/
+cd nginx-${NGINX}
+./configure \
+	--add-${DYNAMIC:+dynamic-}module=.. \
+	--with-http_addition_module \
+	--prefix="$(pwd)/../prefix"
+make install
+cd ..
+exec ./t/run prefix ${DYNAMIC}
diff --git a/ngx-fancyindex/t/has-index.test b/ngx-fancyindex/t/has-index.test
new file mode 100644
index 0000000..cf34207
--- /dev/null
+++ b/ngx-fancyindex/t/has-index.test
@@ -0,0 +1,7 @@
+#! /bin/bash
+cat <<---
+This test ensures that the "index.html" is returned instead of a directory
+listing when fetching a directory which contains an index file.
+--
+nginx_start
+diff -u "${TESTDIR}/has-index/index.html" <( fetch /has-index/ ) 1>&2
diff --git a/ngx-fancyindex/t/has-index/index.html b/ngx-fancyindex/t/has-index/index.html
new file mode 100644
index 0000000..419ae86
--- /dev/null
+++ b/ngx-fancyindex/t/has-index/index.html
@@ -0,0 +1,10 @@
+
+
+	
+        
+		Index file test
+	
+	
+		This is index.html.
+	
+
diff --git a/ngx-fancyindex/t/nginx.conf b/ngx-fancyindex/t/nginx.conf
new file mode 100644
index 0000000..2b99a3d
--- /dev/null
+++ b/ngx-fancyindex/t/nginx.conf
@@ -0,0 +1,25 @@
+worker_processes  1;
+
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+    sendfile        on;
+    keepalive_timeout  65;
+    server {
+        listen       80;
+        server_name  localhost;
+        location / {
+            root   html;
+            index  index.html index.htm;
+        }
+        error_page   500 502 503 504  /50x.html;
+        location = /50x.html {
+            root   html;
+        }
+    }
+}
diff --git a/ngx-fancyindex/t/preamble b/ngx-fancyindex/t/preamble
new file mode 100644
index 0000000..a571381
--- /dev/null
+++ b/ngx-fancyindex/t/preamble
@@ -0,0 +1,84 @@
+#! /bin/bash
+#
+# preamble
+# Copyright (C) 2016 Adrian Perez 
+#
+# Distributed under terms of the MIT license.
+#
+
+function nginx_conf_generate () {
+	if ${DYNAMIC} ; then
+		echo 'load_module modules/ngx_http_fancyindex_module.so;'
+	fi
+	cat <<-EOF
+	worker_processes 1;
+	events { worker_connections 1024; }
+	http {
+		include mime.types;
+		default_type application/octet-stream;
+		sendfile on;
+		keepalive_timeout 65;
+		server {
+			server_name localhost;
+			listen 127.0.0.1:8888;
+			root ${TESTDIR};
+			error_page 500 502 503 504 /50x.html;
+			location = /50x.html { root html; }
+			location / {
+				index index.html;
+				fancyindex on;
+				$*
+			}
+		}
+	}
+	EOF
+}
+
+readonly NGINX_CONF="${PREFIX}/conf/nginx.conf"
+readonly NGINX_PID="${PREFIX}/logs/nginx.pid"
+rm -f "${NGINX_CONF}" "${NGINX_PID}"
+mkdir -p "${PREFIX}/logs"
+
+function nginx () {
+	env - PATH="${PATH}" "${PREFIX}/sbin/nginx" "$@"
+}
+
+function nginx_conf () {
+	nginx_conf_generate "$@" > "${NGINX_CONF}"
+}
+
+function nginx_is_running () {
+	[[ -r ${NGINX_PID} ]] && kill -0 $(< "${NGINX_PID}")
+}
+
+function nginx_stop () {
+	if nginx_is_running ; then nginx -s stop ; fi
+	rm -f "${NGINX_PID}"
+}
+trap nginx_stop EXIT
+
+function nginx_start () {
+	if [[ $# -gt 0 || ! -r ${NGINX_CONF} ]] ; then nginx_conf "$@" ; fi
+	nginx_stop  # Ensure that it is not running.
+	nginx
+}
+
+function fetch () {
+	local -a opts=( -q )
+	if [[ $1 = --with-headers ]] ; then
+		opts+=( -S )
+		shift
+	fi
+	wget "${opts[@]}" -O- "http://localhost:8888${1:-/}" 2>&1
+}
+
+function fail () {
+	printf "(FF) "
+	printf "$@"
+	exit 1
+} 1>&2
+
+function warn () {
+	printf "(WW)"
+	printf "$@"
+} 1>&2
diff --git a/ngx-fancyindex/t/run b/ngx-fancyindex/t/run
new file mode 100755
index 0000000..2001bff
--- /dev/null
+++ b/ngx-fancyindex/t/run
@@ -0,0 +1,67 @@
+#! /bin/bash
+set -e
+
+if [[ $# -lt 1 || $# -gt 2 ]] ; then
+	echo "Usage: $0  [1]" 1>&2
+	exit 1
+fi
+
+# Obtain the absolute path to the tests directory
+pushd "$(dirname "$0")" &> /dev/null
+readonly T=$(pwd)
+popd &> /dev/null
+export T
+
+# Same for the nginx prefix directory
+pushd "$1" &> /dev/null
+readonly prefix=$(pwd)
+popd &> /dev/null
+
+dynamic=false
+if [[ $# -gt 1 && $2 -eq 1 ]] ; then
+	dynamic=true
+fi
+readonly dynamic
+
+declare -a t_pass=( )
+declare -a t_fail=( )
+
+for t in "$T"/*.test ; do
+	name="t/${t##*/}"
+	name=${name%.test}
+	printf "${name} ... "
+	errfile="${name}.err"
+	outfile="${name}.out"
+	shfile="${name}.sh"
+	cat > "${shfile}" <<-EOF
+	readonly DYNAMIC=${dynamic}
+	readonly TESTDIR='$T'
+	readonly PREFIX='${prefix}'
+	$(< "$T/preamble")
+	$(< "$t")
+	EOF
+	if bash -e "${shfile}" > "${outfile}" 2> "${errfile}" ; then
+		t_pass+=( "${name}" )
+		printf 'passed\n'
+	else
+		t_fail+=( "${name}" )
+		printf 'failed\n'
+	fi
+done
+
+for name in "${t_fail[@]}" ; do
+	echo
+	printf '=== %s.out\n' "${name}"
+	cat "${name}.out"
+	echo
+	printf '=== %s.err\n' "${name}"
+	cat "${name}.err"
+	echo
+done
+
+printf '=== passed/failed/total: %d/%d/%d\n' \
+	${#t_pass[@]} ${#t_fail[@]} $(( ${#t_pass[@]} + ${#t_fail[@]} ))
+
+if [[ ${#t_fail[@]} -gt 0 ]] ; then
+	exit 1
+fi
diff --git a/ngx-fancyindex/template.awk b/ngx-fancyindex/template.awk
new file mode 100755
index 0000000..f9ec4a6
--- /dev/null
+++ b/ngx-fancyindex/template.awk
@@ -0,0 +1,52 @@
+#! /usr/bin/awk -f
+#
+# Copyright © Adrian Perez 
+#
+# Converts an HTML template into a C header suitable for inclusion.
+# Take a look at the HACKING.rst file to know how to use it :-)
+#
+# This code is placed in the public domain.
+
+BEGIN {
+	varname = 0;
+	print "/* Automagically generated, do not edit! */"
+	vars_count = 0;
+}
+
+/^$/ {
+	if (varname) print ";";
+	if ($3 == "NONE") {
+		varname = 0;
+		next;
+	}
+	varname = $3;
+	vars[vars_count++] = varname;
+	print "static const u_char " varname "[] = \"\"";
+	next;
+}
+
+/^$/ {
+	if (!varname) next;
+	print "\"\\n\"";
+	next;
+}
+
+{
+	if (!varname) next;
+	# Order matters
+	gsub(/[\t\v\n\r\f]+/, "");
+	gsub(/\\/, "\\\\");
+	gsub(/"/, "\\\"");
+	print "\"" $0 "\""
+}
+
+
+END {
+	if (varname) print ";";
+	print "#define NFI_TEMPLATE_SIZE (0 \\";
+	for (var in vars) {
+		print "\t+ nfi_sizeof_ssz(" vars[var] ") \\";
+	}
+	print "\t)"
+}
+
diff --git a/ngx-fancyindex/template.h b/ngx-fancyindex/template.h
new file mode 100644
index 0000000..4881ac7
--- /dev/null
+++ b/ngx-fancyindex/template.h
@@ -0,0 +1,103 @@
+/* Automagically generated, do not edit! */
+static const u_char t01_head1[] = ""
+""
+"\n"
+""
+"\n"
+""
+""
+""
+""
+"\n"
+;
+static const u_char t02_head2[] = ""
+"\n"
+"Index of "
+;
+static const u_char t03_head3[] = ""
+""
+"\n"
+""
+;
+static const u_char t04_body1[] = ""
+""
+"

Index of " +; +static const u_char t05_body2[] = "" +"

" +"\n" +; +static const u_char t06_list1[] = "" +"
last = ngx_cpymem(b->last, + sort_url_args, + ngx_sizeof_ssz("?C=N&O=A")); + } + b->last = ngx_cpymem_ssz(b->last, + "\">Parent directory/--
last, + entry[i].name.data, + entry[i].name.len); + + b->last += entry[i].name.len + entry[i].escape; + + } else { + b->last = ngx_cpymem_str(b->last, entry[i].name); + } + + if (entry[i].dir) { + *b->last++ = '/'; + if (*sort_url_args) { + b->last = ngx_cpymem(b->last, + sort_url_args, + ngx_sizeof_ssz("?C=x&O=y")); + } + } + + *b->last++ = '"'; + b->last = ngx_cpymem_ssz(b->last, " title=\""); + b->last = ngx_cpymem_str(b->last, entry[i].name); + *b->last++ = '"'; + *b->last++ = '>'; + + len = entry[i].utf_len; + + if (entry[i].name.len - len) { + if (len > alcf->name_length) { + copy = alcf->name_length - 3 + 1; + } else { + copy = alcf->name_length + 1; + } + + b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data, + copy, entry[i].name.len); + last = b->last; + + } else { + b->last = ngx_cpystrn(b->last, entry[i].name.data, + alcf->name_length + 1); + last = b->last - 3; + } + + if (len > alcf->name_length) { + b->last = ngx_cpymem_ssz(last, "..>"); + + } else { + if (entry[i].dir && alcf->name_length - len > 0) { + *b->last++ = '/'; + len++; + } + + b->last = ngx_cpymem_ssz(b->last, ""); + } + + if (alcf->exact_size) { + if (entry[i].dir) { + *b->last++ = '-'; + } else { + b->last = ngx_sprintf(b->last, "%19O", entry[i].size); + } + + } else { + if (entry[i].dir) { + *b->last++ = '-'; + } else { + length = entry[i].size; + + if (length > 1024 * 1024 * 1024 - 1) { + size = (ngx_int_t) (length / (1024 * 1024 * 1024)); + if ((length % (1024 * 1024 * 1024)) + > (1024 * 1024 * 1024 / 2 - 1)) + { + size++; + } + scale = 'G'; + + } else if (length > 1024 * 1024 - 1) { + size = (ngx_int_t) (length / (1024 * 1024)); + if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) { + size++; + } + scale = 'M'; + + } else if (length > 9999) { + size = (ngx_int_t) (length / 1024); + if (length % 1024 > 511) { + size++; + } + scale = 'K'; + + } else { + size = (ngx_int_t) length; + scale = '\0'; + } + + if (scale) { + b->last = ngx_sprintf(b->last, "%6i%c", size, scale); + + } else { + b->last = ngx_sprintf(b->last, " %6i", size); + } + } + } + + ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm); + b->last = ngx_cpymem_ssz(b->last, ""); + b->last = ngx_fancyindex_timefmt(b->last, &alcf->time_format, &tm); + b->last = ngx_cpymem_ssz(b->last, "
" +"\n" +"" +"" +"" +"" +"" +"\n" +"" +"" +"" +"" +"" +"" +"" +"\n" +"" +; +static const u_char t_parentdir_entry[] = "" +"" +"" +"" +"" +"" +"\n" +; +static const u_char t07_list2[] = "" +"" +"
File Name  ↓ File Size  ↓ Date  ↓ 
Parent directory/--
" +; +static const u_char t08_foot1[] = "" +"" +"" +; +#define NFI_TEMPLATE_SIZE (0 \ + + nfi_sizeof_ssz(t01_head1) \ + + nfi_sizeof_ssz(t02_head2) \ + + nfi_sizeof_ssz(t03_head3) \ + + nfi_sizeof_ssz(t04_body1) \ + + nfi_sizeof_ssz(t05_body2) \ + + nfi_sizeof_ssz(t06_list1) \ + + nfi_sizeof_ssz(t_parentdir_entry) \ + + nfi_sizeof_ssz(t07_list2) \ + + nfi_sizeof_ssz(t08_foot1) \ + ) diff --git a/ngx-fancyindex/template.html b/ngx-fancyindex/template.html new file mode 100644 index 0000000..bc41292 --- /dev/null +++ b/ngx-fancyindex/template.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + Index of +<!-- var NONE --> + /path/to/somewhere +<!-- var t03_head3 --> + + + + + +

Index of + + /path/to/somewhere + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
File Name  ↓ File Size  ↓ Date  ↓ 
Parent directory/--
test file 1123kBdate
test file 2321MBdate
test file 3666date
+ + +