/* * ngx_http_awesomeindex_module.c * Copyright © 2007-2016 Adrian Perez * * Module used for fancy indexing of directories. Features and differences * with the stock nginx autoindex module: * * - Output is a table instead of a
 element with embedded  links.
 *  - Header and footer may be added to every generated directory listing.
 *  - Default header and/or footer are generated if custom ones are not
 *    configured. Files used for header and footer can only be local path
 *    names (i.e. you cannot insert the result of a subrequest.)
 *  - Proper HTML is generated: it should validate both as XHTML 1.0 Strict
 *    and HTML 4.01.
 *
 * Base functionality heavy based upon the stock nginx autoindex module,
 * which in turn was made by Igor Sysoev, like the majority of nginx.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */
#include 
#include 
#include 
#include 
#include "ngx_buf.h"
#include "ngx_string.h"

#include "md4c-html.h"
#include "template.h"

#if defined(__GNUC__) && (__GNUC__ >= 3)
# define ngx_force_inline __attribute__((__always_inline__))
#else /* !__GNUC__ */
# define ngx_force_inline
#endif /* __GNUC__ */


static const char *short_weekday[] = {
    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
};
static const char *long_weekday[] = {
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday",
};
static const char *short_month[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};
static const char *long_month[] = {
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December",
};

#define MKD_FLAGS MKD_TOC | MKD_IDANCHOR | MKD_AUTOLINK


#define DATETIME_FORMATS(F_, t) \
    F_ ('a',  3, "%3s",  short_weekday[((t)->ngx_tm_wday + 6) % 7]) \
    F_ ('A',  9, "%s",   long_weekday [((t)->ngx_tm_wday + 6) % 7]) \
    F_ ('b',  3, "%3s",  short_month[(t)->ngx_tm_mon - 1]         ) \
    F_ ('B',  9, "%s",   long_month [(t)->ngx_tm_mon - 1]         ) \
    F_ ('d',  2, "%02d", (t)->ngx_tm_mday                         ) \
    F_ ('e',  2, "%2d",  (t)->ngx_tm_mday                         ) \
    F_ ('F', 10, "%d-%02d-%02d",                                    \
                  (t)->ngx_tm_year,                                 \
                  (t)->ngx_tm_mon,                                  \
                  (t)->ngx_tm_mday                                ) \
    F_ ('H',  2, "%02d", (t)->ngx_tm_hour                         ) \
    F_ ('I',  2, "%02d", ((t)->ngx_tm_hour % 12) + 1              ) \
    F_ ('k',  2, "%2d",  (t)->ngx_tm_hour                         ) \
    F_ ('l',  2, "%2d",  ((t)->ngx_tm_hour % 12) + 1              ) \
    F_ ('m',  2, "%02d", (t)->ngx_tm_mon                          ) \
    F_ ('M',  2, "%02d", (t)->ngx_tm_min                          ) \
    F_ ('p',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "AM" : "PM")  ) \
    F_ ('P',  2, "%2s",  (((t)->ngx_tm_hour < 12) ? "am" : "pm")  ) \
    F_ ('r', 11, "%02d:%02d:%02d %2s",                              \
                 ((t)->ngx_tm_hour % 12) + 1,                       \
                 (t)->ngx_tm_min,                                   \
                 (t)->ngx_tm_sec,                                   \
                 (((t)->ngx_tm_hour < 12) ? "AM" : "PM")          ) \
    F_ ('R',  5, "%02d:%02d", (t)->ngx_tm_hour, (t)->ngx_tm_min   ) \
    F_ ('S',  2, "%02d", (t)->ngx_tm_sec                          ) \
    F_ ('T',  8, "%02d:%02d:%02d",                                  \
                 (t)->ngx_tm_hour,                                  \
                 (t)->ngx_tm_min,                                   \
                 (t)->ngx_tm_sec                                  ) \
    F_ ('u',  1, "%1d", (((t)->ngx_tm_wday + 6) % 7) + 1          ) \
    F_ ('w',  1, "%1d", ((t)->ngx_tm_wday + 6) % 7                ) \
    F_ ('y',  2, "%02d", (t)->ngx_tm_year % 100                   ) \
    F_ ('Y',  4, "%04d", (t)->ngx_tm_year                         )


static size_t
ngx_awesomeindex_timefmt_calc_size (const ngx_str_t *fmt)
{
#define DATETIME_CASE(letter, fmtlen, fmt, ...) \
        case letter: result += (fmtlen); break;

    size_t i, result = 0;
    for (i = 0; i < fmt->len; i++) {
        if (fmt->data[i] == '%') {
            if (++i >= fmt->len) {
                result++;
                break;
            }
            switch (fmt->data[i]) {
                DATETIME_FORMATS(DATETIME_CASE,)
                default:
                    result++;
            }
        } else {
            result++;
        }
    }
    return result;

#undef DATETIME_CASE
}


