File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src / mod_dirlisting.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Nov 2 10:35:00 2016 UTC (7 years, 7 months ago) by misho
Branches: lighttpd, MAIN
CVS tags: v1_4_41p8, HEAD
lighttpd 1.4.41

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

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