#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>

#include "base.h"
#include "log.h"
#include "buffer.h"

#include "plugin.h"

#include "response.h"
#include "stat_cache.h"
#include "stream.h"
#include "etag.h"

#include "sys-strings.h"

/**
 * this is a dirlisting for a lighttpd plugin
 */


#ifdef HAVE_SYS_SYSLIMITS_H
#include <sys/syslimits.h>
#endif

#ifdef HAVE_XATTR
#include <attr/attributes.h>
#endif

#include "sys-files.h"
#include "sys-strings.h"

/* plugin config for all request/connections */

typedef struct {
#ifdef HAVE_PCRE_H
	pcre *regex;
#endif
	buffer *string;
} excludes;

typedef struct {
	excludes **ptr;

	size_t used;
	size_t size;
} excludes_buffer;

typedef struct {
	unsigned short dir_listing;
	unsigned short hide_dot_files;
	unsigned short show_readme;
	unsigned short hide_readme_file;
	unsigned short show_header;
	unsigned short hide_header_file;

	excludes_buffer *excludes;

	buffer *external_css;
	buffer *encoding;
	buffer *set_footer;
} plugin_config;

typedef struct {
	PLUGIN_DATA;

	buffer *tmp_buf;
	buffer *content_charset;
	buffer *path;

	plugin_config **config_storage;

	plugin_config conf;
} plugin_data;

static excludes_buffer *excludes_buffer_init(void) {
	excludes_buffer *exb;

	exb = calloc(1, sizeof(*exb));

	return exb;
}

static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
#ifdef HAVE_PCRE_H
	size_t i;
	const char *errptr;
	int erroff;

	if (!string) return -1;

	if (exb->size == 0) {
		exb->size = 4;
		exb->used = 0;

		exb->ptr = malloc(exb->size * sizeof(*exb->ptr));

		for(i = 0; i < exb->size ; i++) {
			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
		}
	} else if (exb->used == exb->size) {
		exb->size += 4;

		exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));

		for(i = exb->used; i < exb->size; i++) {
			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
		}
	}


	if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
						    &errptr, &erroff, NULL))) {
		return -1;
	}

	exb->ptr[exb->used]->string = buffer_init();
	buffer_copy_string_buffer(exb->ptr[exb->used]->string, string);

	exb->used++;

	return 0;
#else
	UNUSED(exb);
	UNUSED(string);

	return -1;
#endif
}

static void excludes_buffer_free(excludes_buffer *exb) {
#ifdef HAVE_PCRE_H
	size_t i;

	for (i = 0; i < exb->size; i++) {
		if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
		if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
		free(exb->ptr[i]);
	}

	if (exb->ptr) free(exb->ptr);
#endif

	free(exb);
}

/* init the plugin data */
INIT_FUNC(mod_dirlisting_init) {
	plugin_data *p;

	UNUSED(srv);

	p = calloc(1, sizeof(*p));

	p->tmp_buf = buffer_init();
	p->content_charset = buffer_init();
	p->path = buffer_init();

	return p;
}

/* destroy the plugin data */
FREE_FUNC(mod_dirlisting_free) {
	plugin_data *p = p_d;

	UNUSED(srv);

	if (!p) return HANDLER_GO_ON;

	if (p->config_storage) {
		size_t i;
		for (i = 0; i < srv->config_context->used; i++) {
			plugin_config *s = p->config_storage[i];

			if (!s) continue;

			excludes_buffer_free(s->excludes);
			buffer_free(s->external_css);
			buffer_free(s->encoding);
			buffer_free(s->set_footer);

			free(s);
		}
		free(p->config_storage);
	}

	buffer_free(p->tmp_buf);
	buffer_free(p->path);
	buffer_free(p->content_charset);

	free(p);

	return HANDLER_GO_ON;
}