static u_char*
ngx_awesomeindex_timefmt (u_char *buffer, const ngx_str_t *fmt, const ngx_tm_t *tm)
{
#define DATETIME_CASE(letter, fmtlen, fmt, ...) \
        case letter: buffer = ngx_snprintf(buffer, fmtlen, fmt, ##__VA_ARGS__); break;

    size_t i;
    for (i = 0; i < fmt->len; i++) {
        if (fmt->data[i] == '%') {
            if (++i >= fmt->len) {
                *buffer++ = '%';
                break;
            }
            switch (fmt->data[i]) {
                DATETIME_FORMATS(DATETIME_CASE, tm)
                default:
                    *buffer++ = fmt->data[i];
            }
        } else {
            *buffer++ = fmt->data[i];
        }
    }
    return buffer;

#undef DATETIME_CASE
}

typedef struct {
    ngx_str_t path;
    ngx_str_t local;
} ngx_awesomeindex_headerfooter_conf_t;

/**
 * Configuration structure for the awesomeindex module. The configuration
 * commands defined in the module do fill in the members of this structure.
 */
typedef struct {
    ngx_flag_t enable;         /**< Module is enabled. */
    ngx_uint_t default_sort;   /**< Default sort criterion. */
    ngx_flag_t case_sensitive; /**< Case-sensitive name sorting */
    ngx_flag_t dirs_first;     /**< Group directories together first when sorting */
    ngx_flag_t localtime;      /**< File mtime dates are sent in local time. */
    ngx_flag_t exact_size;     /**< Sizes are sent always in bytes. */
    ngx_flag_t hide_symlinks;  /**< Hide symbolic links in listings. */
    ngx_flag_t show_path;      /**< Whether to display or not the path + '' after the header */
    ngx_flag_t hide_parent;    /**< Hide parent directory. */
    ngx_flag_t show_dot_files; /**< Show files that start with a dot.*/

    ngx_str_t  css_href;       /**< Link to a CSS stylesheet, or empty if none. */
    ngx_str_t  time_format;    /**< Format used for file timestamps. */

    ngx_array_t *ignore;       /**< List of files to ignore in listings. */

    ngx_awesomeindex_headerfooter_conf_t header;
    ngx_awesomeindex_headerfooter_conf_t footer;

    ngx_str_t title;
    ngx_str_t icon_href;
} ngx_http_awesomeindex_loc_conf_t;

#define ngx_http_awesomeindex_SORT_CRITERION_NAME       0
#define ngx_http_awesomeindex_SORT_CRITERION_SIZE       1
#define ngx_http_awesomeindex_SORT_CRITERION_DATE       2
#define ngx_http_awesomeindex_SORT_CRITERION_NAME_DESC  3
#define ngx_http_awesomeindex_SORT_CRITERION_SIZE_DESC  4
#define ngx_http_awesomeindex_SORT_CRITERION_DATE_DESC  5

static ngx_conf_enum_t ngx_http_awesomeindex_sort_criteria[] = {
    { ngx_string("name"), ngx_http_awesomeindex_SORT_CRITERION_NAME },
    { ngx_string("size"), ngx_http_awesomeindex_SORT_CRITERION_SIZE },
    { ngx_string("date"), ngx_http_awesomeindex_SORT_CRITERION_DATE },
    { ngx_string("name_desc"), ngx_http_awesomeindex_SORT_CRITERION_NAME_DESC },
    { ngx_string("size_desc"), ngx_http_awesomeindex_SORT_CRITERION_SIZE_DESC },
    { ngx_string("date_desc"), ngx_http_awesomeindex_SORT_CRITERION_DATE_DESC },
    { ngx_null_string, 0 }
};

enum {
    ngx_http_awesomeindex_HEADERFOOTER_SUBREQUEST,
    ngx_http_awesomeindex_HEADERFOOTER_LOCAL,
};

static ngx_uint_t
headerfooter_kind(const ngx_str_t *value)
{
    static const struct {
        ngx_str_t name;
        ngx_uint_t value;
    } values[] = {
        { ngx_string("subrequest"), ngx_http_awesomeindex_HEADERFOOTER_SUBREQUEST },
        { ngx_string("local"), ngx_http_awesomeindex_HEADERFOOTER_LOCAL },
    };

    unsigned i;

    for (i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
        if (value->len == values[i].name.len &&
            ngx_strcasecmp(value->data, values[i].name.data) == 0)
        {
            return values[i].value;
        }
    }

    return NGX_CONF_UNSET_UINT;
}

static char*
ngx_awesomeindex_conf_set_headerfooter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_awesomeindex_headerfooter_conf_t *item =
        (void*) (((char*) conf) + cmd->offset);
    ngx_str_t *values = cf->args->elts;

    if (item->path.data)
        return "is duplicate";

    item->path = values[1];

    /* Kind of path. Default is "subrequest". */
    ngx_uint_t kind = ngx_http_awesomeindex_HEADERFOOTER_SUBREQUEST;
    if (cf->args->nelts == 3) {
        kind = headerfooter_kind(&values[2]);
        if (kind == NGX_CONF_UNSET_UINT) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unknown header/footer kind \"%V\"", &values[2]);
            return NGX_CONF_ERROR;
        }
    }

    if (kind == ngx_http_awesomeindex_HEADERFOOTER_LOCAL) {
        ngx_file_t file;
        ngx_file_info_t fi;
        ssize_t n;

        ngx_memzero(&file, sizeof(ngx_file_t));
        file.log = cf->log;
        file.fd = ngx_open_file(item->path.data, NGX_FILE_RDONLY, 0, 0);
        if (file.fd == NGX_INVALID_FILE) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                               "cannot open file \"%V\"", &values[1]);
            return NGX_CONF_ERROR;
        }

        if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
            ngx_close_file(file.fd);
            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                               "cannot get info for file \"%V\"", &values[1]);
            return NGX_CONF_ERROR;
        }

        item->local.len = ngx_file_size(&fi);
        item->local.data = ngx_pcalloc(cf->pool, item->local.len + 1);
        if (item->local.data == NULL) {
            ngx_close_file(file.fd);
            return NGX_CONF_ERROR;
        }

        n = item->local.len;
        while (n > 0) {
            ssize_t r = ngx_read_file(&file,
                                      item->local.data + file.offset,
                                      n,
                                      file.offset);
            if (r == NGX_ERROR) {
                ngx_close_file(file.fd);
                ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                                   "cannot read file \"%V\"", &values[1]);
                return NGX_CONF_ERROR;
            }

            n -= r;
        }
        item->local.data[item->local.len] = '\0';
    }

    return NGX_CONF_OK;
}

