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