static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) {
	data_unset *du;

	if (NULL != (du = array_get_element(ca, option, strlen(option)))) {
		data_array *da = (data_array *)du;
		size_t j;

		if (du->type != TYPE_ARRAY) {
			log_error_write(srv, __FILE__, __LINE__, "sss",
				"unexpected type for key: ", option, "array of strings");

			return HANDLER_ERROR;
		}

		da = (data_array *)du;

		for (j = 0; j < da->value->used; j++) {
			if (da->value->data[j]->type != TYPE_STRING) {
				log_error_write(srv, __FILE__, __LINE__, "sssbs",
					"unexpected type for key: ", option, "[",
					da->value->data[j]->key, "](string)");

				return HANDLER_ERROR;
			}

			if (0 != excludes_buffer_append(s->excludes,
				    ((data_string *)(da->value->data[j]))->value)) {
#ifdef HAVE_PCRE_H
				log_error_write(srv, __FILE__, __LINE__, "sb",
						"pcre-compile failed for", ((data_string *)(da->value->data[j]))->value);
#else
				log_error_write(srv, __FILE__, __LINE__, "s",
						"pcre support is missing, please install libpcre and the headers");
#endif
			}
		}
	}

	return 0;
}

/* handle plugin config and check values */

#define CONFIG_EXCLUDE          "dir-listing.exclude"
#define CONFIG_ACTIVATE         "dir-listing.activate"
#define CONFIG_HIDE_DOTFILES    "dir-listing.hide-dotfiles"
#define CONFIG_EXTERNAL_CSS     "dir-listing.external-css"
#define CONFIG_ENCODING         "dir-listing.encoding"
#define CONFIG_SHOW_README      "dir-listing.show-readme"
#define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
#define CONFIG_SHOW_HEADER      "dir-listing.show-header"
#define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
#define CONFIG_DIR_LISTING      "server.dir-listing"
#define CONFIG_SET_FOOTER       "dir-listing.set-footer"


SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
	plugin_data *p = p_d;
	size_t i = 0;

	config_values_t cv[] = {
		{ CONFIG_EXCLUDE,          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },   /* 0 */
		{ CONFIG_ACTIVATE,         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
		{ CONFIG_HIDE_DOTFILES,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
		{ CONFIG_EXTERNAL_CSS,     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 3 */
		{ CONFIG_ENCODING,         NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 4 */
		{ CONFIG_SHOW_README,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
		{ CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
		{ CONFIG_SHOW_HEADER,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
		{ CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
		{ CONFIG_DIR_LISTING,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
		{ CONFIG_SET_FOOTER,       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 10 */

		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
	};

	if (!p) return HANDLER_ERROR;

	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));

	for (i = 0; i < srv->config_context->used; i++) {
		plugin_config *s;
		array *ca;

		s = calloc(1, sizeof(plugin_config));
		s->excludes = excludes_buffer_init();
		s->dir_listing = 0;
		s->external_css = buffer_init();
		s->hide_dot_files = 0;
		s->show_readme = 0;
		s->hide_readme_file = 0;
		s->show_header = 0;
		s->hide_header_file = 0;
		s->encoding = buffer_init();
		s->set_footer = buffer_init();

		cv[0].destination = s->excludes;
		cv[1].destination = &(s->dir_listing);
		cv[2].destination = &(s->hide_dot_files);
		cv[3].destination = s->external_css;
		cv[4].destination = s->encoding;
		cv[5].destination = &(s->show_readme);
		cv[6].destination = &(s->hide_readme_file);
		cv[7].destination = &(s->show_header);
		cv[8].destination = &(s->hide_header_file);
		cv[9].destination = &(s->dir_listing); /* old name */
		cv[10].destination = s->set_footer;

		p->config_storage[i] = s;
		ca = ((data_config *)srv->config_context->data[i])->value;

		if (0 != config_insert_values_global(srv, ca, cv)) {
			return HANDLER_ERROR;
		}

		parse_config_entry(srv, s, ca, CONFIG_EXCLUDE);
	}

	return HANDLER_GO_ON;
}

static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
	size_t i, j;
	plugin_config *s = p->config_storage[0];

	PATCH_OPTION(dir_listing);
	PATCH_OPTION(external_css);
	PATCH_OPTION(hide_dot_files);
	PATCH_OPTION(encoding);
	PATCH_OPTION(show_readme);
	PATCH_OPTION(hide_readme_file);
	PATCH_OPTION(show_header);
	PATCH_OPTION(hide_header_file);
	PATCH_OPTION(excludes);
	PATCH_OPTION(set_footer);

	/* skip the first, the global context */
	for (i = 1; i < srv->config_context->used; i++) {
		data_config *dc = (data_config *)srv->config_context->data[i];
		s = p->config_storage[i];

		/* condition didn't match */
		if (!config_check_cond(srv, con, dc)) continue;

		/* merge config */
		for (j = 0; j < dc->value->used; j++) {
			data_unset *du = dc->value->data[j];

			if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
			    buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
				PATCH_OPTION(dir_listing);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
				PATCH_OPTION(hide_dot_files);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
				PATCH_OPTION(external_css);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
				PATCH_OPTION(encoding);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
				PATCH_OPTION(show_readme);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
				PATCH_OPTION(hide_readme_file);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
				PATCH_OPTION(show_header);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
				PATCH_OPTION(hide_header_file);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
				PATCH_OPTION(set_footer);
			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
				PATCH_OPTION(excludes);
			}
		}
	}

	return 0;
}

typedef struct {
	size_t  namelen;
	time_t  mtime;
	off_t   size;
} dirls_entry_t;

typedef struct {
	dirls_entry_t **ent;
	size_t used;
	size_t size;
} dirls_list_t;

#define DIRLIST_ENT_NAME(ent)	((char*)(ent) + sizeof(dirls_entry_t))
#define DIRLIST_BLOB_SIZE		16

/* simple combsort algorithm */
static void http_dirls_sort(dirls_entry_t **ent, int num) {
	int gap = num;
	int i, j;
	int swapped;
	dirls_entry_t *tmp;

	do {
		gap = (gap * 10) / 13;
		if (gap == 9 || gap == 10)
			gap = 11;
		if (gap < 1)
			gap = 1;
		swapped = 0;

		for (i = 0; i < num - gap; i++) {
			j = i + gap;
			if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
				tmp = ent[i];
				ent[i] = ent[j];
				ent[j] = tmp;
				swapped = 1;
			}
		}

	} while (gap > 1 || swapped);
}

/* buffer must be able to hold "999.9K"
 * conversion is simple but not perfect
 */
static int http_list_directory_sizefmt(char *buf, off_t size) {
	const char unit[] = "KMGTPE";	/* Kilo, Mega, Tera, Peta, Exa */
	const char *u = unit - 1;		/* u will always increment at least once */
	int remain;
	char *out = buf;

	if (size < 100)
		size += 99;
	if (size < 100)
		size = 0;

	while (1) {
		remain = (int) size & 1023;
		size >>= 10;
		u++;
		if ((size & (~0 ^ 1023)) == 0)
			break;
	}

	remain /= 100;
	if (remain > 9)
		remain = 9;
	if (size > 999) {
		size   = 0;
		remain = 9;
		u++;
	}

	out   += LI_ltostr(out, size);
	out[0] = '.';
	out[1] = remain + '0';
	out[2] = *u;
	out[3] = '\0';

	return (out + 3 - buf);
}

static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
	UNUSED(srv);

	buffer_append_string_len(out, CONST_STR_LEN(
		"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
		"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
		"<head>\n"
		"<title>Index of "
	));
	buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
	buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));

	if (p->conf.external_css->used > 1) {
		buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
		buffer_append_string_buffer(out, p->conf.external_css);
		buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
	} else {
		buffer_append_string_len(out, CONST_STR_LEN(
			"<style type=\"text/css\">\n"
			"a, a:active {text-decoration: none; color: blue;}\n"
			"a:visited {color: #48468F;}\n"
			"a:hover, a:focus {text-decoration: underline; color: red;}\n"
			"body {background-color: #F5F5F5;}\n"
			"h2 {margin-bottom: 12px;}\n"
			"table {margin-left: 12px;}\n"
			"th, td {"
			" font: 90% monospace;"
			" text-align: left;"
			"}\n"
			"th {"
			" font-weight: bold;"
			" padding-right: 14px;"
			" padding-bottom: 3px;"
			"}\n"

			"td {padding-right: 14px;}\n"
			"td.s, th.s {text-align: right;}\n"
			"div.list {"
			" background-color: white;"
			" border-top: 1px solid #646464;"
			" border-bottom: 1px solid #646464;"
			" padding-top: 10px;"
			" padding-bottom: 14px;"
			"}\n"
			"div.foot {"
			" font: 90% monospace;"
			" color: #787878;"
			" padding-top: 4px;"
			"}\n"
			"</style>\n"
		));
	}

	buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));

	/* HEADER.txt */
	if (p->conf.show_header) {
		stream s;
		/* if we have a HEADER file, display it in <pre class="header"></pre> */

		buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
		PATHNAME_APPEND_SLASH(p->tmp_buf);
		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("HEADER.txt"));

		if (-1 != stream_open(&s, p->tmp_buf)) {
			buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"header\">"));
			buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
			buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
		}
		stream_close(&s);
	}

	buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
	buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
	buffer_append_string_len(out, CONST_STR_LEN(
		"</h2>\n"
		"<div class=\"list\">\n"
		"<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
		"<thead>"
		"<tr>"
			"<th class=\"n\">Name</th>"
			"<th class=\"m\">Last Modified</th>"
			"<th class=\"s\">Size</th>"
			"<th class=\"t\">Type</th>"
		"</tr>"
		"</thead>\n"
		"<tbody>\n"
		"<tr>"
			"<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
			"<td class=\"m\">&nbsp;</td>"
			"<td class=\"s\">- &nbsp;</td>"
			"<td class=\"t\">Directory</td>"
		"</tr>\n"
	));
}