#define ngx_http_awesomeindex_PREALLOCATE  50


/**
 * Calculates the length of a NULL-terminated string. It is ugly having to
 * remember to substract 1 from the sizeof result.
 */
#define ngx_sizeof_ssz(_s)  (sizeof(_s) - 1)

/**
 * Compute the length of a statically allocated array
 */
#define DIM(x) (sizeof(x)/sizeof(*(x)))

/**
 * Copy a static zero-terminated string. Useful to output template
 * string pieces into a temporary buffer.
 */
#define ngx_cpymem_ssz(_p, _t) \
	(ngx_cpymem((_p), (_t), sizeof(_t) - 1))

/**
 * Copy a ngx_str_t.
 */
#define ngx_cpymem_str(_p, _s) \
	(ngx_cpymem((_p), (_s).data, (_s).len))

/**
 * Check whether a particular bit is set in a particular value.
 */
#define ngx_has_flag(_where, _what) \
	(((_where) & (_what)) == (_what))

/*********************************
 ***  Simple grow-able buffer  ***
 *********************************/

/* We render to a memory buffer instead of directly outputting the rendered
 * documents, as this allows using this utility for evaluating performance
 * of MD4C (--stat option). This allows us to measure just time of the parser,
 * without the I/O.
 */

struct membuffer {
    char* data;
    size_t asize;
    size_t size;
    u_char fail;
};

static u_char
membuf_init(struct membuffer* buf, MD_SIZE new_asize)
{
    buf->size = 0;
    buf->asize = new_asize;
    buf->data = malloc(buf->asize);
    if(buf->data == NULL) {
        buf->fail = 1;
        return 1;
    }
    buf->fail = 0;
    return 0;
}

static void
membuf_free(struct membuffer* buf)
{
    if(buf && buf->data) free(buf->data);
}

static void
membuf_grow(struct membuffer* buf, size_t new_asize)
{
    buf->data = realloc(buf->data, new_asize);
    if(buf->data == NULL) {
        buf->fail = 1;
    }
    buf->asize = new_asize;
}

static void
membuf_append(struct membuffer* buf, const char* data, MD_SIZE size)
{
    if(buf->asize < buf->size + size)
        membuf_grow(buf, buf->size + buf->size / 2 + size);
    memcpy(buf->data + buf->size, data, size);
    buf->size += size;
}

static void
process_md_output(const MD_CHAR* text, MD_SIZE size, void* userdata)
{
    struct membuffer *buf = (struct membuffer*) userdata;
    if (!buf->fail) membuf_append(buf, text, size);
}

