File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src / mod_dirlisting.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 20:20:06 2014 UTC (10 years ago) by misho
Branches: lighttpd, MAIN
CVS tags: v1_4_35p0, v1_4_35, HEAD
lighttpd 1.4.35

    1: #include "base.h"
    2: #include "log.h"
    3: #include "buffer.h"
    4: 
    5: #include "plugin.h"
    6: 
    7: #include "response.h"
    8: #include "stat_cache.h"
    9: #include "stream.h"
   10: 
   11: #include <ctype.h>
   12: #include <stdlib.h>
   13: #include <string.h>
   14: #include <dirent.h>
   15: #include <assert.h>
   16: #include <errno.h>
   17: #include <stdio.h>
   18: #include <unistd.h>
   19: #include <time.h>
   20: 
   21: /**
   22:  * this is a dirlisting for a lighttpd plugin
   23:  */
   24: 
   25: 
   26: #ifdef HAVE_SYS_SYSLIMITS_H
   27: #include <sys/syslimits.h>
   28: #endif
   29: 
   30: #ifdef HAVE_ATTR_ATTRIBUTES_H
   31: #include <attr/attributes.h>
   32: #endif
   33: 
   34: #include "version.h"
   35: 
   36: /* plugin config for all request/connections */
   37: 
   38: typedef struct {
   39: #ifdef HAVE_PCRE_H
   40: 	pcre *regex;
   41: #endif
   42: 	buffer *string;
   43: } excludes;
   44: 
   45: typedef struct {
   46: 	excludes **ptr;
   47: 
   48: 	size_t used;
   49: 	size_t size;
   50: } excludes_buffer;
   51: 
   52: typedef struct {
   53: 	unsigned short dir_listing;
   54: 	unsigned short hide_dot_files;
   55: 	unsigned short show_readme;
   56: 	unsigned short hide_readme_file;
   57: 	unsigned short encode_readme;
   58: 	unsigned short show_header;
   59: 	unsigned short hide_header_file;
   60: 	unsigned short encode_header;
   61: 	unsigned short auto_layout;
   62: 
   63: 	excludes_buffer *excludes;
   64: 
   65: 	buffer *external_css;
   66: 	buffer *encoding;
   67: 	buffer *set_footer;
   68: } plugin_config;
   69: 
   70: typedef struct {
   71: 	PLUGIN_DATA;
   72: 
   73: 	buffer *tmp_buf;
   74: 	buffer *content_charset;
   75: 
   76: 	plugin_config **config_storage;
   77: 
   78: 	plugin_config conf;
   79: } plugin_data;
   80: 
   81: static excludes_buffer *excludes_buffer_init(void) {
   82: 	excludes_buffer *exb;
   83: 
   84: 	exb = calloc(1, sizeof(*exb));
   85: 
   86: 	return exb;
   87: }
   88: 
   89: static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
   90: #ifdef HAVE_PCRE_H
   91: 	size_t i;
   92: 	const char *errptr;
   93: 	int erroff;
   94: 
   95: 	if (!string) return -1;
   96: 
   97: 	if (exb->size == 0) {
   98: 		exb->size = 4;
   99: 		exb->used = 0;
  100: 
  101: 		exb->ptr = malloc(exb->size * sizeof(*exb->ptr));
  102: 
  103: 		for(i = 0; i < exb->size ; i++) {
  104: 			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
  105: 		}
  106: 	} else if (exb->used == exb->size) {
  107: 		exb->size += 4;
  108: 
  109: 		exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
  110: 
  111: 		for(i = exb->used; i < exb->size; i++) {
  112: 			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
  113: 		}
  114: 	}
  115: 
  116: 
  117: 	if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
  118: 						    &errptr, &erroff, NULL))) {
  119: 		return -1;
  120: 	}
  121: 
  122: 	exb->ptr[exb->used]->string = buffer_init();
  123: 	buffer_copy_string_buffer(exb->ptr[exb->used]->string, string);
  124: 
  125: 	exb->used++;
  126: 
  127: 	return 0;
  128: #else
  129: 	UNUSED(exb);
  130: 	UNUSED(string);
  131: 
  132: 	return -1;
  133: #endif
  134: }
  135: 
  136: static void excludes_buffer_free(excludes_buffer *exb) {
  137: #ifdef HAVE_PCRE_H
  138: 	size_t i;
  139: 
  140: 	for (i = 0; i < exb->size; i++) {
  141: 		if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
  142: 		if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
  143: 		free(exb->ptr[i]);
  144: 	}
  145: 
  146: 	if (exb->ptr) free(exb->ptr);
  147: #endif
  148: 
  149: 	free(exb);
  150: }
  151: 
  152: /* init the plugin data */
  153: INIT_FUNC(mod_dirlisting_init) {
  154: 	plugin_data *p;
  155: 
  156: 	p = calloc(1, sizeof(*p));
  157: 
  158: 	p->tmp_buf = buffer_init();
  159: 	p->content_charset = buffer_init();
  160: 
  161: 	return p;
  162: }
  163: 
  164: /* detroy the plugin data */
  165: FREE_FUNC(mod_dirlisting_free) {
  166: 	plugin_data *p = p_d;
  167: 
  168: 	UNUSED(srv);
  169: 
  170: 	if (!p) return HANDLER_GO_ON;
  171: 
  172: 	if (p->config_storage) {
  173: 		size_t i;
  174: 		for (i = 0; i < srv->config_context->used; i++) {
  175: 			plugin_config *s = p->config_storage[i];
  176: 
  177: 			if (!s) continue;
  178: 
  179: 			excludes_buffer_free(s->excludes);
  180: 			buffer_free(s->external_css);
  181: 			buffer_free(s->encoding);
  182: 			buffer_free(s->set_footer);
  183: 
  184: 			free(s);
  185: 		}
  186: 		free(p->config_storage);
  187: 	}
  188: 
  189: 	buffer_free(p->tmp_buf);
  190: 	buffer_free(p->content_charset);
  191: 
  192: 	free(p);
  193: 
  194: 	return HANDLER_GO_ON;
  195: }
  196: 
  197: static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) {
  198: 	data_unset *du;
  199: 
  200: 	if (NULL != (du = array_get_element(ca, option))) {
  201: 		data_array *da;
  202: 		size_t j;
  203: 
  204: 		if (du->type != TYPE_ARRAY) {
  205: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  206: 				"unexpected type for key: ", option, "array of strings");
  207: 
  208: 			return HANDLER_ERROR;
  209: 		}
  210: 
  211: 		da = (data_array *)du;
  212: 
  213: 		for (j = 0; j < da->value->used; j++) {
  214: 			if (da->value->data[j]->type != TYPE_STRING) {
  215: 				log_error_write(srv, __FILE__, __LINE__, "sssbs",
  216: 					"unexpected type for key: ", option, "[",
  217: 					da->value->data[j]->key, "](string)");
  218: 
  219: 				return HANDLER_ERROR;
  220: 			}
  221: 
  222: 			if (0 != excludes_buffer_append(s->excludes,
  223: 				    ((data_string *)(da->value->data[j]))->value)) {
  224: #ifdef HAVE_PCRE_H
  225: 				log_error_write(srv, __FILE__, __LINE__, "sb",
  226: 						"pcre-compile failed for", ((data_string *)(da->value->data[j]))->value);
  227: #else
  228: 				log_error_write(srv, __FILE__, __LINE__, "s",
  229: 						"pcre support is missing, please install libpcre and the headers");
  230: #endif
  231: 			}
  232: 		}
  233: 	}
  234: 
  235: 	return 0;
  236: }
  237: 
  238: /* handle plugin config and check values */
  239: 
  240: #define CONFIG_EXCLUDE          "dir-listing.exclude"
  241: #define CONFIG_ACTIVATE         "dir-listing.activate"
  242: #define CONFIG_HIDE_DOTFILES    "dir-listing.hide-dotfiles"
  243: #define CONFIG_EXTERNAL_CSS     "dir-listing.external-css"
  244: #define CONFIG_ENCODING         "dir-listing.encoding"
  245: #define CONFIG_SHOW_README      "dir-listing.show-readme"
  246: #define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
  247: #define CONFIG_SHOW_HEADER      "dir-listing.show-header"
  248: #define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
  249: #define CONFIG_DIR_LISTING      "server.dir-listing"
  250: #define CONFIG_SET_FOOTER       "dir-listing.set-footer"
  251: #define CONFIG_ENCODE_README    "dir-listing.encode-readme"
  252: #define CONFIG_ENCODE_HEADER    "dir-listing.encode-header"
  253: #define CONFIG_AUTO_LAYOUT      "dir-listing.auto-layout"
  254: 
  255: 
  256: SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
  257: 	plugin_data *p = p_d;
  258: 	size_t i = 0;
  259: 
  260: 	config_values_t cv[] = {
  261: 		{ CONFIG_EXCLUDE,          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },   /* 0 */
  262: 		{ CONFIG_ACTIVATE,         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
  263: 		{ CONFIG_HIDE_DOTFILES,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
  264: 		{ CONFIG_EXTERNAL_CSS,     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 3 */
  265: 		{ CONFIG_ENCODING,         NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 4 */
  266: 		{ CONFIG_SHOW_README,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
  267: 		{ CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
  268: 		{ CONFIG_SHOW_HEADER,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
  269: 		{ CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
  270: 		{ CONFIG_DIR_LISTING,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
  271: 		{ CONFIG_SET_FOOTER,       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 10 */
  272: 		{ CONFIG_ENCODE_README,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
  273: 		{ CONFIG_ENCODE_HEADER,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
  274: 		{ CONFIG_AUTO_LAYOUT,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
  275: 
  276: 		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
  277: 	};
  278: 
  279: 	if (!p) return HANDLER_ERROR;
  280: 
  281: 	p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
  282: 
  283: 	for (i = 0; i < srv->config_context->used; i++) {
  284: 		plugin_config *s;
  285: 		array *ca;
  286: 
  287: 		s = calloc(1, sizeof(plugin_config));
  288: 		s->excludes = excludes_buffer_init();
  289: 		s->dir_listing = 0;
  290: 		s->external_css = buffer_init();
  291: 		s->hide_dot_files = 0;
  292: 		s->show_readme = 0;
  293: 		s->hide_readme_file = 0;
  294: 		s->show_header = 0;
  295: 		s->hide_header_file = 0;
  296: 		s->encode_readme = 1;
  297: 		s->encode_header = 1;
  298: 		s->auto_layout = 1;
  299: 
  300: 		s->encoding = buffer_init();
  301: 		s->set_footer = buffer_init();
  302: 
  303: 		cv[0].destination = s->excludes;
  304: 		cv[1].destination = &(s->dir_listing);
  305: 		cv[2].destination = &(s->hide_dot_files);
  306: 		cv[3].destination = s->external_css;
  307: 		cv[4].destination = s->encoding;
  308: 		cv[5].destination = &(s->show_readme);
  309: 		cv[6].destination = &(s->hide_readme_file);
  310: 		cv[7].destination = &(s->show_header);
  311: 		cv[8].destination = &(s->hide_header_file);
  312: 		cv[9].destination = &(s->dir_listing); /* old name */
  313: 		cv[10].destination = s->set_footer;
  314: 		cv[11].destination = &(s->encode_readme);
  315: 		cv[12].destination = &(s->encode_header);
  316: 		cv[13].destination = &(s->auto_layout);
  317: 
  318: 		p->config_storage[i] = s;
  319: 		ca = ((data_config *)srv->config_context->data[i])->value;
  320: 
  321: 		if (0 != config_insert_values_global(srv, ca, cv)) {
  322: 			return HANDLER_ERROR;
  323: 		}
  324: 
  325: 		parse_config_entry(srv, s, ca, CONFIG_EXCLUDE);
  326: 	}
  327: 
  328: 	return HANDLER_GO_ON;
  329: }
  330: 
  331: #define PATCH(x) \
  332: 	p->conf.x = s->x;
  333: static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
  334: 	size_t i, j;
  335: 	plugin_config *s = p->config_storage[0];
  336: 
  337: 	PATCH(dir_listing);
  338: 	PATCH(external_css);
  339: 	PATCH(hide_dot_files);
  340: 	PATCH(encoding);
  341: 	PATCH(show_readme);
  342: 	PATCH(hide_readme_file);
  343: 	PATCH(show_header);
  344: 	PATCH(hide_header_file);
  345: 	PATCH(excludes);
  346: 	PATCH(set_footer);
  347: 	PATCH(encode_readme);
  348: 	PATCH(encode_header);
  349: 	PATCH(auto_layout);
  350: 
  351: 	/* skip the first, the global context */
  352: 	for (i = 1; i < srv->config_context->used; i++) {
  353: 		data_config *dc = (data_config *)srv->config_context->data[i];
  354: 		s = p->config_storage[i];
  355: 
  356: 		/* condition didn't match */
  357: 		if (!config_check_cond(srv, con, dc)) continue;
  358: 
  359: 		/* merge config */
  360: 		for (j = 0; j < dc->value->used; j++) {
  361: 			data_unset *du = dc->value->data[j];
  362: 
  363: 			if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
  364: 			    buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
  365: 				PATCH(dir_listing);
  366: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
  367: 				PATCH(hide_dot_files);
  368: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
  369: 				PATCH(external_css);
  370: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
  371: 				PATCH(encoding);
  372: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
  373: 				PATCH(show_readme);
  374: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
  375: 				PATCH(hide_readme_file);
  376: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
  377: 				PATCH(show_header);
  378: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
  379: 				PATCH(hide_header_file);
  380: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
  381: 				PATCH(set_footer);
  382: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
  383: 				PATCH(excludes);
  384: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_README))) {
  385: 				PATCH(encode_readme);
  386: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_HEADER))) {
  387: 				PATCH(encode_header);
  388: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_AUTO_LAYOUT))) {
  389: 				PATCH(auto_layout);
  390: 			}
  391: 		}
  392: 	}
  393: 
  394: 	return 0;
  395: }
  396: #undef PATCH
  397: 
  398: typedef struct {
  399: 	size_t  namelen;
  400: 	time_t  mtime;
  401: 	off_t   size;
  402: } dirls_entry_t;
  403: 
  404: typedef struct {
  405: 	dirls_entry_t **ent;
  406: 	size_t used;
  407: 	size_t size;
  408: } dirls_list_t;
  409: 
  410: #define DIRLIST_ENT_NAME(ent)	((char*)(ent) + sizeof(dirls_entry_t))
  411: #define DIRLIST_BLOB_SIZE		16
  412: 
  413: /* simple combsort algorithm */
  414: static void http_dirls_sort(dirls_entry_t **ent, int num) {
  415: 	int gap = num;
  416: 	int i, j;
  417: 	int swapped;
  418: 	dirls_entry_t *tmp;
  419: 
  420: 	do {
  421: 		gap = (gap * 10) / 13;
  422: 		if (gap == 9 || gap == 10)
  423: 			gap = 11;
  424: 		if (gap < 1)
  425: 			gap = 1;
  426: 		swapped = 0;
  427: 
  428: 		for (i = 0; i < num - gap; i++) {
  429: 			j = i + gap;
  430: 			if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
  431: 				tmp = ent[i];
  432: 				ent[i] = ent[j];
  433: 				ent[j] = tmp;
  434: 				swapped = 1;
  435: 			}
  436: 		}
  437: 
  438: 	} while (gap > 1 || swapped);
  439: }
  440: 
  441: /* buffer must be able to hold "999.9K"
  442:  * conversion is simple but not perfect
  443:  */
  444: static int http_list_directory_sizefmt(char *buf, off_t size) {
  445: 	const char unit[] = "KMGTPE";	/* Kilo, Mega, Tera, Peta, Exa */
  446: 	const char *u = unit - 1;		/* u will always increment at least once */
  447: 	int remain;
  448: 	char *out = buf;
  449: 
  450: 	if (size < 100)
  451: 		size += 99;
  452: 	if (size < 100)
  453: 		size = 0;
  454: 
  455: 	while (1) {
  456: 		remain = (int) size & 1023;
  457: 		size >>= 10;
  458: 		u++;
  459: 		if ((size & (~0 ^ 1023)) == 0)
  460: 			break;
  461: 	}
  462: 
  463: 	remain /= 100;
  464: 	if (remain > 9)
  465: 		remain = 9;
  466: 	if (size > 999) {
  467: 		size   = 0;
  468: 		remain = 9;
  469: 		u++;
  470: 	}
  471: 
  472: 	out   += LI_ltostr(out, size);
  473: 	out[0] = '.';
  474: 	out[1] = remain + '0';
  475: 	out[2] = *u;
  476: 	out[3] = '\0';
  477: 
  478: 	return (out + 3 - buf);
  479: }
  480: 
  481: static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
  482: 	UNUSED(srv);
  483: 
  484: 	if (p->conf.auto_layout) {
  485: 		buffer_append_string_len(out, CONST_STR_LEN(
  486: 			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
  487: 			"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
  488: 			"<head>\n"
  489: 			"<title>Index of "
  490: 		));
  491: 		buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
  492: 		buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
  493: 
  494: 		if (p->conf.external_css->used > 1) {
  495: 			buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
  496: 			buffer_append_string_buffer(out, p->conf.external_css);
  497: 			buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
  498: 		} else {
  499: 			buffer_append_string_len(out, CONST_STR_LEN(
  500: 				"<style type=\"text/css\">\n"
  501: 				"a, a:active {text-decoration: none; color: blue;}\n"
  502: 				"a:visited {color: #48468F;}\n"
  503: 				"a:hover, a:focus {text-decoration: underline; color: red;}\n"
  504: 				"body {background-color: #F5F5F5;}\n"
  505: 				"h2 {margin-bottom: 12px;}\n"
  506: 				"table {margin-left: 12px;}\n"
  507: 				"th, td {"
  508: 				" font: 90% monospace;"
  509: 				" text-align: left;"
  510: 				"}\n"
  511: 				"th {"
  512: 				" font-weight: bold;"
  513: 				" padding-right: 14px;"
  514: 				" padding-bottom: 3px;"
  515: 				"}\n"
  516: 				"td {padding-right: 14px;}\n"
  517: 				"td.s, th.s {text-align: right;}\n"
  518: 				"div.list {"
  519: 				" background-color: white;"
  520: 				" border-top: 1px solid #646464;"
  521: 				" border-bottom: 1px solid #646464;"
  522: 				" padding-top: 10px;"
  523: 				" padding-bottom: 14px;"
  524: 				"}\n"
  525: 				"div.foot {"
  526: 				" font: 90% monospace;"
  527: 				" color: #787878;"
  528: 				" padding-top: 4px;"
  529: 				"}\n"
  530: 				"</style>\n"
  531: 			));
  532: 		}
  533: 
  534: 		buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
  535: 	}
  536: 
  537: 	/* HEADER.txt */
  538: 	if (p->conf.show_header) {
  539: 		stream s;
  540: 		/* if we have a HEADER file, display it in <pre class="header"></pre> */
  541: 
  542: 		buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
  543: 		BUFFER_APPEND_SLASH(p->tmp_buf);
  544: 		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("HEADER.txt"));
  545: 
  546: 		if (-1 != stream_open(&s, p->tmp_buf)) {
  547: 			if (p->conf.encode_header) {
  548: 				buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"header\">"));
  549: 				buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
  550: 				buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
  551: 			} else {
  552: 				buffer_append_string_len(out, s.start, s.size);
  553: 			}
  554: 		}
  555: 		stream_close(&s);
  556: 	}
  557: 
  558: 	buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
  559: 	buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
  560: 	buffer_append_string_len(out, CONST_STR_LEN(
  561: 		"</h2>\n"
  562: 		"<div class=\"list\">\n"
  563: 		"<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
  564: 		"<thead>"
  565: 		"<tr>"
  566: 			"<th class=\"n\">Name</th>"
  567: 			"<th class=\"m\">Last Modified</th>"
  568: 			"<th class=\"s\">Size</th>"
  569: 			"<th class=\"t\">Type</th>"
  570: 		"</tr>"
  571: 		"</thead>\n"
  572: 		"<tbody>\n"
  573: 		"<tr>"
  574: 			"<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
  575: 			"<td class=\"m\">&nbsp;</td>"
  576: 			"<td class=\"s\">- &nbsp;</td>"
  577: 			"<td class=\"t\">Directory</td>"
  578: 		"</tr>\n"
  579: 	));
  580: }
  581: 
  582: static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
  583: 	UNUSED(srv);
  584: 
  585: 	buffer_append_string_len(out, CONST_STR_LEN(
  586: 		"</tbody>\n"
  587: 		"</table>\n"
  588: 		"</div>\n"
  589: 	));
  590: 
  591: 	if (p->conf.show_readme) {
  592: 		stream s;
  593: 		/* if we have a README file, display it in <pre class="readme"></pre> */
  594: 
  595: 		buffer_copy_string_buffer(p->tmp_buf,  con->physical.path);
  596: 		BUFFER_APPEND_SLASH(p->tmp_buf);
  597: 		buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("README.txt"));
  598: 
  599: 		if (-1 != stream_open(&s, p->tmp_buf)) {
  600: 			if (p->conf.encode_readme) {
  601: 				buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"readme\">"));
  602: 				buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
  603: 				buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
  604: 			} else {
  605: 				buffer_append_string_len(out, s.start, s.size);
  606: 			}
  607: 		}
  608: 		stream_close(&s);
  609: 	}
  610: 
  611: 	if(p->conf.auto_layout) {
  612: 		buffer_append_string_len(out, CONST_STR_LEN(
  613: 			"<div class=\"foot\">"
  614: 		));
  615: 
  616: 		if (p->conf.set_footer->used > 1) {
  617: 			buffer_append_string_buffer(out, p->conf.set_footer);
  618: 		} else if (buffer_is_empty(con->conf.server_tag)) {
  619: 			buffer_append_string_len(out, CONST_STR_LEN(PACKAGE_DESC));
  620: 		} else {
  621: 			buffer_append_string_buffer(out, con->conf.server_tag);
  622: 		}
  623: 
  624: 		buffer_append_string_len(out, CONST_STR_LEN(
  625: 			"</div>\n"
  626: 			"</body>\n"
  627: 			"</html>\n"
  628: 		));
  629: 	}
  630: }
  631: 
  632: static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
  633: 	DIR *dp;
  634: 	buffer *out;
  635: 	struct dirent *dent;
  636: 	struct stat st;
  637: 	char *path, *path_file;
  638: 	size_t i;
  639: 	int hide_dotfiles = p->conf.hide_dot_files;
  640: 	dirls_list_t dirs, files, *list;
  641: 	dirls_entry_t *tmp;
  642: 	char sizebuf[sizeof("999.9K")];
  643: 	char datebuf[sizeof("2005-Jan-01 22:23:24")];
  644: 	size_t k;
  645: 	const char *content_type;
  646: 	long name_max;
  647: #ifdef HAVE_XATTR
  648: 	char attrval[128];
  649: 	int attrlen;
  650: #endif
  651: #ifdef HAVE_LOCALTIME_R
  652: 	struct tm tm;
  653: #endif
  654: 
  655: 	if (dir->used == 0) return -1;
  656: 
  657: 	i = dir->used - 1;
  658: 
  659: #ifdef HAVE_PATHCONF
  660: 	if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
  661: 		/* some broken fs (fuse) return 0 instead of -1 */
  662: #ifdef NAME_MAX
  663: 		name_max = NAME_MAX;
  664: #else
  665: 		name_max = 255; /* stupid default */
  666: #endif
  667: 	}
  668: #elif defined __WIN32
  669: 	name_max = FILENAME_MAX;
  670: #else
  671: 	name_max = NAME_MAX;
  672: #endif
  673: 
  674: 	path = malloc(dir->used + name_max);
  675: 	force_assert(path);
  676: 	strcpy(path, dir->ptr);
  677: 	path_file = path + i;
  678: 
  679: 	if (NULL == (dp = opendir(path))) {
  680: 		log_error_write(srv, __FILE__, __LINE__, "sbs",
  681: 			"opendir failed:", dir, strerror(errno));
  682: 
  683: 		free(path);
  684: 		return -1;
  685: 	}
  686: 
  687: 	dirs.ent   = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
  688: 	force_assert(dirs.ent);
  689: 	dirs.size  = DIRLIST_BLOB_SIZE;
  690: 	dirs.used  = 0;
  691: 	files.ent  = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
  692: 	force_assert(files.ent);
  693: 	files.size = DIRLIST_BLOB_SIZE;
  694: 	files.used = 0;
  695: 
  696: 	while ((dent = readdir(dp)) != NULL) {
  697: 		unsigned short exclude_match = 0;
  698: 
  699: 		if (dent->d_name[0] == '.') {
  700: 			if (hide_dotfiles)
  701: 				continue;
  702: 			if (dent->d_name[1] == '\0')
  703: 				continue;
  704: 			if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
  705: 				continue;
  706: 		}
  707: 
  708: 		if (p->conf.hide_readme_file) {
  709: 			if (strcmp(dent->d_name, "README.txt") == 0)
  710: 				continue;
  711: 		}
  712: 		if (p->conf.hide_header_file) {
  713: 			if (strcmp(dent->d_name, "HEADER.txt") == 0)
  714: 				continue;
  715: 		}
  716: 
  717: 		/* compare d_name against excludes array
  718: 		 * elements, skipping any that match.
  719: 		 */
  720: #ifdef HAVE_PCRE_H
  721: 		for(i = 0; i < p->conf.excludes->used; i++) {
  722: 			int n;
  723: #define N 10
  724: 			int ovec[N * 3];
  725: 			pcre *regex = p->conf.excludes->ptr[i]->regex;
  726: 
  727: 			if ((n = pcre_exec(regex, NULL, dent->d_name,
  728: 				    strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
  729: 				if (n != PCRE_ERROR_NOMATCH) {
  730: 					log_error_write(srv, __FILE__, __LINE__, "sd",
  731: 						"execution error while matching:", n);
  732: 
  733: 					/* aborting would require a lot of manual cleanup here.
  734: 					 * skip instead (to not leak names that break pcre matching)
  735: 					 */
  736: 					exclude_match = 1;
  737: 					break;
  738: 				}
  739: 			}
  740: 			else {
  741: 				exclude_match = 1;
  742: 				break;
  743: 			}
  744: 		}
  745: 
  746: 		if (exclude_match) {
  747: 			continue;
  748: 		}
  749: #endif
  750: 
  751: 		i = strlen(dent->d_name);
  752: 
  753: 		/* NOTE: the manual says, d_name is never more than NAME_MAX
  754: 		 *       so this should actually not be a buffer-overflow-risk
  755: 		 */
  756: 		if (i > (size_t)name_max) continue;
  757: 
  758: 		memcpy(path_file, dent->d_name, i + 1);
  759: 		if (stat(path, &st) != 0)
  760: 			continue;
  761: 
  762: 		list = &files;
  763: 		if (S_ISDIR(st.st_mode))
  764: 			list = &dirs;
  765: 
  766: 		if (list->used == list->size) {
  767: 			list->size += DIRLIST_BLOB_SIZE;
  768: 			list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
  769: 			force_assert(list->ent);
  770: 		}
  771: 
  772: 		tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
  773: 		tmp->mtime = st.st_mtime;
  774: 		tmp->size  = st.st_size;
  775: 		tmp->namelen = i;
  776: 		memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
  777: 
  778: 		list->ent[list->used++] = tmp;
  779: 	}
  780: 	closedir(dp);
  781: 
  782: 	if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
  783: 
  784: 	if (files.used) http_dirls_sort(files.ent, files.used);
  785: 
  786: 	out = chunkqueue_get_append_buffer(con->write_queue);
  787: 	buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
  788: 	if (buffer_is_empty(p->conf.encoding)) {
  789: 		buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
  790: 	} else {
  791: 		buffer_append_string_buffer(out, p->conf.encoding);
  792: 	}
  793: 	buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
  794: 	http_list_directory_header(srv, con, p, out);
  795: 
  796: 	/* directories */
  797: 	for (i = 0; i < dirs.used; i++) {
  798: 		tmp = dirs.ent[i];
  799: 
  800: #ifdef HAVE_LOCALTIME_R
  801: 		localtime_r(&(tmp->mtime), &tm);
  802: 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
  803: #else
  804: 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
  805: #endif
  806: 
  807: 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
  808: 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
  809: 		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
  810: 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
  811: 		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
  812: 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
  813: 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
  814: 
  815: 		free(tmp);
  816: 	}
  817: 
  818: 	/* files */
  819: 	for (i = 0; i < files.used; i++) {
  820: 		tmp = files.ent[i];
  821: 
  822: 		content_type = NULL;
  823: #ifdef HAVE_XATTR
  824: 
  825: 		if (con->conf.use_xattr) {
  826: 			memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
  827: 			attrlen = sizeof(attrval) - 1;
  828: 			if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
  829: 				attrval[attrlen] = '\0';
  830: 				content_type = attrval;
  831: 			}
  832: 		}
  833: #endif
  834: 
  835: 		if (content_type == NULL) {
  836: 			content_type = "application/octet-stream";
  837: 			for (k = 0; k < con->conf.mimetypes->used; k++) {
  838: 				data_string *ds = (data_string *)con->conf.mimetypes->data[k];
  839: 				size_t ct_len;
  840: 
  841: 				if (ds->key->used == 0)
  842: 					continue;
  843: 
  844: 				ct_len = ds->key->used - 1;
  845: 				if (tmp->namelen < ct_len)
  846: 					continue;
  847: 
  848: 				if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
  849: 					content_type = ds->value->ptr;
  850: 					break;
  851: 				}
  852: 			}
  853: 		}
  854: 
  855: #ifdef HAVE_LOCALTIME_R
  856: 		localtime_r(&(tmp->mtime), &tm);
  857: 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
  858: #else
  859: 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
  860: #endif
  861: 		http_list_directory_sizefmt(sizebuf, tmp->size);
  862: 
  863: 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
  864: 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
  865: 		buffer_append_string_len(out, CONST_STR_LEN("\">"));
  866: 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
  867: 		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
  868: 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
  869: 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
  870: 		buffer_append_string(out, sizebuf);
  871: 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
  872: 		buffer_append_string(out, content_type);
  873: 		buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
  874: 
  875: 		free(tmp);
  876: 	}
  877: 
  878: 	free(files.ent);
  879: 	free(dirs.ent);
  880: 	free(path);
  881: 
  882: 	http_list_directory_footer(srv, con, p, out);
  883: 
  884: 	/* Insert possible charset to Content-Type */
  885: 	if (buffer_is_empty(p->conf.encoding)) {
  886: 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
  887: 	} else {
  888: 		buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
  889: 		buffer_append_string_buffer(p->content_charset, p->conf.encoding);
  890: 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
  891: 	}
  892: 
  893: 	con->file_finished = 1;
  894: 
  895: 	return 0;
  896: }
  897: 
  898: 
  899: 
  900: URIHANDLER_FUNC(mod_dirlisting_subrequest) {
  901: 	plugin_data *p = p_d;
  902: 	stat_cache_entry *sce = NULL;
  903: 
  904: 	UNUSED(srv);
  905: 
  906: 	/* we only handle GET, POST and HEAD */
  907: 	switch(con->request.http_method) {
  908: 	case HTTP_METHOD_GET:
  909: 	case HTTP_METHOD_POST:
  910: 	case HTTP_METHOD_HEAD:
  911: 		break;
  912: 	default:
  913: 		return HANDLER_GO_ON;
  914: 	}
  915: 
  916: 	if (con->mode != DIRECT) return HANDLER_GO_ON;
  917: 
  918: 	if (con->physical.path->used == 0) return HANDLER_GO_ON;
  919: 	if (con->uri.path->used == 0) return HANDLER_GO_ON;
  920: 	if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
  921: 
  922: 	mod_dirlisting_patch_connection(srv, con, p);
  923: 
  924: 	if (!p->conf.dir_listing) return HANDLER_GO_ON;
  925: 
  926: 	if (con->conf.log_request_handling) {
  927: 		log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
  928: 		log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
  929: 	}
  930: 
  931: 	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
  932: 		log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
  933: 		SEGFAULT();
  934: 	}
  935: 
  936: 	if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
  937: 
  938: 	if (http_list_directory(srv, con, p, con->physical.path)) {
  939: 		/* dirlisting failed */
  940: 		con->http_status = 403;
  941: 	}
  942: 
  943: 	buffer_reset(con->physical.path);
  944: 
  945: 	/* not found */
  946: 	return HANDLER_FINISHED;
  947: }
  948: 
  949: /* this function is called at dlopen() time and inits the callbacks */
  950: 
  951: int mod_dirlisting_plugin_init(plugin *p);
  952: int mod_dirlisting_plugin_init(plugin *p) {
  953: 	p->version     = LIGHTTPD_VERSION_ID;
  954: 	p->name        = buffer_init_string("dirlisting");
  955: 
  956: 	p->init        = mod_dirlisting_init;
  957: 	p->handle_subrequest_start  = mod_dirlisting_subrequest;
  958: 	p->set_defaults  = mod_dirlisting_set_defaults;
  959: 	p->cleanup     = mod_dirlisting_free;
  960: 
  961: 	p->data        = NULL;
  962: 
  963: 	return 0;
  964: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>