static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
	UNUSED(srv);

	buffer_append_string_len(out, CONST_STR_LEN(
		"</tbody>\n"
		"</table>\n"
		"</div>\n"
	));

	if (p->conf.show_readme) {
		stream s;
		/* if we have a README file, display it in <pre class="readme"></pre> */

		buffer_copy_string_buffer(p->tmp_buf,  con->physical.path);
		PATHNAME_APPEND_SLASH(p->tmp_buf);
		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("README.txt"));

		if (-1 != stream_open(&s, p->tmp_buf)) {
			buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"readme\">"));
			buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
			buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
		}
		stream_close(&s);
	}

	buffer_append_string_len(out, CONST_STR_LEN(
		"<div class=\"foot\">"
	));

	if (p->conf.set_footer->used > 1) {
		buffer_append_string_buffer(out, p->conf.set_footer);
	} else if (buffer_is_empty(con->conf.server_tag)) {
		buffer_append_string_len(out, CONST_STR_LEN(PACKAGE_NAME "/" PACKAGE_VERSION));
	} else {
		buffer_append_string_buffer(out, con->conf.server_tag);
	}

	buffer_append_string_len(out, CONST_STR_LEN(
		"</div>\n"
		"</body>\n"
		"</html>\n"
	));
}

static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
	DIR *dp;
	buffer *out;
	struct dirent *dent;
	struct stat st;
	size_t i;
	int hide_dotfiles = p->conf.hide_dot_files;
	dirls_list_t dirs, files, *list;
	dirls_entry_t *tmp;
	char sizebuf[sizeof("999.9K")];
	char datebuf[sizeof("2005-Jan-01 22:23:24")];
	size_t k;
	const char *content_type;
	long name_max;

#ifdef HAVE_XATTR
	char attrval[128];
	int attrlen;
#endif
#ifdef HAVE_LOCALTIME_R
	struct tm tm;
#endif

	/* empty pathname, never ... */
	if (buffer_is_empty(dir)) return -1;

	/* max-length for the opendir */
#ifdef HAVE_PATHCONF
	if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
#ifdef NAME_MAX
		name_max = NAME_MAX;
#else
		name_max = 256; /* stupid default */
#endif
	}
#elif defined _WIN32
	name_max = FILENAME_MAX;
