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 
excludes_buffer_init(void)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 
excludes_buffer_append(excludes_buffer * exb,buffer * string)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 
excludes_buffer_free(excludes_buffer * exb)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 */
INIT_FUNC(mod_dirlisting_init)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 */
FREE_FUNC(mod_dirlisting_free)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 
parse_config_entry(server * srv,plugin_config * s,array * ca,const char * option)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 
SETDEFAULTS_FUNC(mod_dirlisting_set_defaults)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(specific_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;
mod_dirlisting_patch_connection(server * srv,connection * con,plugin_data * p)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 */
http_dirls_sort(dirls_entry_t ** ent,int num)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  */
http_list_directory_sizefmt(char * buf,off_t size)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 
http_list_directory_header(server * srv,connection * con,plugin_data * p,buffer * out)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 
http_list_directory_footer(server * srv,connection * con,plugin_data * p,buffer * out)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 
http_list_directory(server * srv,connection * con,plugin_data * p,buffer * dir)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 	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 	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 	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 					return -1;
734 				}
735 			}
736 			else {
737 				exclude_match = 1;
738 				break;
739 			}
740 		}
741 
742 		if (exclude_match) {
743 			continue;
744 		}
745 #endif
746 
747 		i = strlen(dent->d_name);
748 
749 		/* NOTE: the manual says, d_name is never more than NAME_MAX
750 		 *       so this should actually not be a buffer-overflow-risk
751 		 */
752 		if (i > (size_t)name_max) continue;
753 
754 		memcpy(path_file, dent->d_name, i + 1);
755 		if (stat(path, &st) != 0)
756 			continue;
757 
758 		list = &files;
759 		if (S_ISDIR(st.st_mode))
760 			list = &dirs;
761 
762 		if (list->used == list->size) {
763 			list->size += DIRLIST_BLOB_SIZE;
764 			list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
765 			assert(list->ent);
766 		}
767 
768 		tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
769 		tmp->mtime = st.st_mtime;
770 		tmp->size  = st.st_size;
771 		tmp->namelen = i;
772 		memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
773 
774 		list->ent[list->used++] = tmp;
775 	}
776 	closedir(dp);
777 
778 	if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
779 
780 	if (files.used) http_dirls_sort(files.ent, files.used);
781 
782 	out = chunkqueue_get_append_buffer(con->write_queue);
783 	buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
784 	if (buffer_is_empty(p->conf.encoding)) {
785 		buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
786 	} else {
787 		buffer_append_string_buffer(out, p->conf.encoding);
788 	}
789 	buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
790 	http_list_directory_header(srv, con, p, out);
791 
792 	/* directories */
793 	for (i = 0; i < dirs.used; i++) {
794 		tmp = dirs.ent[i];
795 
796 #ifdef HAVE_LOCALTIME_R
797 		localtime_r(&(tmp->mtime), &tm);
798 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
799 #else
800 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
801 #endif
802 
803 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
804 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
805 		buffer_append_string_len(out, CONST_STR_LEN("/\">"));
806 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
807 		buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
808 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
809 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
810 
811 		free(tmp);
812 	}
813 
814 	/* files */
815 	for (i = 0; i < files.used; i++) {
816 		tmp = files.ent[i];
817 
818 		content_type = NULL;
819 #ifdef HAVE_XATTR
820 
821 		if (con->conf.use_xattr) {
822 			memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
823 			attrlen = sizeof(attrval) - 1;
824 			if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
825 				attrval[attrlen] = '\0';
826 				content_type = attrval;
827 			}
828 		}
829 #endif
830 
831 		if (content_type == NULL) {
832 			content_type = "application/octet-stream";
833 			for (k = 0; k < con->conf.mimetypes->used; k++) {
834 				data_string *ds = (data_string *)con->conf.mimetypes->data[k];
835 				size_t ct_len;
836 
837 				if (ds->key->used == 0)
838 					continue;
839 
840 				ct_len = ds->key->used - 1;
841 				if (tmp->namelen < ct_len)
842 					continue;
843 
844 				if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
845 					content_type = ds->value->ptr;
846 					break;
847 				}
848 			}
849 		}
850 
851 #ifdef HAVE_LOCALTIME_R
852 		localtime_r(&(tmp->mtime), &tm);
853 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
854 #else
855 		strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
856 #endif
857 		http_list_directory_sizefmt(sizebuf, tmp->size);
858 
859 		buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
860 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
861 		buffer_append_string_len(out, CONST_STR_LEN("\">"));
862 		buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
863 		buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
864 		buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
865 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
866 		buffer_append_string(out, sizebuf);
867 		buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
868 		buffer_append_string(out, content_type);
869 		buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
870 
871 		free(tmp);
872 	}
873 
874 	free(files.ent);
875 	free(dirs.ent);
876 	free(path);
877 
878 	http_list_directory_footer(srv, con, p, out);
879 
880 	/* Insert possible charset to Content-Type */
881 	if (buffer_is_empty(p->conf.encoding)) {
882 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
883 	} else {
884 		buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
885 		buffer_append_string_buffer(p->content_charset, p->conf.encoding);
886 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
887 	}
888 
889 	con->file_finished = 1;
890 
891 	return 0;
892 }
893 
894 
895 
URIHANDLER_FUNC(mod_dirlisting_subrequest)896 URIHANDLER_FUNC(mod_dirlisting_subrequest) {
897 	plugin_data *p = p_d;
898 	stat_cache_entry *sce = NULL;
899 
900 	UNUSED(srv);
901 
902 	/* we only handle GET, POST and HEAD */
903 	switch(con->request.http_method) {
904 	case HTTP_METHOD_GET:
905 	case HTTP_METHOD_POST:
906 	case HTTP_METHOD_HEAD:
907 		break;
908 	default:
909 		return HANDLER_GO_ON;
910 	}
911 
912 	if (con->mode != DIRECT) return HANDLER_GO_ON;
913 
914 	if (con->physical.path->used == 0) return HANDLER_GO_ON;
915 	if (con->uri.path->used == 0) return HANDLER_GO_ON;
916 	if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
917 
918 	mod_dirlisting_patch_connection(srv, con, p);
919 
920 	if (!p->conf.dir_listing) return HANDLER_GO_ON;
921 
922 	if (con->conf.log_request_handling) {
923 		log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
924 		log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
925 	}
926 
927 	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
928 		log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
929 		SEGFAULT();
930 	}
931 
932 	if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
933 
934 	if (http_list_directory(srv, con, p, con->physical.path)) {
935 		/* dirlisting failed */
936 		con->http_status = 403;
937 	}
938 
939 	buffer_reset(con->physical.path);
940 
941 	/* not found */
942 	return HANDLER_FINISHED;
943 }
944 
945 /* this function is called at dlopen() time and inits the callbacks */
946 
947 int mod_dirlisting_plugin_init(plugin *p);
mod_dirlisting_plugin_init(plugin * p)948 int mod_dirlisting_plugin_init(plugin *p) {
949 	p->version     = LIGHTTPD_VERSION_ID;
950 	p->name        = buffer_init_string("dirlisting");
951 
952 	p->init        = mod_dirlisting_init;
953 	p->handle_subrequest_start  = mod_dirlisting_subrequest;
954 	p->set_defaults  = mod_dirlisting_set_defaults;
955 	p->cleanup     = mod_dirlisting_free;
956 
957 	p->data        = NULL;
958 
959 	return 0;
960 }
961