From 3b9c090fd32da6d056251b1c446acd3d7fe84e58 Mon Sep 17 00:00:00 2001
From: Peter Wu <lekensteyn@gmail.com>
Date: Tue, 29 Jan 2013 17:48:30 +0100
Subject: [PATCH] Autoindex: support sorting using URL parameters

Based on Apache HTTPD autoindex docs[1]. Supported:
- C=N sorts the directory by file name
- C=M sorts the directory by last-modified date, then file name
- C=S sorts the directory by size, then file name
- O=A sorts the listing in Ascending Order
- O=D sorts the listing in Descending Order

Not supported (does not make sense for nginx):
- C=D sorts the directory by description, then file name
- All F= (FancyIndex) related arguments
- Version sorting for file names (V=)
- Pattern filter (P=)

Argument processing stops when the query string does not exactly match the
options allowed by nginx, invalid values (like "C=m", "C=x" or "C=foo") are
ignored and cause further processing to stop.

C and O are the most useful options and can commonly be found in my old Apache
logs (also outputted in header links). This patch is made for these cases, not
for some exotic use of Apache-specific properties.

 [1]: http://httpd.apache.org/docs/2.4/mod/mod_autoindex.html
---
 src/http/modules/ngx_http_autoindex_module.c | 93 +++++++++++++++++++++++++++-
 1 file changed, 92 insertions(+), 1 deletion(-)

diff --git a/src/http/modules/ngx_http_autoindex_module.c b/src/http/modules/ngx_http_autoindex_module.c
index 450a48e..aa1be25 100644
--- a/src/http/modules/ngx_http_autoindex_module.c
+++ b/src/http/modules/ngx_http_autoindex_module.c
@@ -24,6 +24,16 @@ typedef struct {
 
 
 typedef struct {
+    u_char         sort_key;
+    unsigned       order_desc:1;
+} ngx_http_autoindex_opts_t;
+
+#define NGX_AUTOINDEX_SORT_NAME     'N'
+#define NGX_AUTOINDEX_SORT_MTIME    'M'
+#define NGX_AUTOINDEX_SORT_SIZE     'S'
+
+
+typedef struct {
     ngx_str_t      name;
     size_t         utf_len;
     size_t         escape;
@@ -33,6 +43,8 @@ typedef struct {
 
     time_t         mtime;
     off_t          size;
+
+    ngx_http_autoindex_opts_t *sort_opts;
 } ngx_http_autoindex_entry_t;
 
 
@@ -50,6 +62,8 @@ typedef struct {
 
 static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
     const void *two);
+static ngx_int_t ngx_http_autoindex_get_opts(ngx_http_request_t *r,
+    ngx_http_autoindex_opts_t *opts);
 static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
     ngx_dir_t *dir, ngx_str_t *name);
 static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
@@ -153,6 +167,7 @@ ngx_http_autoindex_handler(ngx_http_request_t *r)
     ngx_array_t                     entries;
     ngx_http_autoindex_entry_t     *entry;
     ngx_http_autoindex_loc_conf_t  *alcf;
+    ngx_http_autoindex_opts_t       opts;
 
     static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
@@ -214,6 +229,8 @@ ngx_http_autoindex_handler(ngx_http_request_t *r)
         return rc;
     }
 
+    ngx_http_autoindex_get_opts(r, &opts);
+
 #if (NGX_SUPPRESS_WARN)
 
     /* MSVC thinks 'entries' may be used without having been initialized */
@@ -353,6 +370,8 @@ ngx_http_autoindex_handler(ngx_http_request_t *r)
         entry->dir = ngx_de_is_dir(&dir);
         entry->mtime = ngx_de_mtime(&dir);
         entry->size = ngx_de_size(&dir);
+
+        entry->sort_opts = &opts;
     }
 
     if (ngx_close_dir(&dir) == NGX_ERROR) {
@@ -580,11 +599,67 @@ ngx_http_autoindex_handler(ngx_http_request_t *r)
 }
 
 
+static ngx_int_t
+ngx_http_autoindex_get_opts(ngx_http_request_t *r,
+    ngx_http_autoindex_opts_t *opts)
+{
+    u_char *p, *last, key, val;
+
+    opts->sort_key = NGX_AUTOINDEX_SORT_NAME;
+    opts->order_desc = 0;
+
+    p = r->args.data;
+    last = p + r->args.len;
+
+    for ( /* void */; p < last; p++) {
+
+        key = *p++;
+
+        if (*p++ != '=') {
+            return NGX_DECLINED;
+        }
+
+        val = *p++;
+
+        /* assume one-letter value and expect separator */
+        if (p < last && *p != '&' && *p != ';') {
+            return NGX_DECLINED;
+        }
+
+        if (key == 'C') {
+            switch (val) {
+            case NGX_AUTOINDEX_SORT_NAME:
+            case NGX_AUTOINDEX_SORT_MTIME:
+            case NGX_AUTOINDEX_SORT_SIZE:
+                opts->sort_key = val;
+                break;
+            default:
+                return NGX_DECLINED;
+            }
+        } else if (key == 'O') {
+            if (val == 'D') {
+                opts->order_desc = 1;
+            } else if (val == 'A') {
+                opts->order_desc = 0;
+            } else {
+                return NGX_DECLINED;
+            }
+        } else {
+            return NGX_DECLINED;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
 static int ngx_libc_cdecl
 ngx_http_autoindex_cmp_entries(const void *one, const void *two)
 {
     ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
     ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
+    ngx_http_autoindex_opts_t *opts = first->sort_opts;
+    int r;
 
     if (first->dir && !second->dir) {
         /* move the directories to the start */
@@ -596,7 +671,23 @@ ngx_http_autoindex_cmp_entries(const void *one, const void *two)
         return 1;
     }
 
-    return (int) ngx_strcmp(first->name.data, second->name.data);
+    switch (opts->sort_key) {
+    case NGX_AUTOINDEX_SORT_MTIME:
+        r = first->mtime - second->mtime;
+        break;
+    case NGX_AUTOINDEX_SORT_SIZE:
+        r = first->size - second->size;
+        break;
+    default: /* includes NGX_AUTOINDEX_SORT_NAME */
+        r = 0;
+        break;
+    }
+
+    if (r == 0) {
+        r = ngx_strcmp(first->name.data, second->name.data);
+    }
+
+    return opts->order_desc ? -r : r;
 }
 
 
-- 
1.8.1.1