#else
	name_max = NAME_MAX;
#endif

	buffer_copy_string_buffer(p->path, dir);
	PATHNAME_APPEND_SLASH(p->path);

#ifdef _WIN32
	/* append *.* to the path */
	buffer_append_string_len(p->path, CONST_STR_LEN("*.*"));
#endif

	if (NULL == (dp = opendir(p->path->ptr))) {
		log_error_write(srv, __FILE__, __LINE__, "sbs",
			"opendir failed:", dir, strerror(errno));

		return -1;
	}

	dirs.ent   = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
	assert(dirs.ent);
	dirs.size  = DIRLIST_BLOB_SIZE;
	dirs.used  = 0;
	files.ent  = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
	assert(files.ent);
	files.size = DIRLIST_BLOB_SIZE;
	files.used = 0;

	while ((dent = readdir(dp)) != NULL) {
		unsigned short exclude_match = 0;

		if (dent->d_name[0] == '.') {
			if (hide_dotfiles)
				continue;
			if (dent->d_name[1] == '\0')
				continue;
			if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
				continue;
		}

		if (p->conf.hide_readme_file) {
			if (strcmp(dent->d_name, "README.txt") == 0)
				continue;
		}
		if (p->conf.hide_header_file) {
			if (strcmp(dent->d_name, "HEADER.txt") == 0)
				continue;
		}

		/* compare d_name against excludes array
		 * elements, skipping any that match.
		 */
#ifdef HAVE_PCRE_H
		for(i = 0; i < p->conf.excludes->used; i++) {
			int n;
#define N 10
			int ovec[N * 3];
			pcre *regex = p->conf.excludes->ptr[i]->regex;

			if ((n = pcre_exec(regex, NULL, dent->d_name,
				    strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
				if (n != PCRE_ERROR_NOMATCH) {
					log_error_write(srv, __FILE__, __LINE__, "sd",
						"execution error while matching:", n);

					return -1;
				}
			}
			else {
				exclude_match = 1;
				break;
			}
		}

		if (exclude_match) {
			continue;
		}
#endif

		i = strlen(dent->d_name);

		/* NOTE: the manual says, d_name is never more than NAME_MAX
		 *       so this should actually not be a buffer-overflow-risk
		 */
		if (i > (size_t)name_max) continue;

		/* build the dirname */
		buffer_copy_string_buffer(p->path, dir);
		PATHNAME_APPEND_SLASH(p->path);
		buffer_append_string(p->path, dent->d_name);

		if (stat(p->path->ptr, &st) != 0) {
			fprintf(stderr, "%s.%d: %s, %s\r\n", __FILE__, __LINE__, p->path->ptr, strerror(errno));
			continue;
		}

		list = &files;
		if (S_ISDIR(st.st_mode))
			list = &dirs;

		if (list->used == list->size) {
			list->size += DIRLIST_BLOB_SIZE;
			list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
			assert(list->ent);
		}

		tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
		tmp->mtime = st.st_mtime;
		tmp->size  = st.st_size;
		tmp->namelen = i;
		memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);

		list->ent[list->used++] = tmp;
	}
	closedir(dp);

	if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);

	if (files.used) http_dirls_sort(files.ent, files.used);

	out = chunkqueue_get_append_buffer(con->send);
	buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
	if (buffer_is_empty(p->conf.encoding)) {
		buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
	} else {
		buffer_append_string_buffer(out, p->conf.encoding);
	}
	buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
	http_list_directory_header(srv, con, p, out);

	/* directories */
	for (i = 0; i < dirs.used; i++) {
		tmp = dirs.ent[i];

#ifdef HAVE_LOCALTIME_R
		localtime_r(&(tmp->mtime), &tm);
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
#else
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
#endif

		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));

		free(tmp);
	}

	/* files */
	for (i = 0; i < files.used; i++) {
		tmp = files.ent[i];

		content_type = NULL;

#ifdef HAVE_XATTR
		if (con->conf.use_xattr) {
			/* build the dirname */
			buffer_copy_string_buffer(p->path, dir);
			PATHNAME_APPEND_SLASH(p->path);
			buffer_append_string_len(p->path, DIRLIST_ENT_NAME(tmp), tmp->namelen);

			attrlen = sizeof(attrval) - 1;
			if (attr_get(p->path->ptr, "Content-Type", attrval, &attrlen, 0) == 0) {
				attrval[attrlen] = '\0';
				content_type = attrval;
			}
		}
#endif

		if (content_type == NULL) {
			content_type = "application/octet-stream";
			for (k = 0; k < con->conf.mimetypes->used; k++) {
				data_string *ds = (data_string *)con->conf.mimetypes->data[k];
				size_t ct_len;

				if (ds->key->used == 0)
					continue;

				ct_len = ds->key->used - 1;
				if (tmp->namelen < ct_len)
					continue;

				if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
					content_type = ds->value->ptr;
					break;
				}
			}
		}

