├── t ├── case-sensitivity │ ├── Bob │ └── alice ├── show_dotfiles │ └── .okay ├── child-directory │ └── empty-file.txt ├── has-index │ └── index.html ├── 01-smoke-hasindex.test ├── has-index.test ├── bug78-case-sensitive.test ├── bug78-case-insensitive.test ├── 03-exact_size_off.test ├── bug107-filesystem-root-404.test ├── 12-local-footer-nested.test ├── 02-smoke-indexisfancy.test ├── 08-local-footer.test ├── 09-local-header.test ├── 07-show_dotfiles.test ├── bug61-empty-file-segfault.test ├── nginx.conf ├── 00-build-artifacts.test ├── bug95-square-brackets.test ├── 06-hide_parent.test ├── 04-hasindex-html.test ├── build-and-run ├── 10-local-headerfooter.test ├── bug157-saturday-in-long-weekdays.test ├── 05-sort-by-size.test ├── 11-local-footer-nested.test ├── 07-directory-first.test ├── run ├── preamble └── get-pup ├── .gitattributes ├── config ├── template.awk ├── LICENSE ├── HACKING.md ├── .github └── workflows │ └── ci.yml ├── template.html ├── template.h ├── CHANGELOG.md ├── README.rst └── ngx_http_fancyindex_module.c /t/case-sensitivity/Bob: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/show_dotfiles/.okay: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/case-sensitivity/alice: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/child-directory/empty-file.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.gitignore export-ignore 2 | /.travis.yml export-ignore 3 | /make-dist export-ignore 4 | t/* text eol=lf 5 | -------------------------------------------------------------------------------- /t/has-index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index file test 6 | 7 | 8 | This is index.html. 9 | 10 | 11 | -------------------------------------------------------------------------------- /t/01-smoke-hasindex.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test fetches the root directory served by Nginx, which has no index file, 4 | and checks that the output contains something that resembles a directory index. 5 | -- 6 | nginx_start 7 | grep 'Index of' <( fetch ) 8 | -------------------------------------------------------------------------------- /t/has-index.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test ensures that the "index.html" is returned instead of a directory 4 | listing when fetching a directory which contains an index file. 5 | -- 6 | nginx_start 7 | diff -u "${TESTDIR}/has-index/index.html" <( fetch /has-index/ ) 1>&2 8 | -------------------------------------------------------------------------------- /t/bug78-case-sensitive.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that case-sensitive sorting works. 4 | -- 5 | 6 | nginx_start 'fancyindex_case_sensitive on;' 7 | content=$(fetch /case-sensitivity/) 8 | grep -A 999 '\' <<< "${content}" | grep '\' # alice is after Bob 9 | -------------------------------------------------------------------------------- /t/bug78-case-insensitive.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that case-insensitive sorting works. 4 | -- 5 | 6 | nginx_start 'fancyindex_case_sensitive off;' 7 | content=$(fetch /case-sensitivity/) 8 | grep -A 999 '\' <<< "${content}" | grep '\' # Bob is after alice 9 | -------------------------------------------------------------------------------- /t/03-exact_size_off.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks if the output from using "fancyindex_exact_size off" 4 | looks sane. 5 | -- 6 | nginx_start 'fancyindex_exact_size off;' 7 | content=$(fetch) 8 | grep -e '[1-9]\.[0-9] KiB' <<< "${content}" 9 | grep -E '[0-9]+ B' <<< "${content}" 10 | -------------------------------------------------------------------------------- /t/bug107-filesystem-root-404.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | Bug #107: 404 is returned when indexing filesystem root 4 | https://github.com/aperezdc/ngx-fancyindex/issues/107 5 | -- 6 | nginx_start 'root /;' 7 | content=$(fetch) 8 | grep 'Index of /' <<< "${content}" # It is an index 9 | grep '' <<< "${content}" # It contains a table 10 | -------------------------------------------------------------------------------- /t/12-local-footer-nested.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that the configuration file is properly parsed if there 4 | is only one parameter passed to the fancyindex_header and fancyindex_footer 5 | configuration directives. 6 | -- 7 | 8 | nginx_start 'fancyindex_header "/header"; 9 | fancyindex_footer "/footer";' 10 | 11 | nginx_is_running || fail 'Nginx died' 12 | -------------------------------------------------------------------------------- /t/02-smoke-indexisfancy.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test fetches the root directory served by Nginx, which has no index file, 4 | and checks that the output contains something that resembles the output from 5 | the fancyindex module. 6 | -- 7 | nginx_start 8 | content=$(fetch --with-headers) 9 | grep 'Index of /' <<< "${content}" # It is an index 10 | grep '' <<< "${content}" # It contains a table 11 | grep '^ Content-Type:[[:space:]]*text/html' <<< "${content}" 12 | -------------------------------------------------------------------------------- /t/08-local-footer.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that a local footer can be included with 4 | "fancyindex_header ... local". 5 | -- 6 | use pup 7 | 8 | cat > "${TESTDIR}/footer" <yes 10 | EOF 11 | 12 | nginx_start "fancyindex_footer \"${TESTDIR}/footer\" local;" 13 | 14 | T=$(fetch / | pup -p body 'div#customfooter' text{}) 15 | [[ $T == yes ]] || fail 'Custom header missing' 16 | 17 | nginx_is_running || fail 'Nginx died' 18 | -------------------------------------------------------------------------------- /t/09-local-header.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that a local header can be included with 4 | "fancyindex_header ... local". 5 | -- 6 | use pup 7 | 8 | cat > "${TESTDIR}/header" <yes 10 | EOF 11 | 12 | nginx_start "fancyindex_header \"${TESTDIR}/header\" local;" 13 | 14 | T=$(fetch / | pup -p body 'div#customheader' text{}) 15 | [[ $T == yes ]] || fail 'Custom header missing' 16 | 17 | nginx_is_running || fail 'Nginx died' 18 | -------------------------------------------------------------------------------- /t/07-show_dotfiles.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks the option to show dotfiles. 4 | -- 5 | # Turn it on. 6 | nginx_start 'fancyindex_show_dotfiles on;' 7 | on_content=$(fetch /show_dotfiles/) 8 | nginx_stop 9 | if [ $(grep '.okay' <<< "${on_content}") -ne 0 ] ; then 10 | exit 1 11 | fi 12 | 13 | # Turn it off. 14 | nginx_start 15 | off_content=$(fetch /show_dotfiles/) 16 | nginx_stop 17 | if [ $(grep '.okay' <<< "${on_content}") -eq 0] ; then 18 | exit 1 19 | fi 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /t/bug61-empty-file-segfault.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | Bug #61: Listing a directory with an empty file crashes Nginx 4 | https://github.com/aperezdc/ngx-fancyindex/issues/61 5 | -- 6 | 7 | # Prepare an empty directory with an empty file 8 | mkdir -p "${TESTDIR}/bug61" 9 | touch "${TESTDIR}/bug61/bug61.txt" 10 | 11 | nginx_start 'fancyindex_exact_size off;' 12 | content=$(fetch /bug61/) 13 | test -n "${content}" || fail "Empty response" 14 | echo "Response:" 15 | echo "${content}" 16 | nginx_is_running || fail "Nginx died" 17 | -------------------------------------------------------------------------------- /t/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | sendfile on; 12 | keepalive_timeout 65; 13 | server { 14 | listen 80; 15 | server_name localhost; 16 | location / { 17 | root html; 18 | index index.html index.htm; 19 | } 20 | error_page 500 502 503 504 /50x.html; 21 | location = /50x.html { 22 | root html; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /t/00-build-artifacts.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that the built Nginx either has the dynamic fancyindex 4 | module available, or that it's not there (for static builds). 5 | -- 6 | 7 | readonly nginx_path="${PREFIX}/sbin/nginx" 8 | readonly so_path="${PREFIX}/modules/ngx_http_fancyindex_module.so" 9 | 10 | if [[ ! -x ${nginx_path} ]] ; then 11 | fail "executable binary not found at '%s'\n" "${nginx_path}" 12 | fi 13 | 14 | if ${DYNAMIC} ; then 15 | if [[ ! -r ${so_path} ]] ; then 16 | fail "module not found at '%s'\n" "${so_path}" 17 | fi 18 | else 19 | if [[ -r ${so_path} ]] ; then 20 | fail "module should not exist at '%s'\n" "${so_path}" 21 | fi 22 | fi 23 | -------------------------------------------------------------------------------- /t/bug95-square-brackets.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | Bug #95: FancyIndex does not encode square brackets 4 | https://github.com/aperezdc/ngx-fancyindex/issues/95 5 | -- 6 | use pup 7 | 8 | # Prepare a directory with a file that contains square brackets in the name. 9 | mkdir -p "${TESTDIR}/bug95" 10 | touch "${TESTDIR}"/bug95/'bug[95].txt' 11 | 12 | nginx_start 13 | content=$(fetch /bug95/) 14 | test -n "${content}" || fail 'Empty response' 15 | 16 | expected_href='bug%5B95%5D.txt' 17 | obtained_href=$(pup -p body tbody 'tr:nth-child(2)' a 'attr{href}' <<< "${content}") 18 | test "${expected_href}" = "${obtained_href}" || \ 19 | fail 'Expected: %s - Obtained: %s' "${expected_href}" "${obtained_href}" 20 | -------------------------------------------------------------------------------- /t/06-hide_parent.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks the output using "fancyindex_hide_parent_dir on". 4 | -- 5 | use pup 6 | nginx_start 'fancyindex_hide_parent_dir on;' 7 | 8 | content=$( fetch /child-directory/ ) 9 | 10 | # Check page title 11 | [[ $(pup -p title text{} <<< "${content}") = "Index of /child-directory/" ]] 12 | 13 | # Check table headers 14 | [[ $(pup -n body table tbody tr:first-child td <<< "${content}") -eq 3 ]] 15 | { 16 | read -r name_label 17 | read -r size_label 18 | read -r date_label 19 | } < <( pup -p body table tbody tr:first-child td text{} <<< "${content}" ) 20 | [[ ${name_label} != Parent\ Directory/ ]] 21 | [[ ${name_label} = empty-file.txt ]] 22 | [[ ${size_label} != - ]] 23 | [[ ${date_label} != - ]] 24 | -------------------------------------------------------------------------------- /t/04-hasindex-html.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test fetches the root directory served by Nginx, which has no index 4 | file, and checks that the output contains a few HTML elements known to 5 | exist in a directory index. 6 | -- 7 | use pup 8 | nginx_start 9 | 10 | content=$( fetch ) 11 | 12 | # Check page title 13 | [[ $(pup -p title text{} <<< "${content}") = 'Index of /' ]] 14 | 15 | # Check table headers 16 | [[ $(pup -n body table thead th a:first-child <<< "${content}") -eq 3 ]] 17 | { 18 | read -r name_label 19 | read -r size_label 20 | read -r date_label 21 | } < <( pup -p body table thead th a:first-child text{} <<< "${content}" ) 22 | [[ ${name_label} = File\ Name ]] 23 | [[ ${size_label} = File\ Size ]] 24 | [[ ${date_label} = Date ]] 25 | -------------------------------------------------------------------------------- /t/build-and-run: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | if [[ $# -lt 1 || $# -gt 2 ]] ; then 5 | echo "Usage: $0 [1]" 1>&2 6 | exit 1 7 | fi 8 | 9 | readonly NGINX=$1 10 | 11 | if [[ $2 -eq 1 ]] ; then 12 | readonly DYNAMIC=$2 13 | fi 14 | 15 | case $(uname -s) in 16 | Darwin) 17 | JOBS=$(sysctl -n hw.activecpu) 18 | ;; 19 | *) 20 | JOBS=1 21 | ;; 22 | esac 23 | 24 | cd "$(dirname "$0")/.." 25 | wget -O - http://nginx.org/download/nginx-${NGINX}.tar.gz | tar -xzf - 26 | rm -rf prefix/ 27 | cd nginx-${NGINX} 28 | ./configure \ 29 | --add-${DYNAMIC:+dynamic-}module=.. \ 30 | --with-http_addition_module \ 31 | --without-http_rewrite_module \ 32 | --prefix="$(pwd)/../prefix" 33 | make -j"$JOBS" 34 | make install 35 | cd .. 36 | exec ./t/run prefix ${DYNAMIC} 37 | -------------------------------------------------------------------------------- /t/10-local-headerfooter.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that both a local header and footer can be included with 4 | "fancyindex_{header,footer} ... local". 5 | -- 6 | use pup 7 | 8 | cat > "${TESTDIR}/header" <yes 10 | EOF 11 | cat > "${TESTDIR}/footer" <yes 13 | EOF 14 | 15 | nginx_start "fancyindex_header \"${TESTDIR}/header\" local; 16 | fancyindex_footer \"${TESTDIR}/footer\" local;" 17 | 18 | P=$(fetch /) 19 | 20 | H=$(pup -p body 'div#customheader' text{} <<< "$P") 21 | [[ $H == yes ]] || fail 'Custom header missing' 22 | 23 | F=$(pup -p body 'div#customfooter' text{} <<< "$P") 24 | [[ $F == yes ]] || fail 'Custom footer missing' 25 | 26 | nginx_is_running || fail 'Nginx died' 27 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | # vim:ft=sh: 2 | ngx_addon_name=ngx_http_fancyindex_module 3 | 4 | if [ "$ngx_module_link" = DYNAMIC ] ; then 5 | ngx_module_type=HTTP 6 | ngx_module_name=ngx_http_fancyindex_module 7 | ngx_module_srcs="$ngx_addon_dir/ngx_http_fancyindex_module.c" 8 | ngx_module_deps="$ngx_addon_dir/template.h" 9 | ngx_module_order="$ngx_module_name ngx_http_autoindex_module" 10 | . auto/module 11 | else 12 | # XXX: Insert fancyindex module *after* index module! 13 | # 14 | HTTP_MODULES=`echo "${HTTP_MODULES}" | sed -e \ 15 | 's/ngx_http_index_module/ngx_http_fancyindex_module ngx_http_index_module/'` 16 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fancyindex_module.c" 17 | if [ $HTTP_ADDITION != YES ] ; then 18 | echo " - The 'addition' filter is needed for fancyindex_{header,footer}, but it was disabled" 19 | fi 20 | fi 21 | -------------------------------------------------------------------------------- /t/bug157-saturday-in-long-weekdays.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | Check whether the Saturday long day name is available. 4 | https://github.com/aperezdc/ngx-fancyindex/issues/157 5 | -- 6 | use pup 7 | nginx_start 'fancyindex_time_format "%A"; fancyindex_default_sort date;' 8 | 9 | mkdir -p "${TESTDIR}/weekdays" 10 | for (( i=2 ; i <= 8 ; i++ )) ; do 11 | TZ=UTC touch -d "2023-01-0${i}T06:00:00" "${TESTDIR}/weekdays/day$i.txt" 12 | done 13 | ls "${TESTDIR}/weekdays" 14 | content=$(fetch /weekdays/) 15 | 16 | # We need row+1 because the first one is the table header. 17 | dayname=$(pup -p body table tbody \ 18 | 'tr:nth-child(7)' 'td:nth-child(3)' 'text{}' \ 19 | <<< "$content") 20 | [[ $dayname = Saturday ]] || fail 'Sixth day is not Saturday' 21 | 22 | dayname=$(pup -p body table tbody \ 23 | 'tr:nth-child(8)' 'td:nth-child(3)' 'text{}' \ 24 | <<< "$content") 25 | [[ $dayname = Sunday ]] || fail 'Seventh day is not Sunday' 26 | 27 | nginx_is_running || fail 'Nginx died' 28 | -------------------------------------------------------------------------------- /t/05-sort-by-size.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test validates that the sorting by file size works. 4 | -- 5 | use pup 6 | nginx_start 7 | 8 | # Ascending sort. 9 | previous='' 10 | while read -r size ; do 11 | if [[ ${size} = - ]] ; then 12 | continue 13 | fi 14 | if [[ -z ${previous} ]] ; then 15 | previous=${size} 16 | continue 17 | fi 18 | [[ ${previous} -le ${size} ]] || fail \ 19 | 'Size %d should be smaller than %d\n' "${previous}" "${size}" 20 | done < <( fetch '/?C=S&O=A' \ 21 | | pup -p body table tbody 'td:nth-child(2)' text{} ) 22 | 23 | # Descending sort. 24 | previous='' 25 | while read -r size ; do 26 | if [[ ${size} = - ]] ; then 27 | continue 28 | fi 29 | if [[ -z ${previous} ]] ; then 30 | previous=${size} 31 | continue 32 | fi 33 | [[ ${previous} -ge ${size} ]] || fail \ 34 | 'Size %d should be greater than %d\n' "${previous}" "${size}" 35 | done < <( fetch '/?C=S&O=D' \ 36 | | pup -p body table tbody 'td:nth-child(2)' text{} ) 37 | -------------------------------------------------------------------------------- /t/11-local-footer-nested.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks that local footers are correctly included in the presence of 4 | directives in nested locations: 5 | 6 | fancyindex_footer local; 7 | location /sub { 8 | fancyindex_footer local; 9 | } 10 | 11 | -- 12 | use pup 13 | 14 | echo '
yes
' > "${TESTDIR}/top-footer" 15 | echo '
yes
' > "${TESTDIR}/sub-footer" 16 | 17 | nginx_start "fancyindex_footer \"${TESTDIR}/top-footer\" local; 18 | location /child-directory { 19 | fancyindex_footer \"${TESTDIR}/sub-footer\" local; 20 | }" 21 | 22 | T=$(fetch /) 23 | echo "$T" > "$TESTDIR/top.html" 24 | [[ $(pup -p body 'div#topfooter' text{} <<< "$T") = yes ]] || fail 'Custom header missing at /' 25 | [[ -z $(pup -p body 'div#subfooter' text{} <<< "$T") ]] || fail 'Wrong header at /' 26 | 27 | T=$(fetch /child-directory/) 28 | [[ $(pup -p body 'div#subfooter' text{} <<< "$T") = yes ]] || fail 'Custom header missing at /sub/' 29 | [[ -z $(pup -p body 'div#topfooter' text{} <<< "$T") ]] || fail 'Wrong header at /sub/' 30 | 31 | nginx_is_running || fail 'Nginx died' 32 | -------------------------------------------------------------------------------- /template.awk: -------------------------------------------------------------------------------- 1 | #! /usr/bin/awk -f 2 | # 3 | # Copyright © Adrian Perez 4 | # 5 | # Converts an HTML template into a C header suitable for inclusion. 6 | # Take a look at the HACKING.md file to know how to use it :-) 7 | # 8 | # This code is placed in the public domain. 9 | 10 | BEGIN { 11 | varname = 0; 12 | print "/* Automagically generated, do not edit! */" 13 | vars_count = 0; 14 | } 15 | 16 | /^$/ { 17 | if (varname) print ";"; 18 | if ($3 == "NONE") { 19 | varname = 0; 20 | next; 21 | } 22 | varname = $3; 23 | vars[vars_count++] = varname; 24 | print "static const u_char " varname "[] = \"\""; 25 | next; 26 | } 27 | 28 | /^$/ { 29 | if (!varname) next; 30 | print "\"\\n\""; 31 | next; 32 | } 33 | 34 | { 35 | if (!varname) next; 36 | # Order matters 37 | gsub(/[\t\v\n\r\f]+/, ""); 38 | gsub(/\\/, "\\\\"); 39 | gsub(/"/, "\\\""); 40 | print "\"" $0 "\"" 41 | } 42 | 43 | 44 | END { 45 | if (varname) print ";"; 46 | print "#define NFI_TEMPLATE_SIZE (0 \\"; 47 | for (var in vars) { 48 | print "\t+ nfi_sizeof_ssz(" vars[var] ") \\"; 49 | } 50 | print "\t)" 51 | } 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions 3 | are met: 4 | 1. Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright 7 | notice, this list of conditions and the following disclaimer in the 8 | documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 11 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 14 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 15 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 16 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 18 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 19 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 20 | SUCH DAMAGE. 21 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Fancy Index module Hacking HOW-TO 2 | 3 | ## How to modify the template 4 | 5 | The template is in the `template.html` file. Note that comment markers are 6 | used to control how the `template.awk` Awk script generates the C header 7 | which gets ultimately included in the compiled object code. Comment markers 8 | have the `` format. Here `identifier` must be 9 | a valid C identifier. All the text following the marker until the next 10 | marker will be flattened into a C string. 11 | 12 | If the identifier is `NONE` (capitalized) the text from that marker up to 13 | the next marker will be discarded. 14 | 15 | 16 | ## Regenerating the C header 17 | 18 | You will need Awk. I hope any decent implementation will do, but the GNU one 19 | is known to work flawlessly. Just do: 20 | 21 | $ awk -f template.awk template.html > template.h 22 | 23 | If your copy of `awk` is not the GNU implementation, you will need to 24 | install it and use `gawk` instead in the command line above. 25 | 26 | This includes macOS where the current built-in `awk` (currently version 27 | 20070501 at time of testing on 10.13.6) doesn't apply correctly and causes 28 | characters to be omitted from the output. `gawk` can be installed with a 29 | package manager such as [Homebrew](https://brew.sh) or 30 | [MacPorts](https://ports.macports.org/port/gawk). 31 | -------------------------------------------------------------------------------- /t/07-directory-first.test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cat <<--- 3 | This test checks the output using "fancyindex_directories_first on". 4 | -- 5 | use pup 6 | 7 | for d in "008d" "000d" "004d" ; do 8 | mkdir -p "${TESTDIR}/dir_first/${d}" 9 | done 10 | for f in "005f" "001f" "003f"; do 11 | touch "${TESTDIR}/dir_first/${f}" 12 | done 13 | for d in "006d" "002d" ; do 14 | mkdir -p "${TESTDIR}/dir_first/${d}" 15 | done 16 | 17 | nginx_start 'fancyindex_directories_first on;' 18 | previous='' 19 | cur_type='' 20 | while read -r name ; do 21 | case "$name" in 22 | *Parent*) 23 | ;; 24 | *d*) 25 | echo "dir $name" 26 | [[ "$cur_type" = f ]] && fail 'Directories should come before files' 27 | cur_type=d 28 | if [[ -z ${previous} ]] ; then 29 | previous=${name} 30 | else 31 | [[ ${previous} < ${name} ]] || fail \ 32 | 'Name %s should come before %s\n' "${previous}" "${name}" 33 | fi 34 | ;; 35 | *f*) 36 | echo "file $name" 37 | [[ -z "$cur_type" ]] && fail 'Directories should come before files' 38 | if [[ "$cur_type" = d ]] ; then 39 | cur_type=f 40 | previous=${name} 41 | else 42 | [[ ${previous} < ${name} ]] || fail \ 43 | 'Name %s should come before %s\n' "${previous}" "${name}" 44 | fi 45 | ;; 46 | esac 47 | done < <( fetch '/dir_first/' \ 48 | | pup -p body table tbody 'td:nth-child(1)' text{} ) 49 | 50 | nginx_is_running || fail "Nginx died" 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [macos-latest, ubuntu-18.04] 11 | compiler: [gcc, clang] 12 | nginx: 13 | # Mainline 14 | - 1.23.3 15 | # Stable. 16 | - 1.22.1 17 | # First version with loadable module support. 18 | - 1.9.15 19 | # Oldest supported version. 20 | - 0.8.55 21 | dynamic: [0, 1] 22 | exclude: 23 | - nginx: 0.8.55 24 | dynamic: 1 25 | - nginx: 0.8.55 26 | os: macos-latest 27 | - compiler: gcc 28 | os: macos-latest 29 | runs-on: ${{ matrix.os }} 30 | env: 31 | CFLAGS: "-Wno-error" 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v2 35 | - name: Install Packages 36 | run: | 37 | case $RUNNER_OS in 38 | Linux ) 39 | sudo apt update 40 | sudo apt install -y libpcre3-dev libssl-dev 41 | ;; 42 | * ) 43 | ;; 44 | esac 45 | t/get-pup || echo 'Tests needing pup will be skipped' 46 | - name: Test 47 | env: 48 | CC: ${{ matrix.compiler }} 49 | run: t/build-and-run ${{ matrix.nginx }} ${{ matrix.dynamic }} 50 | -------------------------------------------------------------------------------- /t/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ $# -lt 1 || $# -gt 2 ]] ; then 5 | echo "Usage: $0 [1]" 1>&2 6 | exit 1 7 | fi 8 | 9 | # Obtain the absolute path to the tests directory 10 | pushd "$(dirname "$0")" &> /dev/null 11 | readonly T=$(pwd) 12 | popd &> /dev/null 13 | export T 14 | 15 | # Same for the nginx prefix directory 16 | pushd "$1" &> /dev/null 17 | readonly prefix=$(pwd) 18 | popd &> /dev/null 19 | 20 | dynamic=false 21 | if [[ $# -gt 1 && $2 -eq 1 ]] ; then 22 | dynamic=true 23 | fi 24 | readonly dynamic 25 | 26 | declare -a t_pass=( ) 27 | declare -a t_fail=( ) 28 | declare -a t_skip=( ) 29 | 30 | for t in `ls "$T"/*.test | sort -R` ; do 31 | name="t/${t##*/}" 32 | name=${name%.test} 33 | printf "${name} ... " 34 | errfile="${name}.err" 35 | outfile="${name}.out" 36 | shfile="${name}.sh" 37 | cat > "${shfile}" <<-EOF 38 | readonly DYNAMIC=${dynamic} 39 | readonly TESTDIR='$T' 40 | readonly PREFIX='${prefix}' 41 | $(< "$T/preamble") 42 | $(< "$t") 43 | EOF 44 | if bash -e "${shfile}" > "${outfile}" 2> "${errfile}" ; then 45 | t_pass+=( "${name}" ) 46 | printf 'passed\n' 47 | elif [[ $? -eq 111 ]] ; then 48 | t_skip+=( "${name}" ) 49 | printf 'skipped\n' 50 | else 51 | t_fail+=( "${name}" ) 52 | printf 'failed\n' 53 | fi 54 | done 55 | 56 | for name in "${t_fail[@]}" ; do 57 | echo 58 | printf '=== %s.out\n' "${name}" 59 | cat "${name}.out" 60 | echo 61 | printf '=== %s.err\n' "${name}" 62 | cat "${name}.err" 63 | echo 64 | done 65 | 66 | if [[ ${#t_skip[@]} -gt 0 ]] ; then 67 | echo 68 | printf 'Skipped tests:\n' 69 | for name in "${t_skip[@]}" ; do 70 | reason=$(grep '^(\-\-) ' "${name}.err" | head -1) 71 | if [[ -z ${reason} ]] ; then 72 | reason='No reason given' 73 | else 74 | reason=${reason:5} 75 | fi 76 | printf ' - %s: %s\n' "${name}" "${reason:-No reason given}" 77 | done 78 | echo 79 | fi 80 | 81 | printf '=== passed/skipped/failed/total: %d/%d/%d/%d\n' \ 82 | ${#t_pass[@]} ${#t_skip[@]} ${#t_fail[@]} $(( ${#t_pass[@]} + ${#t_fail[@]} )) 83 | 84 | if [[ ${#t_fail[@]} -gt 0 ]] ; then 85 | exit 1 86 | fi 87 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 44 | 45 | 46 | 47 | Index of 48 | <!-- var NONE --> 49 | /path/to/somewhere 50 | <!-- var t03_head3 --> 51 | 52 | 53 | 54 | 55 | 56 |

Index of 57 | 58 | /path/to/somewhere 59 | 60 |

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
File Name  ↓ File Size  ↓ Date  ↓ 
--
test file 1123kBdate
test file 2321MBdate
test file 3666date
99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /template.h: -------------------------------------------------------------------------------- 1 | /* Automagically generated, do not edit! */ 2 | static const u_char t01_head1[] = "" 3 | "" 4 | "" 5 | "" 6 | "" 7 | "" 8 | "" 45 | "\n" 46 | ; 47 | static const u_char t02_head2[] = "" 48 | "\n" 49 | "Index of " 50 | ; 51 | static const u_char t03_head3[] = "" 52 | "" 53 | "\n" 54 | "" 55 | ; 56 | static const u_char t04_body1[] = "" 57 | "" 58 | "

Index of " 59 | ; 60 | static const u_char t05_body2[] = "" 61 | "

" 62 | "\n" 63 | ; 64 | static const u_char t06_list1[] = "" 65 | "" 66 | "" 67 | "" 68 | "" 69 | "" 70 | "" 71 | "" 72 | "" 73 | "\n" 74 | "" 75 | ; 76 | static const u_char t_parentdir_entry[] = "" 77 | "" 78 | "" 79 | "" 80 | "" 81 | "" 82 | "\n" 83 | ; 84 | static const u_char t07_list2[] = "" 85 | "" 86 | "
File Name  ↓ File Size  ↓ Date  ↓ 
Parent directory/--
" 87 | ; 88 | static const u_char t08_foot1[] = "" 89 | "" 90 | "" 91 | ; 92 | #define NFI_TEMPLATE_SIZE (0 \ 93 | + nfi_sizeof_ssz(t01_head1) \ 94 | + nfi_sizeof_ssz(t02_head2) \ 95 | + nfi_sizeof_ssz(t03_head3) \ 96 | + nfi_sizeof_ssz(t04_body1) \ 97 | + nfi_sizeof_ssz(t05_body2) \ 98 | + nfi_sizeof_ssz(t06_list1) \ 99 | + nfi_sizeof_ssz(t_parentdir_entry) \ 100 | + nfi_sizeof_ssz(t07_list2) \ 101 | + nfi_sizeof_ssz(t08_foot1) \ 102 | ) 103 | -------------------------------------------------------------------------------- /t/preamble: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # preamble 4 | # Copyright (C) 2016 Adrian Perez 5 | # 6 | # SPDX-License-Identifier: BSD-2-Clause 7 | # 8 | 9 | function nginx_conf_generate () { 10 | if ${DYNAMIC} ; then 11 | echo 'load_module modules/ngx_http_fancyindex_module.so;' 12 | fi 13 | cat <<-EOF 14 | worker_processes 1; 15 | events { worker_connections 1024; } 16 | http { 17 | include mime.types; 18 | default_type application/octet-stream; 19 | sendfile on; 20 | keepalive_timeout 65; 21 | server { 22 | server_name localhost; 23 | listen 127.0.0.1:${NGINX_PORT}; 24 | root ${TESTDIR}; 25 | error_page 500 502 503 504 /50x.html; 26 | location = /50x.html { root html; } 27 | location / { 28 | index index.html; 29 | fancyindex on; 30 | $* 31 | } 32 | } 33 | } 34 | EOF 35 | } 36 | 37 | readonly NGINX_CONF="${PREFIX}/conf/nginx.conf" 38 | readonly NGINX_PID="${PREFIX}/logs/nginx.pid" 39 | 40 | case $(uname -s) in 41 | Darwin) 42 | NGINX_PORT=$(netstat -a -n -finet -ptcp | awk '/LISTEN/ { sub(".+\\.", "", $4) ; seen[$4]=1 } 43 | END { p=1025 ; while (seen[p]) p++; print p}') 44 | ;; 45 | *) 46 | NGINX_PORT=$(ss -4Htnl | awk '{ sub("[^:]+:", "", $4) ; seen[$4]=1 } 47 | END { p=1025 ; while (seen[p]) p++; print p}') 48 | ;; 49 | esac 50 | readonly NGINX_PORT 51 | 52 | rm -f "${NGINX_CONF}" "${NGINX_PID}" 53 | mkdir -p "${PREFIX}/logs" 54 | 55 | function pup () { 56 | if [[ -x ${TESTDIR}/pup ]] ; then 57 | "${TESTDIR}/pup" "$@" 58 | else 59 | skip 'Test uses "pup", which is not available' 60 | fi 61 | } 62 | 63 | function use () { 64 | case $1 in 65 | pup ) [[ -x ${TESTDIR}/pup ]] \ 66 | || skip 'Test uses "pup", which is unavailable\n' ;; 67 | * ) warn "Invalid 'use' flag: '%s'\n'" "$1" ;; 68 | esac 69 | } 70 | 71 | function nginx () { 72 | env - PATH="${PATH}" "${PREFIX}/sbin/nginx" "$@" 73 | } 74 | 75 | function nginx_conf () { 76 | nginx_conf_generate "$@" > "${NGINX_CONF}" 77 | } 78 | 79 | function nginx_is_running () { 80 | [[ -r ${NGINX_PID} ]] && kill -0 $(< "${NGINX_PID}") 81 | } 82 | 83 | function nginx_stop () { 84 | if nginx_is_running ; then nginx -s stop ; fi 85 | rm -f "${NGINX_PID}" 86 | } 87 | trap nginx_stop EXIT 88 | 89 | function nginx_start () { 90 | if [[ $# -gt 0 || ! -r ${NGINX_CONF} ]] ; then nginx_conf "$@" ; fi 91 | nginx_stop # Ensure that it is not running. 92 | nginx 93 | local n=0 94 | while [[ ! -r ${NGINX_PID} && n -lt 20 ]] ; do 95 | sleep 0.1 # Wait until pid exists. 96 | n=$((n+1)) 97 | done 98 | } 99 | 100 | function fetch () { 101 | local -a opts=( -q ) 102 | if [[ $1 = --with-headers ]] ; then 103 | opts+=( -S ) 104 | shift 105 | fi 106 | wget "${opts[@]}" -O- "http://localhost:${NGINX_PORT}${1:-/}" 2>&1 107 | } 108 | 109 | function skip () { 110 | printf '(--) ' 111 | printf "$@" 112 | exit 111 113 | } 1>&2 114 | 115 | function fail () { 116 | printf '(FF) ' 117 | printf "$@" 118 | exit 1 119 | } 1>&2 120 | 121 | function warn () { 122 | printf '(WW) ' 123 | printf "$@" 124 | } 1>&2 125 | -------------------------------------------------------------------------------- /t/get-pup: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | declare -r SHASUMS='\ 5 | ec9522193516ad49c78d40a8163f1d92e98866892a11aadb7be584a975026a8a pup_69c02e189c2aaed331061ee436c39e72b830ef32_darwin_amd64.xz 6 | 75c27caa0008a9cc639beb7506077ad9f32facbffcc4e815e999eaf9588a527e pup_v0.4.0_darwin_386.zip 7 | c539a697efee2f8e56614a54cb3b215338e00de1f6a7c2fa93144ab6e1db8ebe pup_v0.4.0_darwin_amd64.zip 8 | 259eee82c7d7d766f1b8f93a382be21dcfefebc855a9ce8124fd78717f9df439 pup_v0.4.0_dragonfly_amd64.zip 9 | ba0fe5e87a24cab818e5d2efdd7540714ddfb1b7246600135915c666fdf1a601 pup_v0.4.0_freebsd_386.zip 10 | 1838ef84ec1f961e8009d19a4d1e6a23b926ee315da3d60c08878f3d69af5692 pup_v0.4.0_freebsd_amd64.zip 11 | 6886a9c60a912a810d012610bc3f784f0417999ff7d7df833a0695b9af60395b pup_v0.4.0_freebsd_arm.zip 12 | e486b32ca07552cd3aa713cbf2f9d1b6e210ddb51d34b3090c7643f465828057 pup_v0.4.0_linux_386.zip 13 | ec3d29e9fb375b87ac492c8b546ad6be84b0c0b49dab7ff4c6b582eac71ba01c pup_v0.4.0_linux_amd64.zip 14 | c09b669fa8240f4f869dee7d34ee3c7ea620a0280cee1ea7d559593bcdd062c9 pup_v0.4.0_linux_arm64.zip 15 | ebf70b3c76c02e0202c94af7ef06dcb3ecc866d1b9b84453d43fe01fa5dd5870 pup_v0.4.0_linux_arm.zip 16 | a98a4d1f3c3a103e8ebe1a7aba9cb9d3cb045003208ca6f5f3d54889a225f267 pup_v0.4.0_linux_mips64le.zip 17 | 8e471cf6cfa118b2497bb3f42a7a48c52d0096107f748f37216855c8ab94f8e5 pup_v0.4.0_linux_mips64.zip 18 | cfda9375eba65f710e052b1b59893c228c3fc92b0510756bb3f02c25938eee30 pup_v0.4.0_linux_ppc64le.zip 19 | 91a1e07ffb2c373d6053252e4de732f5db78c8eace49c6e1a0ef52402ecdf56c pup_v0.4.0_linux_ppc64.zip 20 | fdc9b28a3daac5ad096023e1647292a7eccea6d9b1686f871307dae9f3bd064f pup_v0.4.0_nacl_386.zip 21 | c8d3c9b56783bd5a55446f4580e1835606b2b945da2d1417ed509c5927a5f8bc pup_v0.4.0_nacl_amd64p32.zip 22 | 48c068c4353672528c8c3447a536208b0719f1e6d0f8fab8416b38b63ad0c1d9 pup_v0.4.0_nacl_arm.zip 23 | 7a27497b2f0be95c51bb2cbc25da12efba682c4f766bc5abc5742e9fc8d1eeb0 pup_v0.4.0_netbsd_386.zip 24 | 71a1808eb1b6442aa45d1de9e1c4fca543b2754c1aff5ba3d62b3456f9519691 pup_v0.4.0_netbsd_amd64.zip 25 | 928e6691b11c68ae3f28826848a13dc5c1c9673848fe7cf7f80dd76c9fb6e8a6 pup_v0.4.0_netbsd_arm.zip 26 | 5aca20a9b3264d2fde5a8d32f213c434edf9570ee6fae18953b8fff09d2976e2 pup_v0.4.0_openbsd_386.zip 27 | e965c6f04b897240d84c60e2c18226deb231a657c5583680f58a61051ff5a100 pup_v0.4.0_openbsd_amd64.zip 28 | 30bc88a1e06606f4f3449af9fbf586f97c2e958677460a72bb1a168f67c4911c pup_v0.4.0_openbsd_arm.zip 29 | 9d50decf4572292f187cfec84660648d648336bc6109e1f032b1699ba1d28549 pup_v0.4.0_plan9_386.zip 30 | 1b2a6bd2388ddd691ca429497d88b2b047ec8dfb7bce9436925cb2f30632bf8e pup_v0.4.0_plan9_amd64.zip 31 | 0835de9c10a9e2b3b958b82d148da49eaafc695fe4a018cbaf7bb861b455583f pup_v0.4.0_solaris_amd64.zip 32 | 01acae220b69fb1ba8477d0e7f4d7669ef5de147966dc819cf75a845af74c5f3 pup_v0.4.0_windows_386.zip 33 | 6755cbd43e94eaf173689e93e914c7056a2249c2977e5b90024fb397f9b45ba4 pup_v0.4.0_windows_amd64.zip 34 | ' 35 | 36 | declare -r TDIR=$(dirname "$0") 37 | 38 | case $(uname -m) in 39 | x86_64 | amd64 ) ARCH=amd64 ;; 40 | i[3456]86 ) ARCH=386 ;; 41 | * ) ARCH= ;; 42 | esac 43 | 44 | OS=$(uname -s | tr 'A-Z' 'a-z') 45 | case ${OS} in 46 | linux | freebsd | openbsd | netbsd | darwin ) ;; 47 | * ) OS= ;; 48 | esac 49 | 50 | # The binary of pup 0.4.0 for macOS provided by the original project 51 | # crashes immediately on macOS 10.13 (Darwin 17) and up so use a fork: 52 | # https://github.com/ericchiang/pup/issues/85 53 | if [[ ${OS} = darwin && $(uname -r | cut -d. -f1) -ge 17 ]] ; then 54 | USE_FORK=1 55 | else 56 | USE_FORK=0 57 | fi 58 | 59 | if (( USE_FORK )) ; then 60 | declare -r VERSION=69c02e189c2aaed331061ee436c39e72b830ef32 61 | declare -r DISTFILE="pup_${VERSION}_${OS}_${ARCH}.xz" 62 | declare -r URL="https://github.com/frioux/pup/releases/download/untagged-${VERSION}/pup.mac.xz" 63 | if ! command -v xz >/dev/null ; then 64 | echo "xz not found" 1>&2 65 | exit 3 66 | fi 67 | else 68 | declare -r VERSION=0.4.0 69 | declare -r DISTFILE="pup_v${VERSION}_${OS}_${ARCH}.zip" 70 | declare -r URL="https://github.com/ericchiang/pup/releases/download/v${VERSION}/${DISTFILE}" 71 | fi 72 | 73 | if [[ -z ${ARCH} || -z ${OS} ]] ; then 74 | echo "pup ${VERSION} is not available for $(uname -s) on $(uname -m)" 1>&2 75 | exit 1 76 | fi 77 | 78 | EXPECT_SHA= 79 | while read sum fname ; do 80 | if [[ ${fname} = ${DISTFILE} ]] ; then 81 | EXPECT_SHA=${sum} 82 | break 83 | fi 84 | done <<< "${SHASUMS}" 85 | 86 | wget -cO "${TDIR}/${DISTFILE}" "${URL}" 87 | 88 | read -r _ GOT_SHA < <( openssl sha256 < "${TDIR}/${DISTFILE}" ) 89 | if [[ ${EXPECT_SHA} = ${GOT_SHA} ]] ; then 90 | echo "Checksum for ${DISTFILE} verified :-)" 91 | else 92 | rm -f "${TDIR}/${DISTFILE}" "${TDIR}/pup" 93 | echo "Checksum for ${DISTFILE} does not match :-(" 94 | echo " Expected: ${EXPECT_SHA}" 95 | echo " Got: ${GOT_SHA}" 96 | exit 2 97 | fi 1>&2 98 | 99 | rm -f "${TDIR}/pup" 100 | 101 | if (( USE_FORK )) ; then 102 | (cd "${TDIR}" && xz -dk "${DISTFILE}" && mv "${DISTFILE%.*}" pup && chmod a+x pup) 103 | else 104 | unzip "${TDIR}/${DISTFILE}" pup -d "${TDIR}" 105 | fi 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased] 5 | 6 | ## [0.5.2] - 2021-10-28 7 | ### Fixed 8 | - Properly escape file names to ensure that file names are never renreded 9 | as HTML. (Patch by Anthony Ryan <>, 10 | [#128](https://github.com/aperezdc/ngx-fancyindex/pull/128).) 11 | 12 | ## [0.5.1] - 2020-10-26 13 | ### Fixed 14 | - Properly handle optional second argument to `fancyindex_header` and 15 | `fancyindex_footer` 16 | ([#117](https://github.com/aperezdc/ngx-fancyindex/issues/117)). 17 | 18 | ## [0.5.0] - 2020-10-24 19 | ### Added 20 | - New option `fancyindex_show_dotfiles`. (Path by Joshua Shaffer 21 | <>.) 22 | - The `fancyindex_header` and `fancyindex_footer` options now support local 23 | files properly, by means of a `local` flag. (Patches by JoungKyun Kim 24 | <> and Adrián Pérez <>.) 25 | 26 | ### Changed 27 | - Improved performance of directory entry sorting, which should be quite 28 | noticeable for directories with thousands of files. (Patch by 29 | [Yuxiang Zhang](https://github.com/z4yx).) 30 | - The minimum Nginx version supported by the module is now 0.8.x. 31 | 32 | ### Fixed 33 | - Properly escape square brackets in directory entry names when the module 34 | is built with older versions of Nginx. (Patch by Adrián Pérez 35 | <>.) 36 | - Fix directory entry listing not being shown when using the 37 | [nginx-auth-ldap](https://github.com/kvspb/nginx-auth-ldap) module. (Patch 38 | by JoungKyun Kim <>.) 39 | 40 | ## [0.4.4] - 2020-02-19 41 | ### Added 42 | - New option `fancyindex_hide_parent_dir`, which disables generating 43 | links to parent directories in listings. (Patch by Kawai Ryota 44 | <>.) 45 | 46 | ### Changed 47 | - Each table row is now separated by a new line (as a matter of fact, 48 | a `CRLF` sequence), which makes it easier to parse output using simple 49 | text tools. (Patch by Anders Trier <>.) 50 | - Some corrections and additions to the README file. (Patches by Nicolas 51 | Carpi <> and David Beitey <>.) 52 | 53 | ### Fixed 54 | - Use correct character references for `&` characters in table sorter URLs 55 | within the template (Patch by David Beitey <>.) 56 | - Properly encode filenames when used as URI components. 57 | 58 | ## [0.4.3] - 2018-07-03 59 | ### Added 60 | - Table cells now have class names, which allows for better CSS styling. 61 | (Patch by qjqqyy <>.) 62 | - The test suite now can parse and check elements from the HTML returned 63 | by the module, thanks to the [pup](https://github.com/EricChiang/pup) 64 | tool. 65 | 66 | ### Fixed 67 | - Sorting by file size now works correctly. 68 | (Patch by qjqqyy <>.) 69 | 70 | ## [0.4.2] - 2017-08-19 71 | ### Changed 72 | - Generated HTML from the default template is now proper HTML5, and it should 73 | pass validation (#52). 74 | - File sizes now have decimal positions when using `fancyindex_exact_size off`. 75 | (Patch by Anders Trier <>.) 76 | - Multiple updates to `README.rst` (Patches by Danila Vershinin 77 | <>, Iulian Onofrei, Lilian Besson, and Nick Geoghegan 78 | <>.) 79 | 80 | ### Fixed 81 | - Sorting by file size now also works correctly for directories which contain 82 | files of sizes bigger than `INT_MAX`. (#74, fix suggestion by Chris Young.) 83 | - Custom headers which fail to declare an UTF-8 encoding no longer cause table 84 | header arrows to be rendered incorrectly by browsers (#50). 85 | - Fix segmentation fault when opening directories with empty files (#61, patch 86 | by Catgirl <>.) 87 | 88 | ## [0.4.1] - 2016-08-18 89 | ### Added 90 | - New `fancyindex_directories_first` configuration directive (enabled by 91 | default), which allows setting whether directories are sorted before other 92 | files. (Patch by Luke Zapart <>.) 93 | 94 | ### Fixed 95 | - Fix index files not working when the fancyindex module is in use (#46). 96 | 97 | 98 | ## [0.4.0] - 2016-06-08 99 | ### Added 100 | - The module can now be built as a [dynamic 101 | module](https://www.nginx.com/resources/wiki/extending/converting/). 102 | (Patch by Róbert Nagy <>.) 103 | - New configuration directive `fancyindex_show_path`, which allows hiding the 104 | `

` header which contains the current path. 105 | (Patch by Thomas P. <>.) 106 | 107 | ### Changed 108 | - Directory and file links in listings now have a title="..." attribute. 109 | (Patch by `@janglapuk` <>.) 110 | 111 | ### Fixed 112 | - Fix for hung requests when the module is used along with `ngx_pagespeed`. 113 | (Patch by Otto van der Schaaf <>.) 114 | 115 | 116 | ## [0.3.6] - 2016-01-26 117 | ### Added 118 | - New feature: Allow filtering out symbolic links using the 119 | `fancyindex_hide_symlinks` configuration directive. (Idea and prototype 120 | patch by Thomas Wemm.) 121 | - New feature: Allow specifying the format of timestamps using the 122 | `fancyindex_time_format` configuration directive. (Idea suggested by Xiao 123 | Meng <>). 124 | 125 | ### Changed 126 | - Listings in top-level directories will not generate a "Parent Directory" 127 | link as first element of the listing. (Patch by Thomas P.) 128 | 129 | ### Fixed 130 | - Fix propagation and overriding of the `fancyindex_css_href` setting inside 131 | nested locations. 132 | - Minor changes in the code to allow building cleanly under Windows with 133 | Visual Studio 2013. (Patch by Y. Yuan <>). 134 | 135 | 136 | ## [0.3.5] - 2015-02-19 137 | ### Added 138 | - New feature: Allow setting the default sort criterion using the 139 | `fancyindex_default_sort` configuration directive. (Patch by 140 | Алексей Урбанский). 141 | - New feature: Allow changing the maximum length of file names, using 142 | the `fancyindex_name_length` configuration directive. (Patch by 143 | Martin Herkt). 144 | 145 | ### Changed 146 | - Renames `NEWS.rst` to `CHANGELOG.md`, which follows the recommendations 147 | from [Keep a Change Log](http://keepachangelog.com/). 148 | - Configuring Nginx without the `http_addition_module` will generate a 149 | warning during configuration, as it is needed for the `fancyindex_footer` 150 | and `fancyindex_header` directives. 151 | 152 | 153 | ## [0.3.4] - 2014-09-03 154 | 155 | ### Added 156 | - Viewport is now defined in the generated HTML, which works better 157 | for mobile devices. 158 | 159 | ### Changed 160 | - Even-odd row styling moved to the CSS using :nth-child(). This 161 | makes the HTML served to clients smaller. 162 | 163 | 164 | ## [0.3.3] - 2013-10-25 165 | 166 | ### Added 167 | - New feature: table headers in the default template are now clickable 168 | to set the sorting criteria and direction of the index entries. 169 | (https://github.com/aperezdc/ngx-fancyindex/issues/7) 170 | 171 | 172 | ## [0.3.2] - 2013-06-05 173 | 174 | ### Fixed 175 | - Solved a bug that would leave certain clients stalled forever. 176 | - Improved handling of subrequests for non-builtin headers/footers. 177 | 178 | 179 | ## [0.3.1] - 2011-04-04 180 | 181 | ### Added 182 | - `NEWS.rst` file, to act as change log. 183 | 184 | 185 | [Unreleased]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.5.2...HEAD 186 | [0.5.2]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.5.1...v0.5.2 187 | [0.5.1]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.5.0...v0.5.1 188 | [0.5.0]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.4...v0.5.0 189 | [0.4.4]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.3...v0.4.4 190 | [0.4.3]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.2...v0.4.3 191 | [0.4.2]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.1...v0.4.2 192 | [0.4.1]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.4.0...v0.4.1 193 | [0.4.0]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.6...v0.4.0 194 | [0.3.6]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.5...v0.3.6 195 | [0.3.5]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.4...v0.3.5 196 | [0.3.4]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.3...v0.3.4 197 | [0.3.3]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.2...v0.3.3 198 | [0.3.2]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3.1...v0.3.2 199 | [0.3.1]: https://github.com/aperezdc/ngx-fancyindex/compare/v0.3...v0.3.1 200 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Nginx Fancy Index module 3 | ======================== 4 | 5 | .. image:: https://travis-ci.com/aperezdc/ngx-fancyindex.svg?branch=master 6 | :target: https://travis-ci.com/aperezdc/ngx-fancyindex 7 | :alt: Build Status 8 | 9 | .. contents:: 10 | 11 | The Fancy Index module makes possible the generation of file listings, like 12 | the built-in `autoindex `__ 13 | module does, but adding a touch of style. This is possible because the module 14 | allows a certain degree of customization of the generated content: 15 | 16 | * Custom headers, either local or stored remotely. 17 | * Custom footers, either local or stored remotely. 18 | * Add your own CSS style rules. 19 | * Allow choosing to sort elements by name (default), modification time, or 20 | size; both ascending (default), or descending. 21 | 22 | This module is designed to work with Nginx_, a high performance open source web 23 | server written by `Igor Sysoev `__. 24 | 25 | 26 | Requirements 27 | ============ 28 | 29 | CentOS, RHEL, Fedora Linux 30 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | 32 | For users of the `official stable `__ Nginx repository, `extra packages repository with dynamic modules `__ is available and fancyindex is included. 33 | 34 | Install repository configuration, then the module package:: 35 | 36 | yum -y install https://extras.getpagespeed.com/release-latest.rpm 37 | yum -y install nginx-module-fancyindex 38 | 39 | Then load the module in `/etc/nginx/nginx.conf` using:: 40 | 41 | load_module "modules/ngx_http_fancyindex_module.so"; 42 | 43 | macOS 44 | ~~~~~ 45 | 46 | Users can `install Nginx on macOS with MacPorts `__; fancyindex is included:: 47 | 48 | sudo port install nginx 49 | 50 | Other platforms 51 | ~~~~~~~~~~~~~~~ 52 | 53 | In most other cases you will need the sources for Nginx_. Any version starting 54 | from the 0.8 series should work. 55 | 56 | In order to use the ``fancyindex_header_`` and ``fancyindex_footer_`` directives 57 | you will also need the `ngx_http_addition_module `_ 58 | built into Nginx. 59 | 60 | 61 | Building 62 | ======== 63 | 64 | 1. Unpack the Nginx_ sources:: 65 | 66 | $ gunzip -c nginx-?.?.?.tar.gz | tar -xvf - 67 | 68 | 2. Unpack the sources for the fancy indexing module:: 69 | 70 | $ gunzip -c nginx-fancyindex-?.?.?.tar.gz | tar -xvf - 71 | 72 | 3. Change to the directory which contains the Nginx_ sources, run the 73 | configuration script with the desired options and be sure to put an 74 | ``--add-module`` flag pointing to the directory which contains the source 75 | of the fancy indexing module:: 76 | 77 | $ cd nginx-?.?.? 78 | $ ./configure --add-module=../nginx-fancyindex-?.?.? \ 79 | [--with-http_addition_module] [extra desired options] 80 | 81 | Since version 0.4.0, the module can also be built as a 82 | `dynamic module `_, 83 | using ``--add-dynamic-module=…`` instead and 84 | ``load_module "modules/ngx_http_fancyindex_module.so";`` 85 | in the configuration file 86 | 87 | 4. Build and install the software:: 88 | 89 | $ make 90 | 91 | And then, as ``root``:: 92 | 93 | # make install 94 | 95 | 5. Configure Nginx_ by using the modules' configuration directives_. 96 | 97 | 98 | Example 99 | ======= 100 | 101 | You can test the default built-in style by adding the following lines into 102 | a ``server`` section in your Nginx_ configuration file:: 103 | 104 | location / { 105 | fancyindex on; # Enable fancy indexes. 106 | fancyindex_exact_size off; # Output human-readable file sizes. 107 | } 108 | 109 | 110 | Themes 111 | ~~~~~~ 112 | 113 | The following themes demonstrate the level of customization which can be 114 | achieved using the module: 115 | 116 | * `Theme `__ by 117 | `@TheInsomniac `__. Uses custom header and 118 | footer. 119 | * `Theme `__ by 120 | `@Naereen `__. Uses custom header and footer. The 121 | header includes a search field to filter by file name using JavaScript. 122 | * `Theme `__ by 123 | `@fraoustin `__. Responsive theme using 124 | Material Design elements. 125 | * `Theme `__ by 126 | `@alehaa `__. Simple, flat theme based on 127 | Bootstrap 4 and FontAwesome. 128 | 129 | 130 | Directives 131 | ========== 132 | 133 | fancyindex 134 | ~~~~~~~~~~ 135 | :Syntax: *fancyindex* [*on* | *off*] 136 | :Default: fancyindex off 137 | :Context: http, server, location 138 | :Description: 139 | Enables or disables fancy directory indexes. 140 | 141 | fancyindex_default_sort 142 | ~~~~~~~~~~~~~~~~~~~~~~~ 143 | :Syntax: *fancyindex_default_sort* [*name* | *size* | *date* | *name_desc* | *size_desc* | *date_desc*] 144 | :Default: fancyindex_default_sort name 145 | :Context: http, server, location 146 | :Description: 147 | Defines sorting criterion by default. 148 | 149 | fancyindex_case_sensitive 150 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 151 | :Syntax: *fancyindex_case_sensitive* [*on* | *off*] 152 | :Default: fancyindex_case_sensitive on 153 | :Context: http, server, location 154 | :Description: 155 | If enabled (default setting), sorting by name will be case-sensitive. 156 | If disabled, case will be ignored when sorting by name. 157 | 158 | fancyindex_directories_first 159 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 160 | :Syntax: *fancyindex_directories_first* [*on* | *off*] 161 | :Default: fancyindex_directories_first on 162 | :Context: http, server, location 163 | :Description: 164 | If enabled (default setting), groups directories together and sorts them 165 | before all regular files. If disabled, directories are sorted together with files. 166 | 167 | fancyindex_css_href 168 | ~~~~~~~~~~~~~~~~~~~ 169 | :Syntax: *fancyindex_css_href uri* 170 | :Default: fancyindex_css_href "" 171 | :Context: http, server, location 172 | :Description: 173 | Allows inserting a link to a CSS style sheet in generated listings. The 174 | provided *uri* parameter will be inserted as-is in a ```` HTML tag. 175 | The link is inserted after the built-in CSS rules, so you can override the 176 | default styles. 177 | 178 | fancyindex_exact_size 179 | ~~~~~~~~~~~~~~~~~~~~~ 180 | :Syntax: *fancyindex_exact_size* [*on* | *off*] 181 | :Default: fancyindex_exact_size on 182 | :Context: http, server, location 183 | :Description: 184 | Defines how to represent file sizes in the directory listing: either 185 | accurately, or rounding off to the kilobyte, the megabyte and the 186 | gigabyte. 187 | 188 | fancyindex_footer 189 | ~~~~~~~~~~~~~~~~~ 190 | :Syntax: *fancyindex_footer path* [*subrequest* | *local*] 191 | :Default: fancyindex_footer "" 192 | :Context: http, server, location 193 | :Description: 194 | Specifies which file should be inserted at the foot of directory listings. 195 | If set to an empty string, the default footer supplied by the module will 196 | be sent. The optional parameter indicates whether the *path* is to be 197 | treated as a URI to load using a *subrequest* (the default), or whether 198 | it refers to a *local* file. 199 | 200 | .. note:: Using this directive needs the ngx_http_addition_module_ built 201 | into Nginx. 202 | 203 | .. warning:: When inserting custom a header/footer, a subrequest will be 204 | issued so potentially any URL can be used as source for them. Although it 205 | will work with external URLs, only using internal ones is supported. 206 | External URLs are totally untested and using them will make Nginx_ block 207 | while waiting for the subrequest to complete. If you feel like external 208 | header/footer is a must-have for you, please 209 | `let me know `__. 210 | 211 | fancyindex_header 212 | ~~~~~~~~~~~~~~~~~ 213 | :Syntax: *fancyindex_header path* [*subrequest* | *local*] 214 | :Default: fancyindex_header "" 215 | :Context: http, server, location 216 | :Description: 217 | Specifies which file should be inserted at the head of directory listings. 218 | If set to an empty string, the default header supplied by the module will 219 | be sent. The optional parameter indicates whether the *path* is to be 220 | treated as a URI to load using a *subrequest* (the default), or whether 221 | it refers to a *local* file. 222 | 223 | .. note:: Using this directive needs the ngx_http_addition_module_ built 224 | into Nginx. 225 | 226 | fancyindex_show_path 227 | ~~~~~~~~~~~~~~~~~~~~ 228 | :Syntax: *fancyindex_show_path* [*on* | *off*] 229 | :Default: fancyindex_show_path on 230 | :Context: http, server, location 231 | :Description: 232 | Whether or not to output the path and the closing

tag after the header. 233 | This is useful when you want to handle the path displaying with a PHP script 234 | for example. 235 | 236 | .. warning:: This directive can be turned off only if a custom header is provided 237 | using fancyindex_header. 238 | 239 | fancyindex_show_dotfiles 240 | ~~~~~~~~~~~~~~~~~~~~ 241 | :Syntax: *fancyindex_show_dotfiles* [*on* | *off*] 242 | :Default: fancyindex_show_dotfiles off 243 | :Context: http, server, location 244 | :Description: 245 | Whether to list files that are preceded with a dot. Normal convention is to 246 | hide these. 247 | 248 | fancyindex_ignore 249 | ~~~~~~~~~~~~~~~~~ 250 | :Syntax: *fancyindex_ignore string1 [string2 [... stringN]]* 251 | :Default: No default. 252 | :Context: http, server, location 253 | :Description: 254 | Specifies a list of file names which will not be shown in generated 255 | listings. If Nginx was built with PCRE support, strings are interpreted as 256 | regular expressions. 257 | 258 | fancyindex_hide_symlinks 259 | ~~~~~~~~~~~~~~~~~~~~~~~~ 260 | :Syntax: *fancyindex_hide_symlinks* [*on* | *off*] 261 | :Default: fancyindex_hide_symlinks off 262 | :Context: http, server, location 263 | :Description: 264 | When enabled, generated listings will not contain symbolic links. 265 | 266 | fancyindex_hide_parent_dir 267 | ~~~~~~~~~~~~~~~~~~~~~~~~ 268 | :Syntax: *fancyindex_hide_parent_dir* [*on* | *off*] 269 | :Default: fancyindex_hide_parent_dir off 270 | :Context: http, server, location 271 | :Description: 272 | When enabled, it will not show the parent directory. 273 | 274 | fancyindex_localtime 275 | ~~~~~~~~~~~~~~~~~~~~ 276 | :Syntax: *fancyindex_localtime* [*on* | *off*] 277 | :Default: fancyindex_localtime off 278 | :Context: http, server, location 279 | :Description: 280 | Enables showing file times as local time. Default is “off” (GMT time). 281 | 282 | fancyindex_time_format 283 | ~~~~~~~~~~~~~~~~~~~~~~ 284 | :Syntax: *fancyindex_time_format* string 285 | :Default: fancyindex_time_format "%Y-%b-%d %H:%M" 286 | :Context: http, server, location 287 | :Description: 288 | Format string used for timestamps. The format specifiers are a subset of 289 | those supported by the `strftime `_ 290 | function, and the behavior is locale-independent (for example, day and month 291 | names are always in English). The supported formats are: 292 | 293 | * ``%a``: Abbreviated name of the day of the week. 294 | * ``%A``: Full name of the day of the week. 295 | * ``%b``: Abbreviated month name. 296 | * ``%B``: Full month name. 297 | * ``%d``: Day of the month as a decimal number (range 01 to 31). 298 | * ``%e``: Like ``%d``, the day of the month as a decimal number, but a 299 | leading zero is replaced by a space. 300 | * ``%F``: Equivalent to ``%Y-%m-%d`` (the ISO 8601 date format). 301 | * ``%H``: Hour as a decimal number using a 24-hour clock (range 00 302 | to 23). 303 | * ``%I``: Hour as a decimal number using a 12-hour clock (range 01 to 12). 304 | * ``%k``: Hour (24-hour clock) as a decimal number (range 0 to 23); 305 | single digits are preceded by a blank. 306 | * ``%l``: Hour (12-hour clock) as a decimal number (range 1 to 12); single 307 | digits are preceded by a blank. 308 | * ``%m``: Month as a decimal number (range 01 to 12). 309 | * ``%M``: Minute as a decimal number (range 00 to 59). 310 | * ``%p``: Either "AM" or "PM" according to the given time value. 311 | * ``%P``: Like ``%p`` but in lowercase: "am" or "pm". 312 | * ``%r``: Time in a.m. or p.m. notation. Equivalent to ``%I:%M:%S %p``. 313 | * ``%R``: Time in 24-hour notation (``%H:%M``). 314 | * ``%S``: Second as a decimal number (range 00 to 60). 315 | * ``%T``: Time in 24-hour notation (``%H:%M:%S``). 316 | * ``%u``: Day of the week as a decimal, range 1 to 7, Monday being 1. 317 | * ``%w``: Day of the week as a decimal, range 0 to 6, Monday being 0. 318 | * ``%y``: Year as a decimal number without a century (range 00 to 99). 319 | * ``%Y``: Year as a decimal number including the century. 320 | 321 | 322 | .. _nginx: https://nginx.org 323 | 324 | .. vim:ft=rst:spell:spelllang=en: 325 | -------------------------------------------------------------------------------- /ngx_http_fancyindex_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ngx_http_fancyindex_module.c 3 | * Copyright © 2007-2016 Adrian Perez 4 | * 5 | * Module used for fancy indexing of directories. Features and differences 6 | * with the stock nginx autoindex module: 7 | * 8 | * - Output is a table instead of a
 element with embedded  links.
   9 |  *  - Header and footer may be added to every generated directory listing.
  10 |  *  - Default header and/or footer are generated if custom ones are not
  11 |  *    configured. Files used for header and footer can only be local path
  12 |  *    names (i.e. you cannot insert the result of a subrequest.)
  13 |  *  - Proper HTML is generated: it should validate both as XHTML 1.0 Strict
  14 |  *    and HTML 4.01.
  15 |  *
  16 |  * Base functionality heavy based upon the stock nginx autoindex module,
  17 |  * which in turn was made by Igor Sysoev, like the majority of nginx.
  18 |  *
  19 |  * SPDX-License-Identifier: BSD-2-Clause
  20 |  */
  21 | 
  22 | #include 
  23 | #include 
  24 | #include 
  25 | #include 
  26 | 
  27 | #include "template.h"
  28 | 
  29 | #if defined(__GNUC__) && (__GNUC__ >= 3)
  30 | # define ngx_force_inline __attribute__((__always_inline__))
  31 | #else /* !__GNUC__ */
  32 | # define ngx_force_inline
  33 | #endif /* __GNUC__ */
  34 | 
  35 | 
  36 | static const char *short_weekday[] = {
  37 |     "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
  38 | };
  39 | static const char *long_weekday[] = {
  40 |     "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday",
  41 | };
  42 | static const char *short_month[] = {
  43 |     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  44 |     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  45 | };
  46 | static const char *long_month[] = {
  47 |     "January", "February", "March", "April", "May", "June", "July",
  48 |     "August", "September", "October", "November", "December",
  49 | };
  50 | 
  51 | 
  52 | #define DATETIME_FORMATS(F_, t) \
  53 |     F_ ('a',  3, "%3s",  short_weekday[((t)->ngx_tm_wday + 6) % 7]) \
  54 |     F_ ('A',  9, "%s",   long_weekday [((t)->ngx_tm_wday + 6) % 7]) \
  55 |     F_ ('b',  3, "%3s",  short_month[(t)->ngx_tm_mon - 1]         ) \
  56 |     F_ ('B',  9, "%s",   long_month [(t)->ngx_tm_mon - 1]         ) \
  57 |     F_ ('d',  2, "%02d", (t)->ngx_tm_mday                         ) \
  58 |     F_ ('e',  2, "%2d",  (t)->ngx_tm_mday                         ) \
  59 |     F_ ('F', 10, "%d-%02d-%02d",                                    \
  60 |                   (t)->ngx_tm_year,                                 \
  61 |                   (t)->ngx_tm_mon,                                  \
  62 |                   (t)->ngx_tm_mday                                ) \
  63 |     F_ ('H',  2, "%02d", (t)->ngx_tm_hour                         ) \
  64 |     F_ ('I',  2, "%02d", ((t)->ngx_tm_hour % 12) + 1              ) \
  65 |     F_ ('k',  2, "%2d",  (t)->ngx_tm_hour                         ) \
  66 |     F_ ('l',  2, "%2d",  ((t)->ngx_tm_hour % 12) + 1              ) \
  67 |     F_ ('m',  2, "%02d", (t)->ngx_tm_mon                          ) \
  68 |     F_ ('M',  2, "%02d", (t)->ngx_tm_min                          ) \
  69 |     F_ ('p',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "AM" : "PM")  ) \
  70 |     F_ ('P',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "am" : "pm")  ) \
  71 |     F_ ('r', 11, "%02d:%02d:%02d %2s",                              \
  72 |                  ((t)->ngx_tm_hour % 12) + 1,                       \
  73 |                  (t)->ngx_tm_min,                                   \
  74 |                  (t)->ngx_tm_sec,                                   \
  75 |                  (((t)->ngx_tm_hour < 12) ? "AM" : "PM")          ) \
  76 |     F_ ('R',  5, "%02d:%02d", (t)->ngx_tm_hour, (t)->ngx_tm_min   ) \
  77 |     F_ ('S',  2, "%02d", (t)->ngx_tm_sec                          ) \
  78 |     F_ ('T',  8, "%02d:%02d:%02d",                                  \
  79 |                  (t)->ngx_tm_hour,                                  \
  80 |                  (t)->ngx_tm_min,                                   \
  81 |                  (t)->ngx_tm_sec                                  ) \
  82 |     F_ ('u',  1, "%1d", (((t)->ngx_tm_wday + 6) % 7) + 1          ) \
  83 |     F_ ('w',  1, "%1d", ((t)->ngx_tm_wday + 6) % 7                ) \
  84 |     F_ ('y',  2, "%02d", (t)->ngx_tm_year % 100                   ) \
  85 |     F_ ('Y',  4, "%04d", (t)->ngx_tm_year                         )
  86 | 
  87 | 
  88 | static size_t
  89 | ngx_fancyindex_timefmt_calc_size (const ngx_str_t *fmt)
  90 | {
  91 | #define DATETIME_CASE(letter, fmtlen, fmt, ...) \
  92 |         case letter: result += (fmtlen); break;
  93 | 
  94 |     size_t i, result = 0;
  95 |     for (i = 0; i < fmt->len; i++) {
  96 |         if (fmt->data[i] == '%') {
  97 |             if (++i >= fmt->len) {
  98 |                 result++;
  99 |                 break;
 100 |             }
 101 |             switch (fmt->data[i]) {
 102 |                 DATETIME_FORMATS(DATETIME_CASE,)
 103 |                 default:
 104 |                     result++;
 105 |             }
 106 |         } else {
 107 |             result++;
 108 |         }
 109 |     }
 110 |     return result;
 111 | 
 112 | #undef DATETIME_CASE
 113 | }
 114 | 
 115 | 
 116 | static u_char*
 117 | ngx_fancyindex_timefmt (u_char *buffer, const ngx_str_t *fmt, const ngx_tm_t *tm)
 118 | {
 119 | #define DATETIME_CASE(letter, fmtlen, fmt, ...) \
 120 |         case letter: buffer = ngx_snprintf(buffer, fmtlen, fmt, ##__VA_ARGS__); break;
 121 | 
 122 |     size_t i;
 123 |     for (i = 0; i < fmt->len; i++) {
 124 |         if (fmt->data[i] == '%') {
 125 |             if (++i >= fmt->len) {
 126 |                 *buffer++ = '%';
 127 |                 break;
 128 |             }
 129 |             switch (fmt->data[i]) {
 130 |                 DATETIME_FORMATS(DATETIME_CASE, tm)
 131 |                 default:
 132 |                     *buffer++ = fmt->data[i];
 133 |             }
 134 |         } else {
 135 |             *buffer++ = fmt->data[i];
 136 |         }
 137 |     }
 138 |     return buffer;
 139 | 
 140 | #undef DATETIME_CASE
 141 | }
 142 | 
 143 | typedef struct {
 144 |     ngx_str_t path;
 145 |     ngx_str_t local;
 146 | } ngx_fancyindex_headerfooter_conf_t;
 147 | 
 148 | /**
 149 |  * Configuration structure for the fancyindex module. The configuration
 150 |  * commands defined in the module do fill in the members of this structure.
 151 |  */
 152 | typedef struct {
 153 |     ngx_flag_t enable;         /**< Module is enabled. */
 154 |     ngx_uint_t default_sort;   /**< Default sort criterion. */
 155 |     ngx_flag_t case_sensitive; /**< Case-sensitive name sorting */
 156 |     ngx_flag_t dirs_first;     /**< Group directories together first when sorting */
 157 |     ngx_flag_t localtime;      /**< File mtime dates are sent in local time. */
 158 |     ngx_flag_t exact_size;     /**< Sizes are sent always in bytes. */
 159 |     ngx_flag_t hide_symlinks;  /**< Hide symbolic links in listings. */
 160 |     ngx_flag_t show_path;      /**< Whether to display or not the path + '' after the header */
 161 |     ngx_flag_t hide_parent;    /**< Hide parent directory. */
 162 |     ngx_flag_t show_dot_files; /**< Show files that start with a dot.*/
 163 | 
 164 |     ngx_str_t  css_href;       /**< Link to a CSS stylesheet, or empty if none. */
 165 |     ngx_str_t  time_format;    /**< Format used for file timestamps. */
 166 | 
 167 |     ngx_array_t *ignore;       /**< List of files to ignore in listings. */
 168 | 
 169 |     ngx_fancyindex_headerfooter_conf_t header;
 170 |     ngx_fancyindex_headerfooter_conf_t footer;
 171 | } ngx_http_fancyindex_loc_conf_t;
 172 | 
 173 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME       0
 174 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE       1
 175 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE       2
 176 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC  3
 177 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC  4
 178 | #define NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC  5
 179 | 
 180 | static ngx_conf_enum_t ngx_http_fancyindex_sort_criteria[] = {
 181 |     { ngx_string("name"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME },
 182 |     { ngx_string("size"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE },
 183 |     { ngx_string("date"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE },
 184 |     { ngx_string("name_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC },
 185 |     { ngx_string("size_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC },
 186 |     { ngx_string("date_desc"), NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC },
 187 |     { ngx_null_string, 0 }
 188 | };
 189 | 
 190 | enum {
 191 |     NGX_HTTP_FANCYINDEX_HEADERFOOTER_SUBREQUEST,
 192 |     NGX_HTTP_FANCYINDEX_HEADERFOOTER_LOCAL,
 193 | };
 194 | 
 195 | static ngx_uint_t
 196 | headerfooter_kind(const ngx_str_t *value)
 197 | {
 198 |     static const struct {
 199 |         ngx_str_t name;
 200 |         ngx_uint_t value;
 201 |     } values[] = {
 202 |         { ngx_string("subrequest"), NGX_HTTP_FANCYINDEX_HEADERFOOTER_SUBREQUEST },
 203 |         { ngx_string("local"), NGX_HTTP_FANCYINDEX_HEADERFOOTER_LOCAL },
 204 |     };
 205 | 
 206 |     unsigned i;
 207 | 
 208 |     for (i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
 209 |         if (value->len == values[i].name.len &&
 210 |             ngx_strcasecmp(value->data, values[i].name.data) == 0)
 211 |         {
 212 |             return values[i].value;
 213 |         }
 214 |     }
 215 | 
 216 |     return NGX_CONF_UNSET_UINT;
 217 | }
 218 | 
 219 | static char*
 220 | ngx_fancyindex_conf_set_headerfooter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 221 | {
 222 |     ngx_fancyindex_headerfooter_conf_t *item =
 223 |         (void*) (((char*) conf) + cmd->offset);
 224 |     ngx_str_t *values = cf->args->elts;
 225 | 
 226 |     if (item->path.data)
 227 |         return "is duplicate";
 228 | 
 229 |     item->path = values[1];
 230 | 
 231 |     /* Kind of path. Default is "subrequest". */
 232 |     ngx_uint_t kind = NGX_HTTP_FANCYINDEX_HEADERFOOTER_SUBREQUEST;
 233 |     if (cf->args->nelts == 3) {
 234 |         kind = headerfooter_kind(&values[2]);
 235 |         if (kind == NGX_CONF_UNSET_UINT) {
 236 |             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
 237 |                                "unknown header/footer kind \"%V\"", &values[2]);
 238 |             return NGX_CONF_ERROR;
 239 |         }
 240 |     }
 241 | 
 242 |     if (kind == NGX_HTTP_FANCYINDEX_HEADERFOOTER_LOCAL) {
 243 |         ngx_file_t file;
 244 |         ngx_file_info_t fi;
 245 |         ssize_t n;
 246 | 
 247 |         ngx_memzero(&file, sizeof(ngx_file_t));
 248 |         file.log = cf->log;
 249 |         file.fd = ngx_open_file(item->path.data, NGX_FILE_RDONLY, 0, 0);
 250 |         if (file.fd == NGX_INVALID_FILE) {
 251 |             ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
 252 |                                "cannot open file \"%V\"", &values[1]);
 253 |             return NGX_CONF_ERROR;
 254 |         }
 255 | 
 256 |         if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
 257 |             ngx_close_file(file.fd);
 258 |             ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
 259 |                                "cannot get info for file \"%V\"", &values[1]);
 260 |             return NGX_CONF_ERROR;
 261 |         }
 262 | 
 263 |         item->local.len = ngx_file_size(&fi);
 264 |         item->local.data = ngx_pcalloc(cf->pool, item->local.len + 1);
 265 |         if (item->local.data == NULL) {
 266 |             ngx_close_file(file.fd);
 267 |             return NGX_CONF_ERROR;
 268 |         }
 269 | 
 270 |         n = item->local.len;
 271 |         while (n > 0) {
 272 |             ssize_t r = ngx_read_file(&file,
 273 |                                       item->local.data + file.offset,
 274 |                                       n,
 275 |                                       file.offset);
 276 |             if (r == NGX_ERROR) {
 277 |                 ngx_close_file(file.fd);
 278 |                 ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
 279 |                                    "cannot read file \"%V\"", &values[1]);
 280 |                 return NGX_CONF_ERROR;
 281 |             }
 282 | 
 283 |             n -= r;
 284 |         }
 285 |         item->local.data[item->local.len] = '\0';
 286 |     }
 287 | 
 288 |     return NGX_CONF_OK;
 289 | }
 290 | 
 291 | #define NGX_HTTP_FANCYINDEX_PREALLOCATE  50
 292 | 
 293 | 
 294 | /**
 295 |  * Calculates the length of a NULL-terminated string. It is ugly having to
 296 |  * remember to substract 1 from the sizeof result.
 297 |  */
 298 | #define ngx_sizeof_ssz(_s)  (sizeof(_s) - 1)
 299 | 
 300 | /**
 301 |  * Compute the length of a statically allocated array
 302 |  */
 303 | #define DIM(x) (sizeof(x)/sizeof(*(x)))
 304 | 
 305 | /**
 306 |  * Copy a static zero-terminated string. Useful to output template
 307 |  * string pieces into a temporary buffer.
 308 |  */
 309 | #define ngx_cpymem_ssz(_p, _t) \
 310 | 	(ngx_cpymem((_p), (_t), sizeof(_t) - 1))
 311 | 
 312 | /**
 313 |  * Copy a ngx_str_t.
 314 |  */
 315 | #define ngx_cpymem_str(_p, _s) \
 316 | 	(ngx_cpymem((_p), (_s).data, (_s).len))
 317 | 
 318 | /**
 319 |  * Check whether a particular bit is set in a particular value.
 320 |  */
 321 | #define ngx_has_flag(_where, _what) \
 322 | 	(((_where) & (_what)) == (_what))
 323 | 
 324 | 
 325 | 
 326 | 
 327 | typedef struct {
 328 |     ngx_str_t      name;
 329 |     size_t         utf_len;
 330 |     ngx_uint_t     escape;
 331 |     ngx_uint_t     escape_html;
 332 |     ngx_uint_t     dir;
 333 |     time_t         mtime;
 334 |     off_t          size;
 335 | } ngx_http_fancyindex_entry_t;
 336 | 
 337 | 
 338 | 
 339 | static int ngx_libc_cdecl
 340 |     ngx_http_fancyindex_cmp_entries_name_cs_desc(const void *one, const void *two);
 341 | static int ngx_libc_cdecl
 342 |     ngx_http_fancyindex_cmp_entries_name_ci_desc(const void *one, const void *two);
 343 | static int ngx_libc_cdecl
 344 |     ngx_http_fancyindex_cmp_entries_size_desc(const void *one, const void *two);
 345 | static int ngx_libc_cdecl
 346 |     ngx_http_fancyindex_cmp_entries_mtime_desc(const void *one, const void *two);
 347 | static int ngx_libc_cdecl
 348 |     ngx_http_fancyindex_cmp_entries_name_cs_asc(const void *one, const void *two);
 349 | static int ngx_libc_cdecl
 350 |     ngx_http_fancyindex_cmp_entries_name_ci_asc(const void *one, const void *two);
 351 | static int ngx_libc_cdecl
 352 |     ngx_http_fancyindex_cmp_entries_size_asc(const void *one, const void *two);
 353 | static int ngx_libc_cdecl
 354 |     ngx_http_fancyindex_cmp_entries_mtime_asc(const void *one, const void *two);
 355 | 
 356 | static ngx_int_t ngx_http_fancyindex_error(ngx_http_request_t *r,
 357 |     ngx_dir_t *dir, ngx_str_t *name);
 358 | 
 359 | static ngx_int_t ngx_http_fancyindex_init(ngx_conf_t *cf);
 360 | 
 361 | static void *ngx_http_fancyindex_create_loc_conf(ngx_conf_t *cf);
 362 | 
 363 | static char *ngx_http_fancyindex_merge_loc_conf(ngx_conf_t *cf,
 364 |     void *parent, void *child);
 365 | 
 366 | static char *ngx_http_fancyindex_ignore(ngx_conf_t    *cf,
 367 |                                         ngx_command_t *cmd,
 368 |                                         void          *conf);
 369 | 
 370 | static uintptr_t
 371 |     ngx_fancyindex_escape_filename(u_char *dst, u_char*src, size_t size);
 372 | 
 373 | /*
 374 |  * These are used only once per handler invocation. We can tell GCC to
 375 |  * inline them always, if possible (see how ngx_force_inline is defined
 376 |  * above).
 377 |  */
 378 | static ngx_inline ngx_buf_t*
 379 |     make_header_buf(ngx_http_request_t *r, const ngx_str_t css_href)
 380 |     ngx_force_inline;
 381 | 
 382 | 
 383 | static ngx_command_t  ngx_http_fancyindex_commands[] = {
 384 | 
 385 |     { ngx_string("fancyindex"),
 386 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 387 |       ngx_conf_set_flag_slot,
 388 |       NGX_HTTP_LOC_CONF_OFFSET,
 389 |       offsetof(ngx_http_fancyindex_loc_conf_t, enable),
 390 |       NULL },
 391 | 
 392 |     { ngx_string("fancyindex_default_sort"),
 393 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
 394 |       ngx_conf_set_enum_slot,
 395 |       NGX_HTTP_LOC_CONF_OFFSET,
 396 |       offsetof(ngx_http_fancyindex_loc_conf_t, default_sort),
 397 |       &ngx_http_fancyindex_sort_criteria },
 398 | 
 399 |     { ngx_string("fancyindex_case_sensitive"),
 400 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 401 |       ngx_conf_set_flag_slot,
 402 |       NGX_HTTP_LOC_CONF_OFFSET,
 403 |       offsetof(ngx_http_fancyindex_loc_conf_t, case_sensitive),
 404 |       NULL },
 405 | 
 406 |     { ngx_string("fancyindex_directories_first"),
 407 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 408 |       ngx_conf_set_flag_slot,
 409 |       NGX_HTTP_LOC_CONF_OFFSET,
 410 |       offsetof(ngx_http_fancyindex_loc_conf_t, dirs_first),
 411 |       NULL },
 412 | 
 413 |     { ngx_string("fancyindex_localtime"),
 414 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 415 |       ngx_conf_set_flag_slot,
 416 |       NGX_HTTP_LOC_CONF_OFFSET,
 417 |       offsetof(ngx_http_fancyindex_loc_conf_t, localtime),
 418 |       NULL },
 419 | 
 420 |     { ngx_string("fancyindex_exact_size"),
 421 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 422 |       ngx_conf_set_flag_slot,
 423 |       NGX_HTTP_LOC_CONF_OFFSET,
 424 |       offsetof(ngx_http_fancyindex_loc_conf_t, exact_size),
 425 |       NULL },
 426 | 
 427 |     { ngx_string("fancyindex_header"),
 428 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
 429 |       ngx_fancyindex_conf_set_headerfooter,
 430 |       NGX_HTTP_LOC_CONF_OFFSET,
 431 |       offsetof(ngx_http_fancyindex_loc_conf_t, header),
 432 |       NULL },
 433 | 
 434 |     { ngx_string("fancyindex_footer"),
 435 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
 436 |       ngx_fancyindex_conf_set_headerfooter,
 437 |       NGX_HTTP_LOC_CONF_OFFSET,
 438 |       offsetof(ngx_http_fancyindex_loc_conf_t, footer),
 439 |       NULL },
 440 | 
 441 |     { ngx_string("fancyindex_css_href"),
 442 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 443 |       ngx_conf_set_str_slot,
 444 |       NGX_HTTP_LOC_CONF_OFFSET,
 445 |       offsetof(ngx_http_fancyindex_loc_conf_t, css_href),
 446 |       NULL },
 447 | 
 448 |     { ngx_string("fancyindex_ignore"),
 449 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
 450 |       ngx_http_fancyindex_ignore,
 451 |       NGX_HTTP_LOC_CONF_OFFSET,
 452 |       0,
 453 |       NULL },
 454 | 
 455 |     { ngx_string("fancyindex_hide_symlinks"),
 456 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 457 |       ngx_conf_set_flag_slot,
 458 |       NGX_HTTP_LOC_CONF_OFFSET,
 459 |       offsetof(ngx_http_fancyindex_loc_conf_t, hide_symlinks),
 460 |       NULL },
 461 | 
 462 |     { ngx_string("fancyindex_show_path"),
 463 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 464 |       ngx_conf_set_flag_slot,
 465 |       NGX_HTTP_LOC_CONF_OFFSET,
 466 |       offsetof(ngx_http_fancyindex_loc_conf_t, show_path),
 467 |       NULL },
 468 | 
 469 |     { ngx_string("fancyindex_show_dotfiles"),
 470 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 471 |       ngx_conf_set_flag_slot,
 472 |       NGX_HTTP_LOC_CONF_OFFSET,
 473 |       offsetof(ngx_http_fancyindex_loc_conf_t, show_dot_files),
 474 |       NULL },
 475 | 
 476 |     { ngx_string("fancyindex_hide_parent_dir"),
 477 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 478 |       ngx_conf_set_flag_slot,
 479 |       NGX_HTTP_LOC_CONF_OFFSET,
 480 |       offsetof(ngx_http_fancyindex_loc_conf_t, hide_parent),
 481 |       NULL },
 482 | 
 483 |     { ngx_string("fancyindex_time_format"),
 484 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
 485 |       ngx_conf_set_str_slot,
 486 |       NGX_HTTP_LOC_CONF_OFFSET,
 487 |       offsetof(ngx_http_fancyindex_loc_conf_t, time_format),
 488 |       NULL },
 489 | 
 490 |     ngx_null_command
 491 | };
 492 | 
 493 | 
 494 | static ngx_http_module_t  ngx_http_fancyindex_module_ctx = {
 495 |     NULL,                                  /* preconfiguration */
 496 |     ngx_http_fancyindex_init,              /* postconfiguration */
 497 | 
 498 |     NULL,                                  /* create main configuration */
 499 |     NULL,                                  /* init main configuration */
 500 | 
 501 |     NULL,                                  /* create server configuration */
 502 |     NULL,                                  /* merge server configuration */
 503 | 
 504 |     ngx_http_fancyindex_create_loc_conf,   /* create location configuration */
 505 |     ngx_http_fancyindex_merge_loc_conf     /* merge location configuration */
 506 | };
 507 | 
 508 | 
 509 | ngx_module_t  ngx_http_fancyindex_module = {
 510 |     NGX_MODULE_V1,
 511 |     &ngx_http_fancyindex_module_ctx,       /* module context */
 512 |     ngx_http_fancyindex_commands,          /* module directives */
 513 |     NGX_HTTP_MODULE,                       /* module type */
 514 |     NULL,                                  /* init master */
 515 |     NULL,                                  /* init module */
 516 |     NULL,                                  /* init process */
 517 |     NULL,                                  /* init thread */
 518 |     NULL,                                  /* exit thread */
 519 |     NULL,                                  /* exit process */
 520 |     NULL,                                  /* exit master */
 521 |     NGX_MODULE_V1_PADDING
 522 | };
 523 | 
 524 | 
 525 | 
 526 | static const ngx_str_t css_href_pre =
 527 |     ngx_string("\n");
 530 | 
 531 | 
 532 | #ifdef NGX_ESCAPE_URI_COMPONENT
 533 | static inline uintptr_t
 534 | ngx_fancyindex_escape_filename(u_char *dst, u_char *src, size_t size)
 535 | {
 536 |     return ngx_escape_uri(dst, src, size, NGX_ESCAPE_URI_COMPONENT);
 537 | }
 538 | #else /* !NGX_ESCAPE_URI_COMPONENT */
 539 | static uintptr_t
 540 | ngx_fancyindex_escape_filename(u_char *dst, u_char *src, size_t size)
 541 | {
 542 |     /*
 543 |      * The ngx_escape_uri() function will not escape colons or the
 544 |      * ? character, which signals the beginning of the query string.
 545 |      * So we handle those characters ourselves.
 546 |      *
 547 |      * TODO: Get rid of this once ngx_escape_uri() works as expected!
 548 |      */
 549 | 
 550 |     u_int escapes = 0;
 551 |     u_char *psrc = src;
 552 |     size_t psize = size;
 553 | 
 554 |     while (psize--) {
 555 |         switch (*psrc++) {
 556 |             case ':':
 557 |             case '?':
 558 |             case '[':
 559 |             case ']':
 560 |                 escapes++;
 561 |                 break;
 562 |         }
 563 |     }
 564 | 
 565 |     if (dst == NULL) {
 566 |         return escapes + ngx_escape_uri(NULL, src, size, NGX_ESCAPE_HTML);
 567 |     }
 568 |     else if (escapes == 0) {
 569 |         /* No need to do extra escaping, avoid the temporary buffer */
 570 |         return ngx_escape_uri(dst, src, size, NGX_ESCAPE_HTML);
 571 |     }
 572 |     else {
 573 |         uintptr_t uescapes = ngx_escape_uri(NULL, src, size, NGX_ESCAPE_HTML);
 574 |         size_t bufsz = size + 2 * uescapes;
 575 | 
 576 |         /*
 577 |          * GCC and CLANG both support stack-allocated variable length
 578 |          * arrays. Take advantage of that to avoid a malloc-free cycle.
 579 |          */
 580 | #if defined(__GNUC__) || defined(__clang__)
 581 |         u_char cbuf[bufsz];
 582 |         u_char *buf = cbuf;
 583 | #else  /* __GNUC__ || __clang__ */
 584 |         u_char *buf = (u_char*) malloc(sizeof(u_char) * bufsz);
 585 | #endif /* __GNUC__ || __clang__ */
 586 | 
 587 |         ngx_escape_uri(buf, src, size, NGX_ESCAPE_HTML);
 588 | 
 589 |         while (bufsz--) {
 590 |             switch (*buf) {
 591 |                 case ':':
 592 |                     *dst++ = '%';
 593 |                     *dst++ = '3';
 594 |                     *dst++ = 'A';
 595 |                     break;
 596 |                 case '?':
 597 |                     *dst++ = '%';
 598 |                     *dst++ = '3';
 599 |                     *dst++ = 'F';
 600 |                     break;
 601 |                 case '[':
 602 |                     *dst++ = '%';
 603 |                     *dst++ = '5';
 604 |                     *dst++ = 'B';
 605 |                     break;
 606 |                 case ']':
 607 |                     *dst++ = '%';
 608 |                     *dst++ = '5';
 609 |                     *dst++ = 'D';
 610 |                     break;
 611 |                 default:
 612 |                     *dst++ = *buf;
 613 |             }
 614 |             buf++;
 615 |         }
 616 | 
 617 | #if !defined(__GNUC__) && !defined(__clang__)
 618 |         free(buf);
 619 | #endif /* !__GNUC__ && !__clang__ */
 620 | 
 621 |         return escapes + uescapes;
 622 |     }
 623 | }
 624 | #endif /* NGX_ESCAPE_URI_COMPONENT */
 625 | 
 626 | 
 627 | static ngx_inline ngx_buf_t*
 628 | make_header_buf(ngx_http_request_t *r, const ngx_str_t css_href)
 629 | {
 630 |     ngx_buf_t *b;
 631 |     size_t blen = r->uri.len
 632 |         + ngx_sizeof_ssz(t01_head1)
 633 |         + ngx_sizeof_ssz(t02_head2)
 634 |         + ngx_sizeof_ssz(t03_head3)
 635 |         + ngx_sizeof_ssz(t04_body1)
 636 |         ;
 637 | 
 638 |     if (css_href.len) {
 639 |         blen += css_href_pre.len \
 640 |               + css_href.len \
 641 |               + css_href_post.len
 642 |               ;
 643 |     }
 644 | 
 645 |     if ((b = ngx_create_temp_buf(r->pool, blen)) == NULL)
 646 |         return NULL;
 647 | 
 648 |     b->last = ngx_cpymem_ssz(b->last, t01_head1);
 649 | 
 650 |     if (css_href.len) {
 651 |         b->last = ngx_cpymem_str(b->last, css_href_pre);
 652 |         b->last = ngx_cpymem_str(b->last, css_href);
 653 |         b->last = ngx_cpymem_str(b->last, css_href_post);
 654 |     }
 655 | 
 656 |     b->last = ngx_cpymem_ssz(b->last, t02_head2);
 657 |     b->last = ngx_cpymem_str(b->last, r->uri);
 658 |     b->last = ngx_cpymem_ssz(b->last, t03_head3);
 659 |     b->last = ngx_cpymem_ssz(b->last, t04_body1);
 660 | 
 661 |     return b;
 662 | }
 663 | 
 664 | 
 665 | static ngx_inline ngx_int_t
 666 | make_content_buf(
 667 |         ngx_http_request_t *r, ngx_buf_t **pb,
 668 |         ngx_http_fancyindex_loc_conf_t *alcf)
 669 | {
 670 |     ngx_http_fancyindex_entry_t *entry;
 671 | 
 672 |     int (*sort_cmp_func)(const void *, const void *);
 673 |     const char  *sort_url_args = "";
 674 | 
 675 |     off_t        length;
 676 |     size_t       len, root, allocated, escape_html;
 677 |     int64_t      multiplier;
 678 |     u_char      *filename, *last;
 679 |     ngx_tm_t     tm;
 680 |     ngx_array_t  entries;
 681 |     ngx_time_t  *tp;
 682 |     ngx_uint_t   i, j;
 683 |     ngx_str_t    path;
 684 |     ngx_dir_t    dir;
 685 |     ngx_buf_t   *b;
 686 | 
 687 |     static const char    *sizes[]  = { "EiB", "PiB", "TiB", "GiB", "MiB", "KiB", "B" };
 688 |     static const int64_t  exbibyte = 1024LL * 1024LL * 1024LL *
 689 |                                      1024LL * 1024LL * 1024LL;
 690 | 
 691 |     /*
 692 |      * NGX_DIR_MASK_LEN is lesser than NGX_HTTP_FANCYINDEX_PREALLOCATE
 693 |      */
 694 |     if ((last = ngx_http_map_uri_to_path(r, &path, &root,
 695 |                     NGX_HTTP_FANCYINDEX_PREALLOCATE)) == NULL)
 696 |         return NGX_HTTP_INTERNAL_SERVER_ERROR;
 697 | 
 698 |     allocated = path.len;
 699 |     path.len = last - path.data;
 700 |     if (path.len > 1) {
 701 |         path.len--;
 702 |     }
 703 |     path.data[path.len] = '\0';
 704 | 
 705 |     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
 706 |                    "http fancyindex: \"%s\"", path.data);
 707 | 
 708 |     if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
 709 |         ngx_int_t rc, err = ngx_errno;
 710 |         ngx_uint_t level;
 711 | 
 712 |         if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
 713 |             level = NGX_LOG_ERR;
 714 |             rc = NGX_HTTP_NOT_FOUND;
 715 |         } else if (err == NGX_EACCES) {
 716 |             level = NGX_LOG_ERR;
 717 |             rc = NGX_HTTP_FORBIDDEN;
 718 |         } else {
 719 |             level = NGX_LOG_CRIT;
 720 |             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
 721 |         }
 722 | 
 723 |         ngx_log_error(level, r->connection->log, err,
 724 |                 ngx_open_dir_n " \"%s\" failed", path.data);
 725 | 
 726 |         return rc;
 727 |     }
 728 | 
 729 | #if (NGX_SUPPRESS_WARN)
 730 |     /* MSVC thinks 'entries' may be used without having been initialized */
 731 |     ngx_memzero(&entries, sizeof(ngx_array_t));
 732 | #endif /* NGX_SUPPRESS_WARN */
 733 | 
 734 | 
 735 |     if (ngx_array_init(&entries, r->pool, 40,
 736 |                 sizeof(ngx_http_fancyindex_entry_t)) != NGX_OK)
 737 |         return ngx_http_fancyindex_error(r, &dir, &path);
 738 | 
 739 |     filename = path.data;
 740 |     filename[path.len] = '/';
 741 | 
 742 |     /* Read directory entries and their associated information. */
 743 |     for (;;) {
 744 |         ngx_set_errno(0);
 745 | 
 746 |         if (ngx_read_dir(&dir) == NGX_ERROR) {
 747 |             ngx_int_t err = ngx_errno;
 748 | 
 749 |             if (err != NGX_ENOMOREFILES) {
 750 |                 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
 751 |                         ngx_read_dir_n " \"%V\" failed", &path);
 752 |                 return ngx_http_fancyindex_error(r, &dir, &path);
 753 |             }
 754 |             break;
 755 |         }
 756 | 
 757 |         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
 758 |                        "http fancyindex file: \"%s\"", ngx_de_name(&dir));
 759 | 
 760 |         len = ngx_de_namelen(&dir);
 761 | 
 762 |         if (!alcf->show_dot_files && ngx_de_name(&dir)[0] == '.')
 763 |             continue;
 764 | 
 765 |         if (alcf->hide_symlinks && ngx_de_is_link (&dir))
 766 |             continue;
 767 | 
 768 | #if NGX_PCRE
 769 |         {
 770 |             ngx_str_t str;
 771 |             str.len = len;
 772 |             str.data = ngx_de_name(&dir);
 773 | 
 774 |             if (alcf->ignore && ngx_regex_exec_array(alcf->ignore, &str,
 775 |                                                      r->connection->log)
 776 |                 != NGX_DECLINED)
 777 |             {
 778 |                 continue;
 779 |             }
 780 |         }
 781 | #else /* !NGX_PCRE */
 782 |         if (alcf->ignore) {
 783 |             u_int match_found = 0;
 784 |             ngx_str_t *s = alcf->ignore->elts;
 785 | 
 786 |             for (i = 0; i < alcf->ignore->nelts; i++, s++) {
 787 |                 if (ngx_strcmp(ngx_de_name(&dir), s->data) == 0) {
 788 |                     match_found = 1;
 789 |                     break;
 790 |                 }
 791 |             }
 792 | 
 793 |             if (match_found) {
 794 |                 continue;
 795 |             }
 796 |         }
 797 | #endif /* NGX_PCRE */
 798 | 
 799 |         if (!dir.valid_info) {
 800 |             /* 1 byte for '/' and 1 byte for terminating '\0' */
 801 |             if (path.len + 1 + len + 1 > allocated) {
 802 |                 allocated = path.len + 1 + len + 1
 803 |                           + NGX_HTTP_FANCYINDEX_PREALLOCATE;
 804 | 
 805 |                 if ((filename = ngx_palloc(r->pool, allocated)) == NULL)
 806 |                     return ngx_http_fancyindex_error(r, &dir, &path);
 807 | 
 808 |                 last = ngx_cpystrn(filename, path.data, path.len + 1);
 809 |                 *last++ = '/';
 810 |             }
 811 | 
 812 |             ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
 813 | 
 814 |             if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
 815 |                 ngx_int_t err = ngx_errno;
 816 | 
 817 |                 if (err != NGX_ENOENT) {
 818 |                     ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
 819 |                             ngx_de_info_n " \"%s\" failed", filename);
 820 |                     continue;
 821 |                 }
 822 | 
 823 |                 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
 824 |                     ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
 825 |                             ngx_de_link_info_n " \"%s\" failed", filename);
 826 |                     return ngx_http_fancyindex_error(r, &dir, &path);
 827 |                 }
 828 |             }
 829 |         }
 830 | 
 831 |         if ((entry = ngx_array_push(&entries)) == NULL)
 832 |             return ngx_http_fancyindex_error(r, &dir, &path);
 833 | 
 834 |         entry->name.len  = len;
 835 |         entry->name.data = ngx_palloc(r->pool, len + 1);
 836 |         if (entry->name.data == NULL)
 837 |             return ngx_http_fancyindex_error(r, &dir, &path);
 838 | 
 839 |         ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
 840 |         entry->escape = 2 * ngx_fancyindex_escape_filename(NULL,
 841 |                                                            ngx_de_name(&dir),
 842 |                                                            len);
 843 |         entry->escape_html = ngx_escape_html(NULL,
 844 |                                              entry->name.data,
 845 |                                              entry->name.len);
 846 | 
 847 |         entry->dir     = ngx_de_is_dir(&dir);
 848 |         entry->mtime   = ngx_de_mtime(&dir);
 849 |         entry->size    = ngx_de_size(&dir);
 850 |         entry->utf_len = (r->headers_out.charset.len == 5 &&
 851 |                 ngx_strncasecmp(r->headers_out.charset.data, (u_char*) "utf-8", 5) == 0)
 852 |             ?  ngx_utf8_length(entry->name.data, entry->name.len)
 853 |             : len;
 854 |     }
 855 | 
 856 |     if (ngx_close_dir(&dir) == NGX_ERROR) {
 857 |         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
 858 |                 ngx_close_dir_n " \"%s\" failed", &path);
 859 |     }
 860 | 
 861 |     /*
 862 |      * Calculate needed buffer length.
 863 |      */
 864 | 
 865 |     escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
 866 | 
 867 |     if (alcf->show_path)
 868 |         len = r->uri.len + escape_html
 869 |           + ngx_sizeof_ssz(t05_body2)
 870 |           + ngx_sizeof_ssz(t06_list1)
 871 |           + ngx_sizeof_ssz(t_parentdir_entry)
 872 |           + ngx_sizeof_ssz(t07_list2)
 873 |           + ngx_fancyindex_timefmt_calc_size (&alcf->time_format) * entries.nelts
 874 |           ;
 875 |    else
 876 |         len = r->uri.len + escape_html
 877 |           + ngx_sizeof_ssz(t06_list1)
 878 |           + ngx_sizeof_ssz(t_parentdir_entry)
 879 |           + ngx_sizeof_ssz(t07_list2)
 880 |           + ngx_fancyindex_timefmt_calc_size (&alcf->time_format) * entries.nelts
 881 |           ;
 882 | 
 883 |     /*
 884 |      * If we are a the root of the webserver (URI =  "/" --> length of 1),
 885 |      * do not display the "Parent Directory" link.
 886 |      */
 887 |     if (r->uri.len == 1) {
 888 |         len -= ngx_sizeof_ssz(t_parentdir_entry);
 889 |     }
 890 | 
 891 |     entry = entries.elts;
 892 |     for (i = 0; i < entries.nelts; i++) {
 893 |         /*
 894 |          * Genearated table rows are as follows, unneeded whitespace
 895 |          * is stripped out:
 896 |          *
 897 |          *   
 898 |          *     fname
 899 |          *     sizedate
 900 |          *   
 901 |          */
 902 |         len += ngx_sizeof_ssz("")
 908 |             + entry[i].name.len + entry[i].utf_len + entry[i].escape_html
 909 |             + ngx_sizeof_ssz("")
 910 |             + 20 /* File size */
 911 |             + ngx_sizeof_ssz("")    /* Date prefix */
 912 |             + ngx_sizeof_ssz("\n") /* Date suffix */
 913 |             + 2 /* CR LF */
 914 |             ;
 915 |     }
 916 | 
 917 |     if ((b = ngx_create_temp_buf(r->pool, len)) == NULL)
 918 |         return NGX_HTTP_INTERNAL_SERVER_ERROR;
 919 | 
 920 |     /*
 921 |      * Determine the sorting criteria. URL arguments look like:
 922 |      *
 923 |      *    C=x[&O=y]
 924 |      *
 925 |      * Where x={M,S,N} and y={A,D}
 926 |      */
 927 |     if ((r->args.len == 3 || (r->args.len == 7 && r->args.data[3] == '&')) &&
 928 |         r->args.data[0] == 'C' && r->args.data[1] == '=')
 929 |     {
 930 |         /* Determine whether the direction of the sorting */
 931 |         ngx_int_t sort_descending = r->args.len == 7
 932 |                                  && r->args.data[4] == 'O'
 933 |                                  && r->args.data[5] == '='
 934 |                                  && r->args.data[6] == 'D';
 935 | 
 936 |         /* Pick the sorting criteria */
 937 |         switch (r->args.data[2]) {
 938 |             case 'M': /* Sort by mtime */
 939 |                 if (sort_descending) {
 940 |                     sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_desc;
 941 |                     if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC)
 942 |                         sort_url_args = "?C=M&O=D";
 943 |                 }
 944 |                 else {
 945 |                     sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_asc;
 946 |                     if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE)
 947 |                         sort_url_args = "?C=M&O=A";
 948 |                 }
 949 |                 break;
 950 |             case 'S': /* Sort by size */
 951 |                 if (sort_descending) {
 952 |                     sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_desc;
 953 |                     if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC)
 954 |                         sort_url_args = "?C=S&O=D";
 955 |                 }
 956 |                 else {
 957 |                     sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_asc;
 958 |                         if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE)
 959 |                     sort_url_args = "?C=S&O=A";
 960 |                 }
 961 |                 break;
 962 |             case 'N': /* Sort by name */
 963 |             default:
 964 |                 if (sort_descending) {
 965 |                     sort_cmp_func = alcf->case_sensitive
 966 |                         ? ngx_http_fancyindex_cmp_entries_name_cs_desc
 967 |                         : ngx_http_fancyindex_cmp_entries_name_ci_desc;
 968 |                     if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC)
 969 |                         sort_url_args = "?C=N&O=D";
 970 |                 }
 971 |                 else {
 972 |                     sort_cmp_func = alcf->case_sensitive
 973 |                         ? ngx_http_fancyindex_cmp_entries_name_cs_asc
 974 |                         : ngx_http_fancyindex_cmp_entries_name_ci_asc;
 975 |                     if (alcf->default_sort != NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME)
 976 |                         sort_url_args = "?C=N&O=A";
 977 |                 }
 978 |                 break;
 979 |         }
 980 |     }
 981 |     else {
 982 |         switch (alcf->default_sort) {
 983 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE_DESC:
 984 |                 sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_desc;
 985 |                 break;
 986 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_DATE:
 987 |                 sort_cmp_func = ngx_http_fancyindex_cmp_entries_mtime_asc;
 988 |                 break;
 989 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE_DESC:
 990 |                 sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_desc;
 991 |                 break;
 992 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_SIZE:
 993 |                 sort_cmp_func = ngx_http_fancyindex_cmp_entries_size_asc;
 994 |                 break;
 995 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME_DESC:
 996 |                 sort_cmp_func = alcf->case_sensitive
 997 |                     ? ngx_http_fancyindex_cmp_entries_name_cs_desc
 998 |                     : ngx_http_fancyindex_cmp_entries_name_ci_desc;
 999 |                 break;
1000 |             case NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME:
1001 |             default:
1002 |                 sort_cmp_func = alcf->case_sensitive
1003 |                     ? ngx_http_fancyindex_cmp_entries_name_cs_asc
1004 |                     : ngx_http_fancyindex_cmp_entries_name_ci_asc;
1005 |                 break;
1006 |         }
1007 |     }
1008 | 
1009 |     /* Sort entries, if needed */
1010 |     if (entries.nelts > 1) {
1011 |         if (alcf->dirs_first)
1012 |         {
1013 |             ngx_http_fancyindex_entry_t *l, *r;
1014 | 
1015 |             l = entry;
1016 |             r = entry + entries.nelts - 1;
1017 |             while (l < r)
1018 |             {
1019 |                 while (l < r && l->dir)
1020 |                     l++;
1021 |                 while (l < r && !r->dir)
1022 |                     r--;
1023 |                 if (l < r) {
1024 |                     /* Now l points a file while r points a directory */
1025 |                     ngx_http_fancyindex_entry_t tmp;
1026 |                     tmp = *l;
1027 |                     *l = *r;
1028 |                     *r = tmp;
1029 |                 }
1030 |             }
1031 |             if (r->dir)
1032 |                 r++;
1033 | 
1034 |             if (r > entry)
1035 |                 /* Sort directories */
1036 |                 ngx_qsort(entry, (size_t)(r - entry),
1037 |                         sizeof(ngx_http_fancyindex_entry_t), sort_cmp_func);
1038 |             if (r < entry + entries.nelts)
1039 |                 /* Sort files */
1040 |                 ngx_qsort(r, (size_t)(entry + entries.nelts - r),
1041 |                         sizeof(ngx_http_fancyindex_entry_t), sort_cmp_func);
1042 |         } else {
1043 |             ngx_qsort(entry, (size_t)entries.nelts,
1044 |                     sizeof(ngx_http_fancyindex_entry_t), sort_cmp_func);
1045 |         }
1046 |     }
1047 | 
1048 |     /* Display the path, if needed */
1049 |     if (alcf->show_path){
1050 |         b->last = last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
1051 |         b->last = ngx_cpymem_ssz(b->last, t05_body2);
1052 |     }
1053 | 
1054 |     /* Open the  tag */
1055 |     b->last = ngx_cpymem_ssz(b->last, t06_list1);
1056 | 
1057 |     tp = ngx_timeofday();
1058 | 
1059 |     /* "Parent dir" entry, always first if displayed */
1060 |     if (r->uri.len > 1 && alcf->hide_parent == 0) {
1061 |         b->last = ngx_cpymem_ssz(b->last,
1062 |                                  ""
1063 |                                  ""
1071 |                                  ""
1072 |                                  ""
1073 |                                  ""
1074 |                                  CRLF);
1075 |     }
1076 | 
1077 |     /* Entries for directories and files */
1078 |     for (i = 0; i < entries.nelts; i++) {
1079 |         b->last = ngx_cpymem_ssz(b->last, "");
1149 | 
1150 |         *b->last++ = CR;
1151 |         *b->last++ = LF;
1152 |     }
1153 | 
1154 |     /* Output table bottom */
1155 |     b->last = ngx_cpymem_ssz(b->last, t07_list2);
1156 | 
1157 |     *pb = b;
1158 |     return NGX_OK;
1159 | }
1160 | 
1161 | 
1162 | 
1163 | static ngx_int_t
1164 | ngx_http_fancyindex_handler(ngx_http_request_t *r)
1165 | {
1166 |     ngx_http_request_t             *sr;
1167 |     ngx_str_t                      *sr_uri;
1168 |     ngx_str_t                       rel_uri;
1169 |     ngx_int_t                       rc;
1170 |     ngx_http_fancyindex_loc_conf_t *alcf;
1171 |     ngx_chain_t                     out[3] = {
1172 |         { NULL, NULL }, { NULL, NULL}, { NULL, NULL }};
1173 | 
1174 | 
1175 |     if (r->uri.data[r->uri.len - 1] != '/') {
1176 |         return NGX_DECLINED;
1177 |     }
1178 | 
1179 |     /* TODO: Win32 */
1180 | #if defined(nginx_version) \
1181 |     && ((nginx_version < 7066) \
1182 |         || ((nginx_version > 8000) && (nginx_version < 8038)))
1183 |     if (r->zero_in_uri) {
1184 |         return NGX_DECLINED;
1185 |     }
1186 | #endif
1187 | 
1188 |     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
1189 |         return NGX_DECLINED;
1190 |     }
1191 | 
1192 |     alcf = ngx_http_get_module_loc_conf(r, ngx_http_fancyindex_module);
1193 | 
1194 |     if (!alcf->enable) {
1195 |         return NGX_DECLINED;
1196 |     }
1197 | 
1198 |     if ((rc = make_content_buf(r, &out[0].buf, alcf)) != NGX_OK)
1199 |         return rc;
1200 | 
1201 |     out[0].buf->last_in_chain = 1;
1202 | 
1203 |     r->headers_out.status = NGX_HTTP_OK;
1204 |     r->headers_out.content_type_len  = ngx_sizeof_ssz("text/html");
1205 |     r->headers_out.content_type.len  = ngx_sizeof_ssz("text/html");
1206 |     r->headers_out.content_type.data = (u_char *) "text/html";
1207 | 
1208 |     rc = ngx_http_send_header(r);
1209 |     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
1210 |         return rc;
1211 | 
1212 |     if (alcf->header.path.len > 0 && alcf->header.local.len == 0) {
1213 |         /* URI is configured, make Nginx take care of with a subrequest. */
1214 |         sr_uri = &alcf->header.path;
1215 | 
1216 |         if (*sr_uri->data != '/') {
1217 |             /* Relative path */
1218 |             rel_uri.len  = r->uri.len + alcf->header.path.len;
1219 |             rel_uri.data = ngx_palloc(r->pool, rel_uri.len);
1220 |             if (rel_uri.data == NULL) {
1221 |                 return NGX_HTTP_INTERNAL_SERVER_ERROR;
1222 |             }
1223 |             ngx_memcpy(ngx_cpymem(rel_uri.data, r->uri.data, r->uri.len),
1224 |                     alcf->header.path.data, alcf->header.path.len);
1225 |             sr_uri = &rel_uri;
1226 |         }
1227 | 
1228 |         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1229 |                 "http fancyindex: header subrequest \"%V\"", sr_uri);
1230 | 
1231 |         rc = ngx_http_subrequest(r, sr_uri, NULL, &sr, NULL, 0);
1232 |         if (rc == NGX_ERROR || rc == NGX_DONE) {
1233 |             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1234 |                     "http fancyindex: header subrequest for \"%V\" failed", sr_uri);
1235 |             return rc;
1236 |         }
1237 | 
1238 |         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1239 |                 "http fancyindex: header subrequest status = %i",
1240 |                 sr->headers_out.status);
1241 |         /* ngx_http_subrequest returns NGX_OK(0), not NGX_HTTP_OK(200) */
1242 |         if (sr->headers_out.status != NGX_OK) {
1243 |             /*
1244 |              * XXX: Should we write a message to the error log just in case
1245 |              * we get something different from a 404?
1246 |              */
1247 |             goto add_builtin_header;
1248 |         }
1249 |     }
1250 |     else {
1251 | add_builtin_header:
1252 |         /* Make space before */
1253 |         out[1].next = out[0].next;
1254 |         out[1].buf  = out[0].buf;
1255 |         /* Chain header buffer */
1256 |         out[0].next = &out[1];
1257 |         if (alcf->header.local.len > 0) {
1258 |             /* Header buffer is local, make a buffer pointing to the data. */
1259 |             out[0].buf = ngx_calloc_buf(r->pool);
1260 |             if (out[0].buf == NULL)
1261 |                 return NGX_ERROR;
1262 |             out[0].buf->memory = 1;
1263 |             out[0].buf->pos = alcf->header.local.data;
1264 |             out[0].buf->last = alcf->header.local.data + alcf->header.local.len;
1265 |         } else {
1266 |             /* Prepare a buffer with the contents of the builtin header. */
1267 |             out[0].buf = make_header_buf(r, alcf->css_href);
1268 |         }
1269 |     }
1270 | 
1271 |     /* If footer is disabled, chain up footer buffer. */
1272 |     if (alcf->footer.path.len == 0 || alcf->footer.local.len > 0) {
1273 |         ngx_uint_t last = (alcf->header.path.len == 0) ? 2 : 1;
1274 | 
1275 |         out[last-1].next = &out[last];
1276 |         out[last].buf = ngx_calloc_buf(r->pool);
1277 |         if (out[last].buf == NULL)
1278 |             return NGX_ERROR;
1279 | 
1280 |         out[last].buf->memory = 1;
1281 |         if (alcf->footer.local.len > 0) {
1282 |             out[last].buf->pos = alcf->footer.local.data;
1283 |             out[last].buf->last = alcf->footer.local.data + alcf->footer.local.len;
1284 |         } else {
1285 |             out[last].buf->pos = (u_char*) t08_foot1;
1286 |             out[last].buf->last = (u_char*) t08_foot1 + sizeof(t08_foot1) - 1;
1287 |         }
1288 | 
1289 |         out[last-1].buf->last_in_chain = 0;
1290 |         out[last].buf->last_in_chain   = 1;
1291 |         out[last].buf->last_buf        = 1;
1292 |         /* Send everything with a single call :D */
1293 |         return ngx_http_output_filter(r, &out[0]);
1294 |     }
1295 | 
1296 |     /*
1297 |      * If we reach here, we were asked to send a custom footer. We need to:
1298 |      * partially send whatever is referenced from out[0] and then send the
1299 |      * footer as a subrequest. If the subrequest fails, we should send the
1300 |      * standard footer as well.
1301 |      */
1302 |     rc = ngx_http_output_filter(r, &out[0]);
1303 | 
1304 |     if (rc != NGX_OK && rc != NGX_AGAIN)
1305 |         return NGX_HTTP_INTERNAL_SERVER_ERROR;
1306 | 
1307 |     /* URI is configured, make Nginx take care of with a subrequest. */
1308 |     sr_uri = &alcf->footer.path;
1309 | 
1310 |     if (*sr_uri->data != '/') {
1311 |         /* Relative path */
1312 |         rel_uri.len  = r->uri.len + alcf->footer.path.len;
1313 |         rel_uri.data = ngx_palloc(r->pool, rel_uri.len);
1314 |         if (rel_uri.data == NULL) {
1315 |             return NGX_HTTP_INTERNAL_SERVER_ERROR;
1316 |         }
1317 |         ngx_memcpy(ngx_cpymem(rel_uri.data, r->uri.data, r->uri.len),
1318 |                 alcf->footer.path.data, alcf->footer.path.len);
1319 |         sr_uri = &rel_uri;
1320 |     }
1321 | 
1322 |     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1323 |             "http fancyindex: footer subrequest \"%V\"", sr_uri);
1324 | 
1325 |     rc = ngx_http_subrequest(r, sr_uri, NULL, &sr, NULL, 0);
1326 |     if (rc == NGX_ERROR || rc == NGX_DONE) {
1327 |         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1328 |                 "http fancyindex: footer subrequest for \"%V\" failed", sr_uri);
1329 |         return rc;
1330 |     }
1331 | 
1332 |     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1333 |             "http fancyindex: header subrequest status = %i",
1334 |             sr->headers_out.status);
1335 | 
1336 |     /* see above: ngx_http_subrequest resturns NGX_OK (0) not NGX_HTTP_OK (200) */
1337 |     if (sr->headers_out.status != NGX_OK) {
1338 |         /*
1339 |          * XXX: Should we write a message to the error log just in case
1340 |          * we get something different from a 404?
1341 |          */
1342 |         out[0].next = NULL;
1343 |         out[0].buf = ngx_calloc_buf(r->pool);
1344 |         if (out[0].buf == NULL)
1345 |             return NGX_ERROR;
1346 |         out[0].buf->memory = 1;
1347 |         out[0].buf->pos = (u_char*) t08_foot1;
1348 |         out[0].buf->last = (u_char*) t08_foot1 + sizeof(t08_foot1) - 1;
1349 |         out[0].buf->last_in_chain = 1;
1350 |         out[0].buf->last_buf = 1;
1351 |         /* Directly send out the builtin footer */
1352 |         return ngx_http_output_filter(r, &out[0]);
1353 |     }
1354 | 
1355 |     return (r != r->main) ? rc : ngx_http_send_special(r, NGX_HTTP_LAST);
1356 | }
1357 | 
1358 | 
1359 | static int ngx_libc_cdecl
1360 | ngx_http_fancyindex_cmp_entries_name_cs_desc(const void *one, const void *two)
1361 | {
1362 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1363 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1364 | 
1365 |     return (int) ngx_strcmp(second->name.data, first->name.data);
1366 | }
1367 | 
1368 | 
1369 | static int ngx_libc_cdecl
1370 | ngx_http_fancyindex_cmp_entries_name_ci_desc(const void *one, const void *two)
1371 | {
1372 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1373 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1374 | 
1375 |     return (int) ngx_strcasecmp(second->name.data, first->name.data);
1376 | }
1377 | 
1378 | 
1379 | static int ngx_libc_cdecl
1380 | ngx_http_fancyindex_cmp_entries_size_desc(const void *one, const void *two)
1381 | {
1382 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1383 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1384 | 
1385 |     return (first->size < second->size) - (first->size > second->size);
1386 | }
1387 | 
1388 | 
1389 | static int ngx_libc_cdecl
1390 | ngx_http_fancyindex_cmp_entries_mtime_desc(const void *one, const void *two)
1391 | {
1392 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1393 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1394 | 
1395 |     return (int) (second->mtime - first->mtime);
1396 | }
1397 | 
1398 | 
1399 | static int ngx_libc_cdecl
1400 | ngx_http_fancyindex_cmp_entries_name_cs_asc(const void *one, const void *two)
1401 | {
1402 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1403 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1404 | 
1405 |     return (int) ngx_strcmp(first->name.data, second->name.data);
1406 | }
1407 | 
1408 | 
1409 | static int ngx_libc_cdecl
1410 | ngx_http_fancyindex_cmp_entries_name_ci_asc(const void *one, const void *two)
1411 | {
1412 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1413 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1414 | 
1415 |     return (int) ngx_strcasecmp(first->name.data, second->name.data);
1416 | }
1417 | 
1418 | 
1419 | static int ngx_libc_cdecl
1420 | ngx_http_fancyindex_cmp_entries_size_asc(const void *one, const void *two)
1421 | {
1422 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1423 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1424 | 
1425 |     return (first->size > second->size) - (first->size < second->size);
1426 | }
1427 | 
1428 | 
1429 | static int ngx_libc_cdecl
1430 | ngx_http_fancyindex_cmp_entries_mtime_asc(const void *one, const void *two)
1431 | {
1432 |     ngx_http_fancyindex_entry_t *first = (ngx_http_fancyindex_entry_t *) one;
1433 |     ngx_http_fancyindex_entry_t *second = (ngx_http_fancyindex_entry_t *) two;
1434 | 
1435 |     return (int) (first->mtime - second->mtime);
1436 | }
1437 | 
1438 | 
1439 | static ngx_int_t
1440 | ngx_http_fancyindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
1441 | {
1442 |     if (ngx_close_dir(dir) == NGX_ERROR) {
1443 |         ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
1444 |                       ngx_close_dir_n " \"%V\" failed", name);
1445 |     }
1446 | 
1447 |     return NGX_HTTP_INTERNAL_SERVER_ERROR;
1448 | }
1449 | 
1450 | 
1451 | static void *
1452 | ngx_http_fancyindex_create_loc_conf(ngx_conf_t *cf)
1453 | {
1454 |     ngx_http_fancyindex_loc_conf_t  *conf;
1455 | 
1456 |     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fancyindex_loc_conf_t));
1457 |     if (conf == NULL) {
1458 |         return NGX_CONF_ERROR;
1459 |     }
1460 | 
1461 |     /*
1462 |      * Set by ngx_pcalloc:
1463 |      *    conf->header.*.len     = 0
1464 |      *    conf->header.*.data    = NULL
1465 |      *    conf->footer.*.len     = 0
1466 |      *    conf->footer.*.data    = NULL
1467 |      *    conf->css_href.len     = 0
1468 |      *    conf->css_href.data    = NULL
1469 |      *    conf->time_format.len  = 0
1470 |      *    conf->time_format.data = NULL
1471 |      */
1472 |     conf->enable         = NGX_CONF_UNSET;
1473 |     conf->default_sort   = NGX_CONF_UNSET_UINT;
1474 |     conf->case_sensitive = NGX_CONF_UNSET;
1475 |     conf->dirs_first     = NGX_CONF_UNSET;
1476 |     conf->localtime      = NGX_CONF_UNSET;
1477 |     conf->exact_size     = NGX_CONF_UNSET;
1478 |     conf->ignore         = NGX_CONF_UNSET_PTR;
1479 |     conf->hide_symlinks  = NGX_CONF_UNSET;
1480 |     conf->show_path      = NGX_CONF_UNSET;
1481 |     conf->hide_parent    = NGX_CONF_UNSET;
1482 |     conf->show_dot_files = NGX_CONF_UNSET;
1483 | 
1484 |     return conf;
1485 | }
1486 | 
1487 | 
1488 | static char *
1489 | ngx_http_fancyindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1490 | {
1491 |     ngx_http_fancyindex_loc_conf_t *prev = parent;
1492 |     ngx_http_fancyindex_loc_conf_t *conf = child;
1493 | 
1494 |     (void) cf; /* unused */
1495 | 
1496 |     ngx_conf_merge_value(conf->enable, prev->enable, 0);
1497 |     ngx_conf_merge_uint_value(conf->default_sort, prev->default_sort, NGX_HTTP_FANCYINDEX_SORT_CRITERION_NAME);
1498 |     ngx_conf_merge_value(conf->case_sensitive, prev->case_sensitive, 1);
1499 |     ngx_conf_merge_value(conf->dirs_first, prev->dirs_first, 1);
1500 |     ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
1501 |     ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
1502 |     ngx_conf_merge_value(conf->show_path, prev->show_path, 1);
1503 |     ngx_conf_merge_value(conf->show_dot_files, prev->show_dot_files, 0);
1504 | 
1505 |     ngx_conf_merge_str_value(conf->header.path, prev->header.path, "");
1506 |     ngx_conf_merge_str_value(conf->header.path, prev->header.local, "");
1507 |     ngx_conf_merge_str_value(conf->footer.path, prev->footer.path, "");
1508 |     ngx_conf_merge_str_value(conf->footer.path, prev->footer.local, "");
1509 | 
1510 |     ngx_conf_merge_str_value(conf->css_href, prev->css_href, "");
1511 |     ngx_conf_merge_str_value(conf->time_format, prev->time_format, "%Y-%b-%d %H:%M");
1512 | 
1513 |     ngx_conf_merge_ptr_value(conf->ignore, prev->ignore, NULL);
1514 |     ngx_conf_merge_value(conf->hide_symlinks, prev->hide_symlinks, 0);
1515 |     ngx_conf_merge_value(conf->hide_parent, prev->hide_parent, 0);
1516 | 
1517 |     /* Just make sure we haven't disabled the show_path directive without providing a custom header */
1518 |     if (conf->show_path == 0 && conf->header.path.len == 0)
1519 |     {
1520 |         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "FancyIndex : cannot set show_path to off without providing a custom header !");
1521 |         return NGX_CONF_ERROR;
1522 |     }
1523 | 
1524 |     return NGX_CONF_OK;
1525 | }
1526 | 
1527 | 
1528 | static char*
1529 | ngx_http_fancyindex_ignore(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1530 | {
1531 |     ngx_http_fancyindex_loc_conf_t *alcf = conf;
1532 |     ngx_str_t *value;
1533 | 
1534 |     (void) cmd; /* unused */
1535 | 
1536 | #if (NGX_PCRE)
1537 |     ngx_uint_t          i;
1538 |     ngx_regex_elt_t    *re;
1539 |     ngx_regex_compile_t rc;
1540 |     u_char              errstr[NGX_MAX_CONF_ERRSTR];
1541 | 
1542 |     if (alcf->ignore == NGX_CONF_UNSET_PTR) {
1543 |         alcf->ignore = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
1544 |         if (alcf->ignore == NULL) {
1545 |             return NGX_CONF_ERROR;
1546 |         }
1547 |     }
1548 | 
1549 |     value = cf->args->elts;
1550 | 
1551 |     ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
1552 | 
1553 |     rc.err.data = errstr;
1554 |     rc.err.len  = NGX_MAX_CONF_ERRSTR;
1555 |     rc.pool     = cf->pool;
1556 | 
1557 |     for (i = 1; i < cf->args->nelts; i++) {
1558 |         re = ngx_array_push(alcf->ignore);
1559 |         if (re == NULL) {
1560 |             return NGX_CONF_ERROR;
1561 |         }
1562 | 
1563 |         rc.pattern = value[i];
1564 |         rc.options = NGX_REGEX_CASELESS;
1565 | 
1566 |         if (ngx_regex_compile(&rc) != NGX_OK) {
1567 |             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
1568 |             return NGX_CONF_ERROR;
1569 |         }
1570 | 
1571 |         re->name  = value[i].data;
1572 |         re->regex = rc.regex;
1573 |     }
1574 | 
1575 |     return NGX_CONF_OK;
1576 | #else /* !NGX_PCRE */
1577 |     ngx_uint_t i;
1578 |     ngx_str_t *str;
1579 | 
1580 |     if (alcf->ignore == NGX_CONF_UNSET_PTR) {
1581 |         alcf->ignore = ngx_array_create(cf->pool, 2, sizeof(ngx_str_t));
1582 |         if (alcf->ignore == NULL) {
1583 |             return NGX_CONF_ERROR;
1584 |         }
1585 |     }
1586 | 
1587 |     value = cf->args->elts;
1588 | 
1589 |     for (i = 1; i < cf->args->nelts; i++) {
1590 |         str = ngx_array_push(alcf->ignore);
1591 |         if (str == NULL) {
1592 |             return NGX_CONF_ERROR;
1593 |         }
1594 | 
1595 |         str->data = value[i].data;
1596 |         str->len  = value[i].len;
1597 |     }
1598 | 
1599 |     return NGX_CONF_OK;
1600 | #endif /* NGX_PCRE */
1601 | 
1602 | }
1603 | 
1604 | 
1605 | static ngx_int_t
1606 | ngx_http_fancyindex_init(ngx_conf_t *cf)
1607 | {
1608 |     ngx_http_handler_pt        *h;
1609 |     ngx_http_core_main_conf_t  *cmcf;
1610 | 
1611 |     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
1612 | 
1613 |     h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
1614 |     if (h == NULL) {
1615 |         return NGX_ERROR;
1616 |     }
1617 | 
1618 |     *h = ngx_http_fancyindex_handler;
1619 | 
1620 |     return NGX_OK;
1621 | }
1622 | 
1623 | /* vim:et:sw=4:ts=4:
1624 |  */
1625 | 


--------------------------------------------------------------------------------
last = ngx_cpymem(b->last, 1066 | sort_url_args, 1067 | ngx_sizeof_ssz("?C=N&O=A")); 1068 | } 1069 | b->last = ngx_cpymem_ssz(b->last, 1070 | "\">Parent directory/--
last, 1083 | entry[i].name.data, 1084 | entry[i].name.len); 1085 | 1086 | b->last += entry[i].name.len + entry[i].escape; 1087 | 1088 | } else { 1089 | b->last = ngx_cpymem_str(b->last, entry[i].name); 1090 | } 1091 | 1092 | if (entry[i].dir) { 1093 | *b->last++ = '/'; 1094 | if (*sort_url_args) { 1095 | b->last = ngx_cpymem(b->last, 1096 | sort_url_args, 1097 | ngx_sizeof_ssz("?C=x&O=y")); 1098 | } 1099 | } 1100 | 1101 | *b->last++ = '"'; 1102 | b->last = ngx_cpymem_ssz(b->last, " title=\""); 1103 | b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data, entry[i].name.len); 1104 | *b->last++ = '"'; 1105 | *b->last++ = '>'; 1106 | 1107 | len = entry[i].utf_len; 1108 | 1109 | b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data, entry[i].name.len); 1110 | last = b->last - 3; 1111 | 1112 | if (entry[i].dir) { 1113 | *b->last++ = '/'; 1114 | len++; 1115 | } 1116 | 1117 | b->last = ngx_cpymem_ssz(b->last, ""); 1118 | 1119 | if (alcf->exact_size) { 1120 | if (entry[i].dir) { 1121 | *b->last++ = '-'; 1122 | } else { 1123 | b->last = ngx_sprintf(b->last, "%19O", entry[i].size); 1124 | } 1125 | 1126 | } else { 1127 | if (entry[i].dir) { 1128 | *b->last++ = '-'; 1129 | } else { 1130 | length = entry[i].size; 1131 | multiplier = exbibyte; 1132 | 1133 | for (j = 0; j < DIM(sizes) - 1 && length < multiplier; j++) 1134 | multiplier /= 1024; 1135 | 1136 | /* If we are showing the filesize in bytes, do not show a decimal */ 1137 | if (j == DIM(sizes) - 1) 1138 | b->last = ngx_sprintf(b->last, "%O %s", length, sizes[j]); 1139 | else 1140 | b->last = ngx_sprintf(b->last, "%.1f %s", 1141 | (float) length / multiplier, sizes[j]); 1142 | } 1143 | } 1144 | 1145 | ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm); 1146 | b->last = ngx_cpymem_ssz(b->last, ""); 1147 | b->last = ngx_fancyindex_timefmt(b->last, &alcf->time_format, &tm); 1148 | b->last = ngx_cpymem_ssz(b->last, "