Annotation of embedaddon/lighttpd/src/mod_dirlisting.c, revision 1.1.1.2
1.1 misho 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:
81: static excludes_buffer *excludes_buffer_init(void) {
82: excludes_buffer *exb;
83:
84: exb = calloc(1, sizeof(*exb));
85:
86: return exb;
87: }
88:
89: static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
90: #ifdef HAVE_PCRE_H
91: size_t i;
92: const char *errptr;
93: int erroff;
94:
95: if (!string) return -1;
96:
97: if (exb->size == 0) {
98: exb->size = 4;
99: exb->used = 0;
100:
101: exb->ptr = malloc(exb->size * sizeof(*exb->ptr));
102:
103: for(i = 0; i < exb->size ; i++) {
104: exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
105: }
106: } else if (exb->used == exb->size) {
107: exb->size += 4;
108:
109: exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
110:
111: for(i = exb->used; i < exb->size; i++) {
112: exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
113: }
114: }
115:
116:
117: if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
118: &errptr, &erroff, NULL))) {
119: return -1;
120: }
121:
122: exb->ptr[exb->used]->string = buffer_init();
123: buffer_copy_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:
136: static void excludes_buffer_free(excludes_buffer *exb) {
137: #ifdef HAVE_PCRE_H
138: size_t i;
139:
140: for (i = 0; i < exb->size; i++) {
141: if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
142: if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
143: free(exb->ptr[i]);
144: }
145:
146: if (exb->ptr) free(exb->ptr);
147: #endif
148:
149: free(exb);
150: }
151:
152: /* init the plugin data */
153: INIT_FUNC(mod_dirlisting_init) {
154: plugin_data *p;
155:
156: p = calloc(1, sizeof(*p));
157:
158: p->tmp_buf = buffer_init();
159: p->content_charset = buffer_init();
160:
161: return p;
162: }
163:
164: /* detroy the plugin data */
165: FREE_FUNC(mod_dirlisting_free) {
166: plugin_data *p = p_d;
167:
168: UNUSED(srv);
169:
170: if (!p) return HANDLER_GO_ON;
171:
172: if (p->config_storage) {
173: size_t i;
174: for (i = 0; i < srv->config_context->used; i++) {
175: plugin_config *s = p->config_storage[i];
176:
177: if (!s) continue;
178:
179: excludes_buffer_free(s->excludes);
180: buffer_free(s->external_css);
181: buffer_free(s->encoding);
182: buffer_free(s->set_footer);
183:
184: free(s);
185: }
186: free(p->config_storage);
187: }
188:
189: buffer_free(p->tmp_buf);
190: buffer_free(p->content_charset);
191:
192: free(p);
193:
194: return HANDLER_GO_ON;
195: }
196:
197: 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:
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:
1.1.1.2 ! misho 281: p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1 misho 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;
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 */
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: */
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:
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\"> </td>"
576: "<td class=\"s\">- </td>"
577: "<td class=\"t\">Directory</td>"
578: "</tr>\n"
579: ));
580: }
581:
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:
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);
1.1.1.2 ! misho 675: force_assert(path);
1.1 misho 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);
1.1.1.2 ! misho 688: force_assert(dirs.ent);
1.1 misho 689: dirs.size = DIRLIST_BLOB_SIZE;
690: dirs.used = 0;
691: files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
1.1.1.2 ! misho 692: force_assert(files.ent);
1.1 misho 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:
1.1.1.2 ! misho 733: /* aborting would require a lot of manual cleanup here.
! 734: * skip instead (to not leak names that break pcre matching)
! 735: */
! 736: exclude_match = 1;
! 737: break;
1.1 misho 738: }
739: }
740: else {
741: exclude_match = 1;
742: break;
743: }
744: }
745:
746: if (exclude_match) {
747: continue;
748: }
749: #endif
750:
751: i = strlen(dent->d_name);
752:
753: /* NOTE: the manual says, d_name is never more than NAME_MAX
754: * so this should actually not be a buffer-overflow-risk
755: */
756: if (i > (size_t)name_max) continue;
757:
758: memcpy(path_file, dent->d_name, i + 1);
759: if (stat(path, &st) != 0)
760: continue;
761:
762: list = &files;
763: if (S_ISDIR(st.st_mode))
764: list = &dirs;
765:
766: if (list->used == list->size) {
767: list->size += DIRLIST_BLOB_SIZE;
768: list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
1.1.1.2 ! misho 769: force_assert(list->ent);
1.1 misho 770: }
771:
772: tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
773: tmp->mtime = st.st_mtime;
774: tmp->size = st.st_size;
775: tmp->namelen = i;
776: memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
777:
778: list->ent[list->used++] = tmp;
779: }
780: closedir(dp);
781:
782: if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
783:
784: if (files.used) http_dirls_sort(files.ent, files.used);
785:
786: out = chunkqueue_get_append_buffer(con->write_queue);
787: buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
788: if (buffer_is_empty(p->conf.encoding)) {
789: buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
790: } else {
791: buffer_append_string_buffer(out, p->conf.encoding);
792: }
793: buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
794: http_list_directory_header(srv, con, p, out);
795:
796: /* directories */
797: for (i = 0; i < dirs.used; i++) {
798: tmp = dirs.ent[i];
799:
800: #ifdef HAVE_LOCALTIME_R
801: localtime_r(&(tmp->mtime), &tm);
802: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
803: #else
804: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
805: #endif
806:
807: buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
808: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
809: buffer_append_string_len(out, CONST_STR_LEN("/\">"));
810: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
811: buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
812: buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
813: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"));
814:
815: free(tmp);
816: }
817:
818: /* files */
819: for (i = 0; i < files.used; i++) {
820: tmp = files.ent[i];
821:
822: content_type = NULL;
823: #ifdef HAVE_XATTR
824:
825: if (con->conf.use_xattr) {
826: memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
827: attrlen = sizeof(attrval) - 1;
828: if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
829: attrval[attrlen] = '\0';
830: content_type = attrval;
831: }
832: }
833: #endif
834:
835: if (content_type == NULL) {
836: content_type = "application/octet-stream";
837: for (k = 0; k < con->conf.mimetypes->used; k++) {
838: data_string *ds = (data_string *)con->conf.mimetypes->data[k];
839: size_t ct_len;
840:
841: if (ds->key->used == 0)
842: continue;
843:
844: ct_len = ds->key->used - 1;
845: if (tmp->namelen < ct_len)
846: continue;
847:
848: if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
849: content_type = ds->value->ptr;
850: break;
851: }
852: }
853: }
854:
855: #ifdef HAVE_LOCALTIME_R
856: localtime_r(&(tmp->mtime), &tm);
857: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
858: #else
859: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
860: #endif
861: http_list_directory_sizefmt(sizebuf, tmp->size);
862:
863: buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
864: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
865: buffer_append_string_len(out, CONST_STR_LEN("\">"));
866: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
867: buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
868: buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
869: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
870: buffer_append_string(out, sizebuf);
871: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
872: buffer_append_string(out, content_type);
873: buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
874:
875: free(tmp);
876: }
877:
878: free(files.ent);
879: free(dirs.ent);
880: free(path);
881:
882: http_list_directory_footer(srv, con, p, out);
883:
884: /* Insert possible charset to Content-Type */
885: if (buffer_is_empty(p->conf.encoding)) {
886: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
887: } else {
888: buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
889: buffer_append_string_buffer(p->content_charset, p->conf.encoding);
890: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
891: }
892:
893: con->file_finished = 1;
894:
895: return 0;
896: }
897:
898:
899:
900: URIHANDLER_FUNC(mod_dirlisting_subrequest) {
901: plugin_data *p = p_d;
902: stat_cache_entry *sce = NULL;
903:
904: UNUSED(srv);
905:
906: /* we only handle GET, POST and HEAD */
907: switch(con->request.http_method) {
908: case HTTP_METHOD_GET:
909: case HTTP_METHOD_POST:
910: case HTTP_METHOD_HEAD:
911: break;
912: default:
913: return HANDLER_GO_ON;
914: }
915:
916: if (con->mode != DIRECT) return HANDLER_GO_ON;
917:
918: if (con->physical.path->used == 0) return HANDLER_GO_ON;
919: if (con->uri.path->used == 0) return HANDLER_GO_ON;
920: if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
921:
922: mod_dirlisting_patch_connection(srv, con, p);
923:
924: if (!p->conf.dir_listing) return HANDLER_GO_ON;
925:
926: if (con->conf.log_request_handling) {
927: log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Dir-Listing");
928: log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path);
929: }
930:
931: if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
932: log_error_write(srv, __FILE__, __LINE__, "SB", "stat_cache_get_entry failed: ", con->physical.path);
933: SEGFAULT();
934: }
935:
936: if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
937:
938: if (http_list_directory(srv, con, p, con->physical.path)) {
939: /* dirlisting failed */
940: con->http_status = 403;
941: }
942:
943: buffer_reset(con->physical.path);
944:
945: /* not found */
946: return HANDLER_FINISHED;
947: }
948:
949: /* this function is called at dlopen() time and inits the callbacks */
950:
951: int mod_dirlisting_plugin_init(plugin *p);
952: int mod_dirlisting_plugin_init(plugin *p) {
953: p->version = LIGHTTPD_VERSION_ID;
954: p->name = buffer_init_string("dirlisting");
955:
956: p->init = mod_dirlisting_init;
957: p->handle_subrequest_start = mod_dirlisting_subrequest;
958: p->set_defaults = mod_dirlisting_set_defaults;
959: p->cleanup = mod_dirlisting_free;
960:
961: p->data = NULL;
962:
963: return 0;
964: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>