#ifdef HAVE_LOCALTIME_R
		localtime_r(&(tmp->mtime), &tm);
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
#else
		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
#endif
		http_list_directory_sizefmt(sizebuf, tmp->size);

		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
		buffer_append_string_len(out, CONST_STR_LEN("\">"));
		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
		buffer_append_string(out, sizebuf);
		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
		buffer_append_string(out, content_type);
		buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));

		free(tmp);
	}

	free(files.ent);
	free(dirs.ent);

	http_list_directory_footer(srv, con, p, out);

	/* Insert possible charset to Content-Type */
	if (buffer_is_empty(p->conf.encoding)) {
		response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
	} else {
		buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
		buffer_append_string_buffer(p->content_charset, p->conf.encoding);
		response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
	}

	con->send->bytes_in += out->used - 1;

	con->send->is_closed = 1;

	return 0;
}



URIHANDLER_FUNC(mod_dirlisting_subrequest) {
	plugin_data *p = p_d;
	stat_cache_entry *sce = NULL;
	buffer *mtime;
	data_string *ds;

	/* we only handle GET, POST and HEAD */
	switch(con->request.http_method) {
	case HTTP_METHOD_GET:
	case HTTP_METHOD_POST:
	case HTTP_METHOD_HEAD:
		break;
	default:
		return HANDLER_GO_ON;
	}

	if (con->uri.path->used < 2) return HANDLER_GO_ON;
	if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
	if (con->physical.path->used == 0) return HANDLER_GO_ON;

	mod_dirlisting_patch_connection(srv, con, p);

	if (!p->conf.dir_listing) return HANDLER_GO_ON;

	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
		/* just a second ago the file was still there */
		return HANDLER_GO_ON;
	}

	if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;

	if (con->conf.log_request_handling) {
		log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
		log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
	}

	/* perhaps this a cachable request
	 * - we use the etag of the directory
	 * */

	etag_mutate(con->physical.etag, sce->etag);
	response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));

	/* prepare header */
	if (NULL == (ds = (data_string *)array_get_element(con->response.headers, CONST_STR_LEN("Last-Modified")))) {
		mtime = strftime_cache_get(srv, sce->st.st_mtime);
		response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
	} else {
		mtime = ds->value;
	}

	if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
		return HANDLER_FINISHED;
	}

	if (http_list_directory(srv, con, p, con->physical.path)) {
		/* dirlisting failed */
		con->http_status = 403;
	}

	buffer_reset(con->physical.path);

	/* not found */
	return HANDLER_FINISHED;
}

/* this function is called at dlopen() time and inits the callbacks */

LI_EXPORT int mod_dirlisting_plugin_init(plugin *p);
LI_EXPORT int mod_dirlisting_plugin_init(plugin *p) {
	p->version     = LIGHTTPD_VERSION_ID;
	p->name        = buffer_init_string("dirlisting");

	p->init        = mod_dirlisting_init;
	p->handle_start_backend  = mod_dirlisting_subrequest;
	p->set_defaults  = mod_dirlisting_set_defaults;
	p->cleanup     = mod_dirlisting_free;

	p->data        = NULL;

	return 0;
}
