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