xref: /lighttpd1.4/src/mod_dirlisting.c (revision 49e9f0ac)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "fdevent.h"
7 #include "http_header.h"
8 
9 #include "plugin.h"
10 
11 #include "stat_cache.h"
12 
13 #include <stdlib.h>
14 #include <string.h>
15 #include <dirent.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <time.h>
20 
21 #ifdef HAVE_PCRE_H
22 #include <pcre.h>
23 #endif
24 
25 /**
26  * this is a dirlisting for a lighttpd plugin
27  */
28 
29 #ifdef HAVE_ATTR_ATTRIBUTES_H
30 #include <attr/attributes.h>
31 #endif
32 
33 #ifdef HAVE_SYS_EXTATTR_H
34 #include <sys/extattr.h>
35 #endif
36 
37 /* plugin config for all request/connections */
38 
39 typedef struct {
40 #ifdef HAVE_PCRE_H
41 	pcre *regex;
42 #endif
43 	buffer *string;
44 } excludes;
45 
46 typedef struct {
47 	excludes **ptr;
48 
49 	size_t used;
50 	size_t size;
51 } excludes_buffer;
52 
53 typedef struct {
54 	unsigned short dir_listing;
55 	unsigned short hide_dot_files;
56 	unsigned short hide_readme_file;
57 	unsigned short encode_readme;
58 	unsigned short hide_header_file;
59 	unsigned short encode_header;
60 	unsigned short auto_layout;
61 
62 	excludes_buffer *excludes;
63 
64 	buffer *show_readme;
65 	buffer *show_header;
66 	buffer *external_css;
67 	buffer *external_js;
68 	buffer *encoding;
69 	buffer *set_footer;
70 } plugin_config;
71 
72 typedef struct {
73 	PLUGIN_DATA;
74 
75 	buffer *tmp_buf;
76 	buffer *content_charset;
77 
78 	plugin_config **config_storage;
79 
80 	plugin_config conf;
81 } plugin_data;
82 
83 static excludes_buffer *excludes_buffer_init(void) {
84 	excludes_buffer *exb;
85 
86 	exb = calloc(1, sizeof(*exb));
87 
88 	return exb;
89 }
90 
91 #ifdef HAVE_PCRE_H
92 static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
93 	size_t i;
94 	const char *errptr;
95 	int erroff;
96 
97 	if (!string) return -1;
98 
99 	if (exb->used == exb->size) {
100 		exb->size += 4;
101 
102 		exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
103 
104 		for(i = exb->used; i < exb->size; i++) {
105 			exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
106 		}
107 	}
108 
109 
110 	if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
111 						    &errptr, &erroff, NULL))) {
112 		return -1;
113 	}
114 
115 	exb->ptr[exb->used]->string = buffer_init();
116 	buffer_copy_buffer(exb->ptr[exb->used]->string, string);
117 
118 	exb->used++;
119 
120 	return 0;
121 }
122 #endif
123 
124 static void excludes_buffer_free(excludes_buffer *exb) {
125 #ifdef HAVE_PCRE_H
126 	size_t i;
127 
128 	for (i = 0; i < exb->size; i++) {
129 		if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
130 		if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
131 		free(exb->ptr[i]);
132 	}
133 
134 	if (exb->ptr) free(exb->ptr);
135 #endif
136 
137 	free(exb);
138 }
139 
140 /* init the plugin data */
141 INIT_FUNC(mod_dirlisting_init) {
142 	plugin_data *p;
143 
144 	p = calloc(1, sizeof(*p));
145 
146 	p->tmp_buf = buffer_init();
147 	p->content_charset = buffer_init();
148 
149 	return p;
150 }
151 
152 /* detroy the plugin data */
153 FREE_FUNC(mod_dirlisting_free) {
154 	plugin_data *p = p_d;
155 
156 	UNUSED(srv);
157 
158 	if (!p) return HANDLER_GO_ON;
159 
160 	if (p->config_storage) {
161 		size_t i;
162 		for (i = 0; i < srv->config_context->used; i++) {
163 			plugin_config *s = p->config_storage[i];
164 
165 			if (!s) continue;
166 
167 			excludes_buffer_free(s->excludes);
168 			buffer_free(s->show_readme);
169 			buffer_free(s->show_header);
170 			buffer_free(s->external_css);
171 			buffer_free(s->external_js);
172 			buffer_free(s->encoding);
173 			buffer_free(s->set_footer);
174 
175 			free(s);
176 		}
177 		free(p->config_storage);
178 	}
179 
180 	buffer_free(p->tmp_buf);
181 	buffer_free(p->content_charset);
182 
183 	free(p);
184 
185 	return HANDLER_GO_ON;
186 }
187 
188 /* handle plugin config and check values */
189 
190 #define CONFIG_EXCLUDE          "dir-listing.exclude"
191 #define CONFIG_ACTIVATE         "dir-listing.activate"
192 #define CONFIG_HIDE_DOTFILES    "dir-listing.hide-dotfiles"
193 #define CONFIG_EXTERNAL_CSS     "dir-listing.external-css"
194 #define CONFIG_EXTERNAL_JS      "dir-listing.external-js"
195 #define CONFIG_ENCODING         "dir-listing.encoding"
196 #define CONFIG_SHOW_README      "dir-listing.show-readme"
197 #define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
198 #define CONFIG_SHOW_HEADER      "dir-listing.show-header"
199 #define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
200 #define CONFIG_DIR_LISTING      "server.dir-listing"
201 #define CONFIG_SET_FOOTER       "dir-listing.set-footer"
202 #define CONFIG_ENCODE_README    "dir-listing.encode-readme"
203 #define CONFIG_ENCODE_HEADER    "dir-listing.encode-header"
204 #define CONFIG_AUTO_LAYOUT      "dir-listing.auto-layout"
205 
206 
207 SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
208 	plugin_data *p = p_d;
209 	size_t i = 0;
210 
211 	config_values_t cv[] = {
212 		{ CONFIG_EXCLUDE,          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },   /* 0 */
213 		{ CONFIG_ACTIVATE,         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
214 		{ CONFIG_HIDE_DOTFILES,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
215 		{ CONFIG_EXTERNAL_CSS,     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 3 */
216 		{ CONFIG_ENCODING,         NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 4 */
217 		{ CONFIG_SHOW_README,      NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 5 */
218 		{ CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
219 		{ CONFIG_SHOW_HEADER,      NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 7 */
220 		{ CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
221 		{ CONFIG_DIR_LISTING,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
222 		{ CONFIG_SET_FOOTER,       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 10 */
223 		{ CONFIG_ENCODE_README,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
224 		{ CONFIG_ENCODE_HEADER,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
225 		{ CONFIG_AUTO_LAYOUT,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
226 		{ CONFIG_EXTERNAL_JS,      NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 14 */
227 
228 		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
229 	};
230 
231 	if (!p) return HANDLER_ERROR;
232 
233 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
234 
235 	for (i = 0; i < srv->config_context->used; i++) {
236 		data_config const* config = (data_config const*)srv->config_context->data[i];
237 		plugin_config *s;
238 		data_unset *du_excludes;
239 
240 		s = calloc(1, sizeof(plugin_config));
241 		s->excludes = excludes_buffer_init();
242 		s->dir_listing = 0;
243 		s->show_readme = buffer_init();
244 		s->show_header = buffer_init();
245 		s->external_css = buffer_init();
246 		s->external_js = buffer_init();
247 		s->hide_dot_files = 1;
248 		s->hide_readme_file = 0;
249 		s->hide_header_file = 0;
250 		s->encode_readme = 1;
251 		s->encode_header = 1;
252 		s->auto_layout = 1;
253 
254 		s->encoding = buffer_init();
255 		s->set_footer = buffer_init();
256 
257 		cv[0].destination = s->excludes;
258 		cv[1].destination = &(s->dir_listing);
259 		cv[2].destination = &(s->hide_dot_files);
260 		cv[3].destination = s->external_css;
261 		cv[4].destination = s->encoding;
262 		cv[5].destination = s->show_readme;
263 		cv[6].destination = &(s->hide_readme_file);
264 		cv[7].destination = s->show_header;
265 		cv[8].destination = &(s->hide_header_file);
266 		cv[9].destination = &(s->dir_listing); /* old name */
267 		cv[10].destination = s->set_footer;
268 		cv[11].destination = &(s->encode_readme);
269 		cv[12].destination = &(s->encode_header);
270 		cv[13].destination = &(s->auto_layout);
271 		cv[14].destination = s->external_js;
272 
273 		p->config_storage[i] = s;
274 
275 		if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
276 			return HANDLER_ERROR;
277 		}
278 
279 		if (NULL != (du_excludes = array_get_element(config->value, CONFIG_EXCLUDE))) {
280 			array *excludes_list;
281 
282 			excludes_list = ((data_array*)du_excludes)->value;
283 
284 			if (du_excludes->type != TYPE_ARRAY || !array_is_vlist(excludes_list)) {
285 				log_error_write(srv, __FILE__, __LINE__, "s",
286 					"unexpected type for " CONFIG_EXCLUDE "; expected list of \"regex\"");
287 				return HANDLER_ERROR;
288 			}
289 
290 #ifndef HAVE_PCRE_H
291 			if (excludes_list->used > 0) {
292 				log_error_write(srv, __FILE__, __LINE__, "sss",
293 					"pcre support is missing for: ", CONFIG_EXCLUDE, ", please install libpcre and the headers");
294 				return HANDLER_ERROR;
295 			}
296 #else
297 			for (size_t j = 0; j < excludes_list->used; ++j) {
298 				data_unset *du_exclude = excludes_list->data[j];
299 
300 				if (du_exclude->type != TYPE_STRING) {
301 					log_error_write(srv, __FILE__, __LINE__, "sssbs",
302 						"unexpected type for key: ", CONFIG_EXCLUDE, "[",
303 						du_exclude->key, "](string)");
304 					return HANDLER_ERROR;
305 				}
306 
307 				if (0 != excludes_buffer_append(s->excludes, ((data_string*)(du_exclude))->value)) {
308 					log_error_write(srv, __FILE__, __LINE__, "sb",
309 						"pcre-compile failed for", ((data_string*)(du_exclude))->value);
310 					return HANDLER_ERROR;
311 				}
312 			}
313 #endif
314 		}
315 
316 		if (!buffer_string_is_empty(s->show_readme)) {
317 			if (buffer_is_equal_string(s->show_readme, CONST_STR_LEN("enable"))) {
318 				buffer_copy_string_len(s->show_readme, CONST_STR_LEN("README.txt"));
319 			}
320 			else if (buffer_is_equal_string(s->show_readme, CONST_STR_LEN("disable"))) {
321 				buffer_clear(s->show_readme);
322 			}
323 		}
324 
325 		if (!buffer_string_is_empty(s->show_header)) {
326 			if (buffer_is_equal_string(s->show_header, CONST_STR_LEN("enable"))) {
327 				buffer_copy_string_len(s->show_header, CONST_STR_LEN("HEADER.txt"));
328 			}
329 			else if (buffer_is_equal_string(s->show_header, CONST_STR_LEN("disable"))) {
330 				buffer_clear(s->show_header);
331 			}
332 		}
333 	}
334 
335 	return HANDLER_GO_ON;
336 }
337 
338 #define PATCH(x) \
339 	p->conf.x = s->x;
340 static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
341 	size_t i, j;
342 	plugin_config *s = p->config_storage[0];
343 
344 	PATCH(dir_listing);
345 	PATCH(external_css);
346 	PATCH(external_js);
347 	PATCH(hide_dot_files);
348 	PATCH(encoding);
349 	PATCH(show_readme);
350 	PATCH(hide_readme_file);
351 	PATCH(show_header);
352 	PATCH(hide_header_file);
353 	PATCH(excludes);
354 	PATCH(set_footer);
355 	PATCH(encode_readme);
356 	PATCH(encode_header);
357 	PATCH(auto_layout);
358 
359 	/* skip the first, the global context */
360 	for (i = 1; i < srv->config_context->used; i++) {
361 		data_config *dc = (data_config *)srv->config_context->data[i];
362 		s = p->config_storage[i];
363 
364 		/* condition didn't match */
365 		if (!config_check_cond(srv, con, dc)) continue;
366 
367 		/* merge config */
368 		for (j = 0; j < dc->value->used; j++) {
369 			data_unset *du = dc->value->data[j];
370 
371 			if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
372 			    buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
373 				PATCH(dir_listing);
374 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
375 				PATCH(hide_dot_files);
376 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
377 				PATCH(external_css);
378 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_JS))) {
379 				PATCH(external_js);
380 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
381 				PATCH(encoding);
382 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
383 				PATCH(show_readme);
384 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
385 				PATCH(hide_readme_file);
386 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
387 				PATCH(show_header);
388 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
389 				PATCH(hide_header_file);
390 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
391 				PATCH(set_footer);
392 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
393 				PATCH(excludes);
394 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_README))) {
395 				PATCH(encode_readme);
396 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_HEADER))) {
397 				PATCH(encode_header);
398 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_AUTO_LAYOUT))) {
399 				PATCH(auto_layout);
400 			}
401 		}
402 	}
403 
404 	return 0;
405 }
406 #undef PATCH
407 
408 typedef struct {
409 	size_t  namelen;
410 	time_t  mtime;
411 	off_t   size;
412 } dirls_entry_t;
413 
414 typedef struct {
415 	dirls_entry_t **ent;
416 	size_t used;
417 	size_t size;
418 } dirls_list_t;
419 
420 #define DIRLIST_ENT_NAME(ent)	((char*)(ent) + sizeof(dirls_entry_t))
421 #define DIRLIST_BLOB_SIZE		16
422 
423 /* simple combsort algorithm */
424 static void http_dirls_sort(dirls_entry_t **ent, int num) {
425 	int gap = num;
426 	int i, j;
427 	int swapped;
428 	dirls_entry_t *tmp;
429 
430 	do {
431 		gap = (gap * 10) / 13;
432 		if (gap == 9 || gap == 10)
433 			gap = 11;
434 		if (gap < 1)
435 			gap = 1;
436 		swapped = 0;
437 
438 		for (i = 0; i < num - gap; i++) {
439 			j = i + gap;
440 			if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
441 				tmp = ent[i];
442 				ent[i] = ent[j];
443 				ent[j] = tmp;
444 				swapped = 1;
445 			}
446 		}
447 
448 	} while (gap > 1 || swapped);
449 }
450 
451 /* buffer must be able to hold "999.9K"
452  * conversion is simple but not perfect
453  */
454 static int http_list_directory_sizefmt(char *buf, size_t bufsz, off_t size) {
455 	const char unit[] = " KMGTPE";	/* Kilo, Mega, Giga, Tera, Peta, Exa */
456 	const char *u = unit;		/* u will always increment at least once */
457 	int remain;
458 	size_t buflen;
459 
460 	if (size < 100)
461 		size += 99;
462 	if (size < 100)
463 		size = 0;
464 
465 	while (1) {
466 		remain = (int) size & 1023;
467 		size >>= 10;
468 		u++;
469 		if ((size & (~0 ^ 1023)) == 0)
470 			break;
471 	}
472 
473 	remain /= 100;
474 	if (remain > 9)
475 		remain = 9;
476 	if (size > 999) {
477 		size   = 0;
478 		remain = 9;
479 		u++;
480 	}
481 
482 	li_itostrn(buf, bufsz, size);
483 	buflen = strlen(buf);
484 	if (buflen + 3 >= bufsz) return buflen;
485 	buf[buflen+0] = '.';
486 	buf[buflen+1] = remain + '0';
487 	buf[buflen+2] = *u;
488 	buf[buflen+3] = '\0';
489 
490 	return buflen + 3;
491 }
492 
493 static void http_list_directory_include_file(buffer *out, int symlinks, buffer *path, const char *classname, int encode) {
494 	int fd = fdevent_open_cloexec(path->ptr, symlinks, O_RDONLY, 0);
495 	ssize_t rd;
496 	char buf[8192];
497 
498 	if (-1 == fd) return;
499 
500 	if (encode) {
501 		buffer_append_string_len(out, CONST_STR_LEN("<pre class=\""));
502 		buffer_append_string(out, classname);
503 		buffer_append_string_len(out, CONST_STR_LEN("\">"));
504 	}
505 
506 	while ((rd = read(fd, buf, sizeof(buf))) > 0) {
507 		if (encode) {
508 			buffer_append_string_encoded(out, buf, (size_t)rd, ENCODING_MINIMAL_XML);
509 		} else {
510 			buffer_append_string_len(out, buf, (size_t)rd);
511 		}
512 	}
513 	close(fd);
514 
515 	if (encode) {
516 		buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
517 	}
518 }
519 
520 /* portions copied from mod_status
521  * modified and specialized for stable dirlist sorting by name */
522 static const char js_simple_table_resort[] = \
523 "var click_column;\n" \
524 "var name_column = 0;\n" \
525 "var date_column = 1;\n" \
526 "var size_column = 2;\n" \
527 "var type_column = 3;\n" \
528 "var prev_span = null;\n" \
529 "\n" \
530 "if (typeof(String.prototype.localeCompare) === 'undefined') {\n" \
531 " String.prototype.localeCompare = function(str, locale, options) {\n" \
532 "   return ((this == str) ? 0 : ((this > str) ? 1 : -1));\n" \
533 " };\n" \
534 "}\n" \
535 "\n" \
536 "if (typeof(String.prototype.toLocaleUpperCase) === 'undefined') {\n" \
537 " String.prototype.toLocaleUpperCase = function() {\n" \
538 "  return this.toUpperCase();\n" \
539 " };\n" \
540 "}\n" \
541 "\n" \
542 "function get_inner_text(el) {\n" \
543 " if((typeof el == 'string')||(typeof el == 'undefined'))\n" \
544 "  return el;\n" \
545 " if(el.innerText)\n" \
546 "  return el.innerText;\n" \
547 " else {\n" \
548 "  var str = \"\";\n" \
549 "  var cs = el.childNodes;\n" \
550 "  var l = cs.length;\n" \
551 "  for (i=0;i<l;i++) {\n" \
552 "   if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" \
553 "   else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" \
554 "  }\n" \
555 " }\n" \
556 " return str;\n" \
557 "}\n" \
558 "\n" \
559 "function isdigit(c) {\n" \
560 " return (c >= '0' && c <= '9');\n" \
561 "}\n" \
562 "\n" \
563 "function unit_multiplier(unit) {\n" \
564 " return (unit=='K') ? 1000\n" \
565 "      : (unit=='M') ? 1000000\n" \
566 "      : (unit=='G') ? 1000000000\n" \
567 "      : (unit=='T') ? 1000000000000\n" \
568 "      : (unit=='P') ? 1000000000000000\n" \
569 "      : (unit=='E') ? 1000000000000000000 : 1;\n" \
570 "}\n" \
571 "\n" \
572 "var li_date_regex=/(\\d{4})-(\\w{3})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})/;\n" \
573 "\n" \
574 "var li_mon = ['Jan','Feb','Mar','Apr','May','Jun',\n" \
575 "              'Jul','Aug','Sep','Oct','Nov','Dec'];\n" \
576 "\n" \
577 "function li_mon_num(mon) {\n" \
578 " var i; for (i = 0; i < 12 && mon != li_mon[i]; ++i); return i;\n" \
579 "}\n" \
580 "\n" \
581 "function li_date_cmp(s1, s2) {\n" \
582 " var dp1 = li_date_regex.exec(s1)\n" \
583 " var dp2 = li_date_regex.exec(s2)\n" \
584 " for (var i = 1; i < 7; ++i) {\n" \
585 "  var cmp = (2 != i)\n" \
586 "   ? parseInt(dp1[i]) - parseInt(dp2[i])\n" \
587 "   : li_mon_num(dp1[2]) - li_mon_num(dp2[2]);\n" \
588 "  if (0 != cmp) return cmp;\n" \
589 " }\n" \
590 " return 0;\n" \
591 "}\n" \
592 "\n" \
593 "function sortfn_then_by_name(a,b,sort_column) {\n" \
594 " if (sort_column == name_column || sort_column == type_column) {\n" \
595 "  var ad = (a.cells[type_column].innerHTML === 'Directory');\n" \
596 "  var bd = (b.cells[type_column].innerHTML === 'Directory');\n" \
597 "  if (ad != bd) return (ad ? -1 : 1);\n" \
598 " }\n" \
599 " var at = get_inner_text(a.cells[sort_column]);\n" \
600 " var bt = get_inner_text(b.cells[sort_column]);\n" \
601 " var cmp;\n" \
602 " if (sort_column == name_column) {\n" \
603 "  if (at == '..') return -1;\n" \
604 "  if (bt == '..') return  1;\n" \
605 " }\n" \
606 " if (a.cells[sort_column].className == 'int') {\n" \
607 "  cmp = parseInt(at)-parseInt(bt);\n" \
608 " } else if (sort_column == date_column) {\n" \
609 "  var ad = isdigit(at.substr(0,1));\n" \
610 "  var bd = isdigit(bt.substr(0,1));\n" \
611 "  if (ad != bd) return (!ad ? -1 : 1);\n" \
612 "  cmp = li_date_cmp(at,bt);\n" \
613 " } else if (sort_column == size_column) {\n" \
614 "  var ai = parseInt(at, 10) * unit_multiplier(at.substr(-1,1));\n" \
615 "  var bi = parseInt(bt, 10) * unit_multiplier(bt.substr(-1,1));\n" \
616 "  if (at.substr(0,1) == '-') ai = -1;\n" \
617 "  if (bt.substr(0,1) == '-') bi = -1;\n" \
618 "  cmp = ai - bi;\n" \
619 " } else {\n" \
620 "  cmp = at.toLocaleUpperCase().localeCompare(bt.toLocaleUpperCase());\n" \
621 "  if (0 != cmp) return cmp;\n" \
622 "  cmp = at.localeCompare(bt);\n" \
623 " }\n" \
624 " if (0 != cmp || sort_column == name_column) return cmp;\n" \
625 " return sortfn_then_by_name(a,b,name_column);\n" \
626 "}\n" \
627 "\n" \
628 "function sortfn(a,b) {\n" \
629 " return sortfn_then_by_name(a,b,click_column);\n" \
630 "}\n" \
631 "\n" \
632 "function resort(lnk) {\n" \
633 " var span = lnk.childNodes[1];\n" \
634 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" \
635 " var rows = new Array();\n" \
636 " for (j=1;j<table.rows.length;j++)\n" \
637 "  rows[j-1] = table.rows[j];\n" \
638 " click_column = lnk.parentNode.cellIndex;\n" \
639 " rows.sort(sortfn);\n" \
640 "\n" \
641 " if (prev_span != null) prev_span.innerHTML = '';\n" \
642 " if (span.getAttribute('sortdir')=='down') {\n" \
643 "  span.innerHTML = '&uarr;';\n" \
644 "  span.setAttribute('sortdir','up');\n" \
645 "  rows.reverse();\n" \
646 " } else {\n" \
647 "  span.innerHTML = '&darr;';\n" \
648 "  span.setAttribute('sortdir','down');\n" \
649 " }\n" \
650 " for (i=0;i<rows.length;i++)\n" \
651 "  table.tBodies[0].appendChild(rows[i]);\n" \
652 " prev_span = span;\n" \
653 "}\n";
654 
655 /* portions copied from mod_dirlist (lighttpd2) */
656 static const char js_simple_table_init_sort[] = \
657 "\n" \
658 "function init_sort(init_sort_column, ascending) {\n" \
659 " var tables = document.getElementsByTagName(\"table\");\n" \
660 " for (var i = 0; i < tables.length; i++) {\n" \
661 "  var table = tables[i];\n" \
662 "  //var c = table.getAttribute(\"class\")\n" \
663 "  //if (-1 != c.split(\" \").indexOf(\"sort\")) {\n" \
664 "   var row = table.rows[0].cells;\n" \
665 "   for (var j = 0; j < row.length; j++) {\n" \
666 "    var n = row[j];\n" \
667 "    if (n.childNodes.length == 1 && n.childNodes[0].nodeType == 3) {\n" \
668 "     var link = document.createElement(\"a\");\n" \
669 "     var title = n.childNodes[0].nodeValue.replace(/:$/, \"\");\n" \
670 "     link.appendChild(document.createTextNode(title));\n" \
671 "     link.setAttribute(\"href\", \"#\");\n" \
672 "     link.setAttribute(\"class\", \"sortheader\");\n" \
673 "     link.setAttribute(\"onclick\", \"resort(this);return false;\");\n" \
674 "     var arrow = document.createElement(\"span\");\n" \
675 "     arrow.setAttribute(\"class\", \"sortarrow\");\n" \
676 "     arrow.appendChild(document.createTextNode(\":\"));\n" \
677 "     link.appendChild(arrow)\n" \
678 "     n.replaceChild(link, n.firstChild);\n" \
679 "    }\n" \
680 "   }\n" \
681 "   var lnk = row[init_sort_column].firstChild;\n" \
682 "   if (ascending) {\n" \
683 "    var span = lnk.childNodes[1];\n" \
684 "    span.setAttribute('sortdir','down');\n" \
685 "   }\n" \
686 "   resort(lnk);\n" \
687 "  //}\n" \
688 " }\n" \
689 "}\n";
690 
691 static void http_dirlist_append_js_table_resort (buffer *b, connection *con) {
692 	char col = '0';
693 	char ascending = '0';
694 	if (!buffer_string_is_empty(con->uri.query)) {
695 		const char *qs = con->uri.query->ptr;
696 		do {
697 			if (qs[0] == 'C' && qs[1] == '=') {
698 				switch (qs[2]) {
699 				case 'N': col = '0'; break;
700 				case 'M': col = '1'; break;
701 				case 'S': col = '2'; break;
702 				case 'T':
703 				case 'D': col = '3'; break;
704 				default:  break;
705 				}
706 			}
707 			else if (qs[0] == 'O' && qs[1] == '=') {
708 				switch (qs[2]) {
709 				case 'A': ascending = '1'; break;
710 				case 'D': ascending = '0'; break;
711 				default:  break;
712 				}
713 			}
714 		} while ((qs = strchr(qs, '&')) && *++qs);
715 	}
716 
717 	buffer_append_string_len(b, CONST_STR_LEN("\n<script type=\"text/javascript\">\n// <!--\n\n"));
718 	buffer_append_string_len(b, js_simple_table_resort, sizeof(js_simple_table_resort)-1);
719 	buffer_append_string_len(b, js_simple_table_init_sort, sizeof(js_simple_table_init_sort)-1);
720 	buffer_append_string_len(b, CONST_STR_LEN("\ninit_sort("));
721 	buffer_append_string_len(b, &col, 1);
722 	buffer_append_string_len(b, CONST_STR_LEN(", "));
723 	buffer_append_string_len(b, &ascending, 1);
724 	buffer_append_string_len(b, CONST_STR_LEN(");\n\n// -->\n</script>\n\n"));
725 }
726 
727 static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
728 	UNUSED(srv);
729 
730 	if (p->conf.auto_layout) {
731 		buffer_append_string_len(out, CONST_STR_LEN(
732 			"<!DOCTYPE html>\n"
733 			"<html>\n"
734 			"<head>\n"
735 		));
736 		if (!buffer_string_is_empty(p->conf.encoding)) {
737 			buffer_append_string_len(out, CONST_STR_LEN("<meta charset=\""));
738 			buffer_append_string_buffer(out, p->conf.encoding);
739 			buffer_append_string_len(out, CONST_STR_LEN("\">\n"));
740 		}
741 		buffer_append_string_len(out, CONST_STR_LEN("<title>Index of "));
742 		buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
743 		buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
744 
745 		if (!buffer_string_is_empty(p->conf.external_css)) {
746 			buffer_append_string_len(out, CONST_STR_LEN("<meta name=\"viewport\" content=\"initial-scale=1\">"));
747 			buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
748 			buffer_append_string_buffer(out, p->conf.external_css);
749 			buffer_append_string_len(out, CONST_STR_LEN("\">\n"));
750 		} else {
751 			buffer_append_string_len(out, CONST_STR_LEN(
752 				"<style type=\"text/css\">\n"
753 				"a, a:active {text-decoration: none; color: blue;}\n"
754 				"a:visited {color: #48468F;}\n"
755 				"a:hover, a:focus {text-decoration: underline; color: red;}\n"
756 				"body {background-color: #F5F5F5;}\n"
757 				"h2 {margin-bottom: 12px;}\n"
758 				"table {margin-left: 12px;}\n"
759 				"th, td {"
760 				" font: 90% monospace;"
761 				" text-align: left;"
762 				"}\n"
763 				"th {"
764 				" font-weight: bold;"
765 				" padding-right: 14px;"
766 				" padding-bottom: 3px;"
767 				"}\n"
768 				"td {padding-right: 14px;}\n"
769 				"td.s, th.s {text-align: right;}\n"
770 				"div.list {"
771 				" background-color: white;"
772 				" border-top: 1px solid #646464;"
773 				" border-bottom: 1px solid #646464;"
774 				" padding-top: 10px;"
775 				" padding-bottom: 14px;"
776 				"}\n"
777 				"div.foot {"
778 				" font: 90% monospace;"
779 				" color: #787878;"
780 				" padding-top: 4px;"
781 				"}\n"
782 				"</style>\n"
783 			));
784 		}
785 
786 		buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
787 	}
788 
789 	if (!buffer_string_is_empty(p->conf.show_header)) {
790 		/* if we have a HEADER file, display it in <pre class="header"></pre> */
791 
792 		buffer *hb = p->conf.show_header;
793 		if (hb->ptr[0] != '/') {
794 			buffer_copy_buffer(p->tmp_buf, con->physical.path);
795 			buffer_append_path_len(p->tmp_buf, CONST_BUF_LEN(p->conf.show_header));
796 			hb = p->tmp_buf;
797 		}
798 
799 		http_list_directory_include_file(out, con->conf.follow_symlink, hb, "header", p->conf.encode_header);
800 	}
801 
802 	buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
803 	buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
804 	buffer_append_string_len(out, CONST_STR_LEN(
805 		"</h2>\n"
806 		"<div class=\"list\">\n"
807 		"<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
808 		"<thead>"
809 		"<tr>"
810 			"<th class=\"n\">Name</th>"
811 			"<th class=\"m\">Last Modified</th>"
812 			"<th class=\"s\">Size</th>"
813 			"<th class=\"t\">Type</th>"
814 		"</tr>"
815 		"</thead>\n"
816 		"<tbody>\n"
817 	));
818 	if (!buffer_is_equal_string(con->uri.path, CONST_STR_LEN("/"))) {
819 		buffer_append_string_len(out, CONST_STR_LEN(
820 		"<tr class=\"d\">"
821 			"<td class=\"n\"><a href=\"../\">..</a>/</td>"
822 			"<td class=\"m\">&nbsp;</td>"
823 			"<td class=\"s\">- &nbsp;</td>"
824 			"<td class=\"t\">Directory</td>"
825 		"</tr>\n"
826 		));
827 	}
828 }
829 
830 static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
831 	UNUSED(srv);
832 
833 	buffer_append_string_len(out, CONST_STR_LEN(
834 		"</tbody>\n"
835 		"</table>\n"
836 		"</div>\n"
837 	));
838 
839 	if (!buffer_string_is_empty(p->conf.show_readme)) {
840 		/* if we have a README file, display it in <pre class="readme"></pre> */
841 
842 		buffer *rb = p->conf.show_readme;
843 		if (rb->ptr[0] != '/') {
844 			buffer_copy_buffer(p->tmp_buf,  con->physical.path);
845 			buffer_append_path_len(p->tmp_buf, CONST_BUF_LEN(p->conf.show_readme));
846 			rb = p->tmp_buf;
847 		}
848 
849 		http_list_directory_include_file(out, con->conf.follow_symlink, rb, "readme", p->conf.encode_readme);
850 	}
851 
852 	if(p->conf.auto_layout) {
853 
854 		buffer_append_string_len(out, CONST_STR_LEN(
855 			"<div class=\"foot\">"
856 		));
857 
858 		if (!buffer_string_is_empty(p->conf.set_footer)) {
859 			buffer_append_string_buffer(out, p->conf.set_footer);
860 		} else {
861 			buffer_append_string_buffer(out, con->conf.server_tag);
862 		}
863 
864 		buffer_append_string_len(out, CONST_STR_LEN(
865 			"</div>\n"
866 		));
867 
868 		if (!buffer_string_is_empty(p->conf.external_js)) {
869 			buffer_append_string_len(out, CONST_STR_LEN("<script type=\"text/javascript\" src=\""));
870 			buffer_append_string_buffer(out, p->conf.external_js);
871 			buffer_append_string_len(out, CONST_STR_LEN("\"></script>\n"));
872 		} else if (buffer_is_empty(p->conf.external_js)) {
873 			http_dirlist_append_js_table_resort(out, con);
874 		}
875 
876 		buffer_append_string_len(out, CONST_STR_LEN(
877 			"</body>\n"
878 			"</html>\n"
879 		));
880 	}
881 }
882 
883 static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
884 	DIR *dp;
885 	buffer *out;
886 	struct dirent *dent;
887 	struct stat st;
888 	char *path, *path_file;
889 	size_t i;
890 	int hide_dotfiles = p->conf.hide_dot_files;
891 	dirls_list_t dirs, files, *list;
892 	dirls_entry_t *tmp;
893 	char sizebuf[sizeof("999.9K")];
894 	char datebuf[sizeof("2005-Jan-01 22:23:24")];
895 	const char *content_type;
896 	long name_max;
897 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
898 	char attrval[128];
899 	int attrlen;
900 #endif
901 #ifdef HAVE_LOCALTIME_R
902 	struct tm tm;
903 #endif
904 
905 	if (buffer_string_is_empty(dir)) return -1;
906 
907 	i = buffer_string_length(dir);
908 
909 #ifdef HAVE_PATHCONF
910 	if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
911 		/* some broken fs (fuse) return 0 instead of -1 */
912 #ifdef NAME_MAX
913 		name_max = NAME_MAX;
914 #else
915 		name_max = 255; /* stupid default */
916 #endif
917 	}
918 #elif defined __WIN32
919 	name_max = FILENAME_MAX;
920 #else
921 	name_max = NAME_MAX;
922 #endif
923 
924 	path = malloc(i + name_max + 1);
925 	force_assert(NULL != path);
926 	memcpy(path, dir->ptr, i+1);
927 	path_file = path + i;
928 
929 	if (NULL == (dp = opendir(path))) {
930 		log_error_write(srv, __FILE__, __LINE__, "sbs",
931 			"opendir failed:", dir, strerror(errno));
932 
933 		free(path);
934 		return -1;
935 	}
936 
937 	dirs.ent   = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
938 	force_assert(dirs.ent);
939 	dirs.size  = DIRLIST_BLOB_SIZE;
940 	dirs.used  = 0;
941 	files.ent  = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
942 	force_assert(files.ent);
943 	files.size = DIRLIST_BLOB_SIZE;
944 	files.used = 0;
945 
946 	while ((dent = readdir(dp)) != NULL) {
947 #ifdef HAVE_PCRE_H
948 		unsigned short exclude_match = 0;
949 #endif
950 
951 		if (dent->d_name[0] == '.') {
952 			if (hide_dotfiles)
953 				continue;
954 			if (dent->d_name[1] == '\0')
955 				continue;
956 			if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
957 				continue;
958 		}
959 
960 		if (p->conf.hide_readme_file && !buffer_string_is_empty(p->conf.show_readme)) {
961 			if (strcmp(dent->d_name, p->conf.show_readme->ptr) == 0)
962 				continue;
963 		}
964 		if (p->conf.hide_header_file && !buffer_string_is_empty(p->conf.show_header)) {
965 			if (strcmp(dent->d_name, p->conf.show_header->ptr) == 0)
966 				continue;
967 		}
968 
969 		/* compare d_name against excludes array
970 		 * elements, skipping any that match.
971 		 */
972 #ifdef HAVE_PCRE_H
973 		for(i = 0; i < p->conf.excludes->used; i++) {
974 			int n;
975 #define N 10
976 			int ovec[N * 3];
977 			pcre *regex = p->conf.excludes->ptr[i]->regex;
978 
979 			if ((n = pcre_exec(regex, NULL, dent->d_name,
980 				    strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
981 				if (n != PCRE_ERROR_NOMATCH) {
982 					log_error_write(srv, __FILE__, __LINE__, "sd",
983 						"execution error while matching:", n);
984 
985 					/* aborting would require a lot of manual cleanup here.
986 					 * skip instead (to not leak names that break pcre matching)
987 					 */
988 					exclude_match = 1;
989 					break;
990 				}
991 			}
992 			else {
993 				exclude_match = 1;
994 				break;
995 			}
996 		}
997 
998 		if (exclude_match) {
999 			continue;
1000 		}
1001 #endif
1002 
1003 		i = strlen(dent->d_name);
1004 
1005 		/* NOTE: the manual says, d_name is never more than NAME_MAX
1006 		 *       so this should actually not be a buffer-overflow-risk
1007 		 */
1008 		if (i > (size_t)name_max) continue;
1009 
1010 		memcpy(path_file, dent->d_name, i + 1);
1011 		if (stat(path, &st) != 0)
1012 			continue;
1013 
1014 		list = &files;
1015 		if (S_ISDIR(st.st_mode))
1016 			list = &dirs;
1017 
1018 		if (list->used == list->size) {
1019 			list->size += DIRLIST_BLOB_SIZE;
1020 			list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
1021 			force_assert(list->ent);
1022 		}
1023 
1024 		tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
1025 		tmp->mtime = st.st_mtime;
1026 		tmp->size  = st.st_size;
1027 		tmp->namelen = i;
1028 		memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
1029 
1030 		list->ent[list->used++] = tmp;
1031 	}
1032 	closedir(dp);
1033 
1034 	if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
1035 
1036 	if (files.used) http_dirls_sort(files.ent, files.used);
1037 
1038 	out = chunkqueue_append_buffer_open(con->write_queue);
1039 	http_list_directory_header(srv, con, p, out);
1040 
1041 	/* directories */
1042 	for (i = 0; i < dirs.used; i++) {
1043 		tmp = dirs.ent[i];
1044 
1045 #ifdef HAVE_LOCALTIME_R
1046 		localtime_r(&(tmp->mtime), &tm);
1047 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
1048 #else
1049 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
1050 #endif
1051 
1052 		buffer_append_string_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\""));
1053 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
1054 		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
1055 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
1056 		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
1057 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
1058 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
1059 
1060 		free(tmp);
1061 	}
1062 
1063 	/* files */
1064 	for (i = 0; i < files.used; i++) {
1065 		tmp = files.ent[i];
1066 
1067 		content_type = NULL;
1068 #if defined(HAVE_XATTR)
1069 		if (con->conf.use_xattr) {
1070 			memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
1071 			attrlen = sizeof(attrval) - 1;
1072 			if (attr_get(path, srv->srvconf.xattr_name->ptr, attrval, &attrlen, 0) == 0) {
1073 				attrval[attrlen] = '\0';
1074 				content_type = attrval;
1075 			}
1076 		}
1077 #elif defined(HAVE_EXTATTR)
1078 		if (con->conf.use_xattr) {
1079 			memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
1080 			if(-1 != (attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, srv->srvconf.xattr_name->ptr, attrval, sizeof(attrval)-1))) {
1081 				attrval[attrlen] = '\0';
1082 				content_type = attrval;
1083 			}
1084 		}
1085 #endif
1086 
1087 		if (content_type == NULL) {
1088 			const buffer *type = stat_cache_mimetype_by_ext(con, DIRLIST_ENT_NAME(tmp), tmp->namelen);
1089 			content_type = NULL != type ? type->ptr : "application/octet-stream";
1090 		}
1091 
1092 #ifdef HAVE_LOCALTIME_R
1093 		localtime_r(&(tmp->mtime), &tm);
1094 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
1095 #else
1096 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
1097 #endif
1098 		http_list_directory_sizefmt(sizebuf, sizeof(sizebuf), tmp->size);
1099 
1100 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
1101 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
1102 		buffer_append_string_len(out, CONST_STR_LEN("\">"));
1103 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
1104 		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
1105 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
1106 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
1107 		buffer_append_string(out, sizebuf);
1108 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
1109 		buffer_append_string(out, content_type);
1110 		buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
1111 
1112 		free(tmp);
1113 	}
1114 
1115 	free(files.ent);
1116 	free(dirs.ent);
1117 	free(path);
1118 
1119 	http_list_directory_footer(srv, con, p, out);
1120 
1121 	/* Insert possible charset to Content-Type */
1122 	if (buffer_string_is_empty(p->conf.encoding)) {
1123 		http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1124 	} else {
1125 		buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
1126 		buffer_append_string_buffer(p->content_charset, p->conf.encoding);
1127 		http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
1128 	}
1129 
1130 	chunkqueue_append_buffer_commit(con->write_queue);
1131 	con->file_finished = 1;
1132 
1133 	return 0;
1134 }
1135 
1136 
1137 
1138 URIHANDLER_FUNC(mod_dirlisting_subrequest) {
1139 	plugin_data *p = p_d;
1140 	stat_cache_entry *sce = NULL;
1141 
1142 	UNUSED(srv);
1143 
1144 	/* we only handle GET and HEAD */
1145 	switch(con->request.http_method) {
1146 	case HTTP_METHOD_GET:
1147 	case HTTP_METHOD_HEAD:
1148 		break;
1149 	default:
1150 		return HANDLER_GO_ON;
1151 	}
1152 
1153 	if (con->mode != DIRECT) return HANDLER_GO_ON;
1154 
1155 	if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1156 	if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON;
1157 	if (con->uri.path->ptr[buffer_string_length(con->uri.path) - 1] != '/') return HANDLER_GO_ON;
1158 
1159 	mod_dirlisting_patch_connection(srv, con, p);
1160 
1161 	if (!p->conf.dir_listing) return HANDLER_GO_ON;
1162 
1163 	if (con->conf.log_request_handling) {
1164 		log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
1165 		log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
1166 	}
1167 
1168 	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
1169 		log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
1170 		SEGFAULT();
1171 	}
1172 
1173 	if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
1174 
1175 	if (http_list_directory(srv, con, p, con->physical.path)) {
1176 		/* dirlisting failed */
1177 		con->http_status = 403;
1178 	}
1179 
1180 	buffer_reset(con->physical.path);
1181 
1182 	/* not found */
1183 	return HANDLER_FINISHED;
1184 }
1185 
1186 /* this function is called at dlopen() time and inits the callbacks */
1187 
1188 int mod_dirlisting_plugin_init(plugin *p);
1189 int mod_dirlisting_plugin_init(plugin *p) {
1190 	p->version     = LIGHTTPD_VERSION_ID;
1191 	p->name        = buffer_init_string("dirlisting");
1192 
1193 	p->init        = mod_dirlisting_init;
1194 	p->handle_subrequest_start  = mod_dirlisting_subrequest;
1195 	p->set_defaults  = mod_dirlisting_set_defaults;
1196 	p->cleanup     = mod_dirlisting_free;
1197 
1198 	p->data        = NULL;
1199 
1200 	return 0;
1201 }
1202