static ssize_t ngx_awesomeindex_read_file(ngx_http_request_t *r, u_char* filename, size_t file_len, u_char* dest)
{
    ngx_file_t file;
    ngx_memzero(&file, sizeof(ngx_file_t));
    file.fd = ngx_open_file(filename, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
    if (file.fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
            "cannot open readme file \"%V\"", filename);
        return NGX_ERROR;
    }
    file.log = r->connection->log;

    ssize_t n = ngx_read_file(&file, dest, file_len, 0);
    ngx_close_file(file.fd);
    return n;
}


typedef struct {
    ngx_str_t      name;
    size_t         utf_len;
    ngx_uint_t     escape;
    ngx_uint_t     escape_html;
    ngx_uint_t     dir;
    ngx_uint_t     link;
    time_t         mtime;
    off_t          size;
} ngx_http_awesomeindex_entry_t;



static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_name_cs_desc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_name_ci_desc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_size_desc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_mtime_desc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_name_cs_asc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_name_ci_asc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_size_asc(const void *one, const void *two);
static int ngx_libc_cdecl
    ngx_http_awesomeindex_cmp_entries_mtime_asc(const void *one, const void *two);

static size_t ngx_http_awesomeindex_num_places (size_t n);

static ngx_int_t ngx_http_awesomeindex_error(ngx_http_request_t *r,
    ngx_dir_t *dir, ngx_str_t *name);

static ngx_int_t ngx_http_awesomeindex_init(ngx_conf_t *cf);

static void *ngx_http_awesomeindex_create_loc_conf(ngx_conf_t *cf);

static char *ngx_http_awesomeindex_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child);

static char *ngx_http_awesomeindex_ignore(ngx_conf_t    *cf,
                                        ngx_command_t *cmd,
                                        void          *conf);

static uintptr_t
    ngx_awesomeindex_escape_filename(u_char *dst, u_char*src, size_t size);

/*
 * These are used only once per handler invocation. We can tell GCC to
 * inline them always, if possible (see how ngx_force_inline is defined
 * above).
 */
static ngx_inline ngx_buf_t*
    make_header_buf(ngx_http_request_t *r, ngx_http_awesomeindex_loc_conf_t *alcf)
    ngx_force_inline;


static ngx_command_t  ngx_http_awesomeindex_commands[] = {

    { ngx_string("awesomeindex"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, enable),
      NULL },

    { ngx_string("awesomeindex_default_sort"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_enum_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, default_sort),
      &ngx_http_awesomeindex_sort_criteria },

    { ngx_string("awesomeindex_case_sensitive"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, case_sensitive),
      NULL },

    { ngx_string("awesomeindex_directories_first"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, dirs_first),
      NULL },

    { ngx_string("awesomeindex_localtime"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, localtime),
      NULL },

    { ngx_string("awesomeindex_exact_size"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, exact_size),
      NULL },

    { ngx_string("awesomeindex_header"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
      ngx_awesomeindex_conf_set_headerfooter,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, header),
      NULL },

    { ngx_string("awesomeindex_footer"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
      ngx_awesomeindex_conf_set_headerfooter,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, footer),
      NULL },

    { ngx_string("awesomeindex_css_href"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, css_href),
      NULL },

    { ngx_string("awesomeindex_ignore"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
      ngx_http_awesomeindex_ignore,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("awesomeindex_hide_symlinks"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, hide_symlinks),
      NULL },

    { ngx_string("awesomeindex_show_path"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, show_path),
      NULL },

    { ngx_string("awesomeindex_show_dotfiles"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, show_dot_files),
      NULL },

    { ngx_string("awesomeindex_hide_parent_dir"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, hide_parent),
      NULL },

    { ngx_string("awesomeindex_time_format"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, time_format),
      NULL },

    { ngx_string("awesomeindex_title"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, title),
      NULL },
    
    { ngx_string("awesomeindex_icon_href"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_awesomeindex_loc_conf_t, icon_href),
      NULL },

    ngx_null_command
};


static ngx_http_module_t  ngx_http_awesomeindex_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_awesomeindex_init,              /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_awesomeindex_create_loc_conf,   /* create location configuration */
    ngx_http_awesomeindex_merge_loc_conf     /* merge location configuration */
};


ngx_module_t  ngx_http_awesomeindex_module = {
    NGX_MODULE_V1,
    &ngx_http_awesomeindex_module_ctx,       /* module context */
    ngx_http_awesomeindex_commands,          /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};



static const ngx_str_t css_href_pre =
    ngx_string("\n");
static const ngx_str_t favicon_pre =
    ngx_string("\n");
static const ngx_str_t img_icon_pre =
    ngx_string("\"\"");
static const ngx_str_t footer_md_pre =
    ngx_string("