Annotation of embedaddon/lighttpd/src/mod_dirlisting.c, revision 1.1.1.3
1.1.1.3 ! misho 1: #include "first.h"
! 2:
1.1 misho 3: #include "base.h"
4: #include "log.h"
5: #include "buffer.h"
6:
7: #include "plugin.h"
8:
9: #include "response.h"
10: #include "stat_cache.h"
11: #include "stream.h"
12:
13: #include <ctype.h>
14: #include <stdlib.h>
15: #include <string.h>
16: #include <dirent.h>
17: #include <assert.h>
18: #include <errno.h>
1.1.1.3 ! misho 19: #include <fcntl.h>
1.1 misho 20: #include <stdio.h>
21: #include <unistd.h>
22: #include <time.h>
23:
24: /**
25: * this is a dirlisting for a lighttpd plugin
26: */
27:
28: #ifdef HAVE_ATTR_ATTRIBUTES_H
29: #include <attr/attributes.h>
30: #endif
31:
1.1.1.3 ! misho 32: #ifdef HAVE_SYS_EXTATTR_H
! 33: #include <sys/extattr.h>
! 34: #endif
1.1 misho 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();
1.1.1.3 ! misho 123: buffer_copy_buffer(exb->ptr[exb->used]->string, string);
1.1 misho 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: /* handle plugin config and check values */
198:
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_ENCODING "dir-listing.encoding"
204: #define CONFIG_SHOW_README "dir-listing.show-readme"
205: #define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
206: #define CONFIG_SHOW_HEADER "dir-listing.show-header"
207: #define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
208: #define CONFIG_DIR_LISTING "server.dir-listing"
209: #define CONFIG_SET_FOOTER "dir-listing.set-footer"
210: #define CONFIG_ENCODE_README "dir-listing.encode-readme"
211: #define CONFIG_ENCODE_HEADER "dir-listing.encode-header"
212: #define CONFIG_AUTO_LAYOUT "dir-listing.auto-layout"
213:
214:
215: SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
216: plugin_data *p = p_d;
217: size_t i = 0;
218:
219: config_values_t cv[] = {
220: { CONFIG_EXCLUDE, NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
221: { CONFIG_ACTIVATE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
222: { CONFIG_HIDE_DOTFILES, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
223: { CONFIG_EXTERNAL_CSS, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
224: { CONFIG_ENCODING, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
225: { CONFIG_SHOW_README, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
226: { CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
227: { CONFIG_SHOW_HEADER, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
228: { CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
229: { CONFIG_DIR_LISTING, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
230: { CONFIG_SET_FOOTER, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
231: { CONFIG_ENCODE_README, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
232: { CONFIG_ENCODE_HEADER, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
233: { CONFIG_AUTO_LAYOUT, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
234:
235: { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
236: };
237:
238: if (!p) return HANDLER_ERROR;
239:
1.1.1.2 misho 240: p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1 misho 241:
242: for (i = 0; i < srv->config_context->used; i++) {
1.1.1.3 ! misho 243: data_config const* config = (data_config const*)srv->config_context->data[i];
1.1 misho 244: plugin_config *s;
1.1.1.3 ! misho 245: data_unset *du_excludes;
1.1 misho 246:
247: s = calloc(1, sizeof(plugin_config));
248: s->excludes = excludes_buffer_init();
249: s->dir_listing = 0;
250: s->external_css = buffer_init();
1.1.1.3 ! misho 251: s->hide_dot_files = 1;
1.1 misho 252: s->show_readme = 0;
253: s->hide_readme_file = 0;
254: s->show_header = 0;
255: s->hide_header_file = 0;
256: s->encode_readme = 1;
257: s->encode_header = 1;
258: s->auto_layout = 1;
259:
260: s->encoding = buffer_init();
261: s->set_footer = buffer_init();
262:
263: cv[0].destination = s->excludes;
264: cv[1].destination = &(s->dir_listing);
265: cv[2].destination = &(s->hide_dot_files);
266: cv[3].destination = s->external_css;
267: cv[4].destination = s->encoding;
268: cv[5].destination = &(s->show_readme);
269: cv[6].destination = &(s->hide_readme_file);
270: cv[7].destination = &(s->show_header);
271: cv[8].destination = &(s->hide_header_file);
272: cv[9].destination = &(s->dir_listing); /* old name */
273: cv[10].destination = s->set_footer;
274: cv[11].destination = &(s->encode_readme);
275: cv[12].destination = &(s->encode_header);
276: cv[13].destination = &(s->auto_layout);
277:
278: p->config_storage[i] = s;
279:
1.1.1.3 ! misho 280: if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
1.1 misho 281: return HANDLER_ERROR;
282: }
283:
1.1.1.3 ! misho 284: if (NULL != (du_excludes = array_get_element(config->value, CONFIG_EXCLUDE))) {
! 285: array *excludes_list;
! 286: size_t j;
! 287:
! 288: if (du_excludes->type != TYPE_ARRAY) {
! 289: log_error_write(srv, __FILE__, __LINE__, "sss",
! 290: "unexpected type for key: ", CONFIG_EXCLUDE, "array of strings");
! 291: return HANDLER_ERROR;
! 292: }
! 293:
! 294: excludes_list = ((data_array*)du_excludes)->value;
! 295:
! 296: #ifndef HAVE_PCRE_H
! 297: if (excludes_list->used > 0) {
! 298: log_error_write(srv, __FILE__, __LINE__, "sss",
! 299: "pcre support is missing for: ", CONFIG_EXCLUDE, ", please install libpcre and the headers");
! 300: return HANDLER_ERROR;
! 301: }
! 302: #else
! 303: for (j = 0; j < excludes_list->used; j++) {
! 304: data_unset *du_exclude = excludes_list->data[j];
! 305:
! 306: if (du_exclude->type != TYPE_STRING) {
! 307: log_error_write(srv, __FILE__, __LINE__, "sssbs",
! 308: "unexpected type for key: ", CONFIG_EXCLUDE, "[",
! 309: du_exclude->key, "](string)");
! 310: return HANDLER_ERROR;
! 311: }
! 312:
! 313: if (0 != excludes_buffer_append(s->excludes, ((data_string*)(du_exclude))->value)) {
! 314: log_error_write(srv, __FILE__, __LINE__, "sb",
! 315: "pcre-compile failed for", ((data_string*)(du_exclude))->value);
! 316: return HANDLER_ERROR;
! 317: }
! 318: }
! 319: #endif
! 320: }
1.1 misho 321: }
322:
323: return HANDLER_GO_ON;
324: }
325:
326: #define PATCH(x) \
327: p->conf.x = s->x;
328: static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
329: size_t i, j;
330: plugin_config *s = p->config_storage[0];
331:
332: PATCH(dir_listing);
333: PATCH(external_css);
334: PATCH(hide_dot_files);
335: PATCH(encoding);
336: PATCH(show_readme);
337: PATCH(hide_readme_file);
338: PATCH(show_header);
339: PATCH(hide_header_file);
340: PATCH(excludes);
341: PATCH(set_footer);
342: PATCH(encode_readme);
343: PATCH(encode_header);
344: PATCH(auto_layout);
345:
346: /* skip the first, the global context */
347: for (i = 1; i < srv->config_context->used; i++) {
348: data_config *dc = (data_config *)srv->config_context->data[i];
349: s = p->config_storage[i];
350:
351: /* condition didn't match */
352: if (!config_check_cond(srv, con, dc)) continue;
353:
354: /* merge config */
355: for (j = 0; j < dc->value->used; j++) {
356: data_unset *du = dc->value->data[j];
357:
358: if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
359: buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
360: PATCH(dir_listing);
361: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
362: PATCH(hide_dot_files);
363: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
364: PATCH(external_css);
365: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
366: PATCH(encoding);
367: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
368: PATCH(show_readme);
369: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
370: PATCH(hide_readme_file);
371: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
372: PATCH(show_header);
373: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
374: PATCH(hide_header_file);
375: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
376: PATCH(set_footer);
377: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
378: PATCH(excludes);
379: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_README))) {
380: PATCH(encode_readme);
381: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_HEADER))) {
382: PATCH(encode_header);
383: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_AUTO_LAYOUT))) {
384: PATCH(auto_layout);
385: }
386: }
387: }
388:
389: return 0;
390: }
391: #undef PATCH
392:
393: typedef struct {
394: size_t namelen;
395: time_t mtime;
396: off_t size;
397: } dirls_entry_t;
398:
399: typedef struct {
400: dirls_entry_t **ent;
401: size_t used;
402: size_t size;
403: } dirls_list_t;
404:
405: #define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t))
406: #define DIRLIST_BLOB_SIZE 16
407:
408: /* simple combsort algorithm */
409: static void http_dirls_sort(dirls_entry_t **ent, int num) {
410: int gap = num;
411: int i, j;
412: int swapped;
413: dirls_entry_t *tmp;
414:
415: do {
416: gap = (gap * 10) / 13;
417: if (gap == 9 || gap == 10)
418: gap = 11;
419: if (gap < 1)
420: gap = 1;
421: swapped = 0;
422:
423: for (i = 0; i < num - gap; i++) {
424: j = i + gap;
425: if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
426: tmp = ent[i];
427: ent[i] = ent[j];
428: ent[j] = tmp;
429: swapped = 1;
430: }
431: }
432:
433: } while (gap > 1 || swapped);
434: }
435:
436: /* buffer must be able to hold "999.9K"
437: * conversion is simple but not perfect
438: */
1.1.1.3 ! misho 439: static int http_list_directory_sizefmt(char *buf, size_t bufsz, off_t size) {
! 440: const char unit[] = " KMGTPE"; /* Kilo, Mega, Giga, Tera, Peta, Exa */
! 441: const char *u = unit; /* u will always increment at least once */
1.1 misho 442: int remain;
1.1.1.3 ! misho 443: size_t buflen;
1.1 misho 444:
445: if (size < 100)
446: size += 99;
447: if (size < 100)
448: size = 0;
449:
450: while (1) {
451: remain = (int) size & 1023;
452: size >>= 10;
453: u++;
454: if ((size & (~0 ^ 1023)) == 0)
455: break;
456: }
457:
458: remain /= 100;
459: if (remain > 9)
460: remain = 9;
461: if (size > 999) {
462: size = 0;
463: remain = 9;
464: u++;
465: }
466:
1.1.1.3 ! misho 467: li_itostrn(buf, bufsz, size);
! 468: buflen = strlen(buf);
! 469: if (buflen + 3 >= bufsz) return buflen;
! 470: buf[buflen+0] = '.';
! 471: buf[buflen+1] = remain + '0';
! 472: buf[buflen+2] = *u;
! 473: buf[buflen+3] = '\0';
! 474:
! 475: return buflen + 3;
! 476: }
1.1 misho 477:
1.1.1.3 ! misho 478: /* don't want to block when open()ing a fifo */
! 479: #if defined(O_NONBLOCK)
! 480: # define FIFO_NONBLOCK O_NONBLOCK
! 481: #else
! 482: # define FIFO_NONBLOCK 0
! 483: #endif
! 484:
! 485: static void http_list_directory_include_file(buffer *out, buffer *path, const char *classname, int encode) {
! 486: int fd = open(path->ptr, O_RDONLY | FIFO_NONBLOCK);
! 487: ssize_t rd;
! 488: char buf[8192];
! 489:
! 490: if (-1 == fd) return;
! 491:
! 492: if (encode) {
! 493: buffer_append_string_len(out, CONST_STR_LEN("<pre class=\""));
! 494: buffer_append_string(out, classname);
! 495: buffer_append_string_len(out, CONST_STR_LEN("\">"));
! 496: }
! 497:
! 498: while ((rd = read(fd, buf, sizeof(buf))) > 0) {
! 499: if (encode) {
! 500: buffer_append_string_encoded(out, buf, (size_t)rd, ENCODING_MINIMAL_XML);
! 501: } else {
! 502: buffer_append_string_len(out, buf, (size_t)rd);
! 503: }
! 504: }
! 505: close(fd);
! 506:
! 507: if (encode) {
! 508: buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
! 509: }
1.1 misho 510: }
511:
512: static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
513: UNUSED(srv);
514:
515: if (p->conf.auto_layout) {
516: buffer_append_string_len(out, CONST_STR_LEN(
517: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
518: "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
519: "<head>\n"
520: "<title>Index of "
521: ));
522: buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
523: buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
524:
1.1.1.3 ! misho 525: if (!buffer_string_is_empty(p->conf.external_css)) {
1.1 misho 526: buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
527: buffer_append_string_buffer(out, p->conf.external_css);
528: buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
529: } else {
530: buffer_append_string_len(out, CONST_STR_LEN(
531: "<style type=\"text/css\">\n"
532: "a, a:active {text-decoration: none; color: blue;}\n"
533: "a:visited {color: #48468F;}\n"
534: "a:hover, a:focus {text-decoration: underline; color: red;}\n"
535: "body {background-color: #F5F5F5;}\n"
536: "h2 {margin-bottom: 12px;}\n"
537: "table {margin-left: 12px;}\n"
538: "th, td {"
539: " font: 90% monospace;"
540: " text-align: left;"
541: "}\n"
542: "th {"
543: " font-weight: bold;"
544: " padding-right: 14px;"
545: " padding-bottom: 3px;"
546: "}\n"
547: "td {padding-right: 14px;}\n"
548: "td.s, th.s {text-align: right;}\n"
549: "div.list {"
550: " background-color: white;"
551: " border-top: 1px solid #646464;"
552: " border-bottom: 1px solid #646464;"
553: " padding-top: 10px;"
554: " padding-bottom: 14px;"
555: "}\n"
556: "div.foot {"
557: " font: 90% monospace;"
558: " color: #787878;"
559: " padding-top: 4px;"
560: "}\n"
561: "</style>\n"
562: ));
563: }
564:
565: buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
566: }
567:
568: /* HEADER.txt */
569: if (p->conf.show_header) {
570: /* if we have a HEADER file, display it in <pre class="header"></pre> */
571:
1.1.1.3 ! misho 572: buffer_copy_buffer(p->tmp_buf, con->physical.path);
! 573: buffer_append_slash(p->tmp_buf);
1.1 misho 574: buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("HEADER.txt"));
575:
1.1.1.3 ! misho 576: http_list_directory_include_file(out, p->tmp_buf, "header", p->conf.encode_header);
1.1 misho 577: }
578:
579: buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
580: buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
581: buffer_append_string_len(out, CONST_STR_LEN(
582: "</h2>\n"
583: "<div class=\"list\">\n"
584: "<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
585: "<thead>"
586: "<tr>"
587: "<th class=\"n\">Name</th>"
588: "<th class=\"m\">Last Modified</th>"
589: "<th class=\"s\">Size</th>"
590: "<th class=\"t\">Type</th>"
591: "</tr>"
592: "</thead>\n"
593: "<tbody>\n"
1.1.1.3 ! misho 594: "<tr class=\"d\">"
1.1 misho 595: "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
596: "<td class=\"m\"> </td>"
597: "<td class=\"s\">- </td>"
598: "<td class=\"t\">Directory</td>"
599: "</tr>\n"
600: ));
601: }
602:
603: static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
604: UNUSED(srv);
605:
606: buffer_append_string_len(out, CONST_STR_LEN(
607: "</tbody>\n"
608: "</table>\n"
609: "</div>\n"
610: ));
611:
612: if (p->conf.show_readme) {
613: /* if we have a README file, display it in <pre class="readme"></pre> */
614:
1.1.1.3 ! misho 615: buffer_copy_buffer(p->tmp_buf, con->physical.path);
! 616: buffer_append_slash(p->tmp_buf);
1.1 misho 617: buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("README.txt"));
618:
1.1.1.3 ! misho 619: http_list_directory_include_file(out, p->tmp_buf, "readme", p->conf.encode_readme);
1.1 misho 620: }
621:
622: if(p->conf.auto_layout) {
623: buffer_append_string_len(out, CONST_STR_LEN(
624: "<div class=\"foot\">"
625: ));
626:
1.1.1.3 ! misho 627: if (!buffer_string_is_empty(p->conf.set_footer)) {
1.1 misho 628: buffer_append_string_buffer(out, p->conf.set_footer);
629: } else {
630: buffer_append_string_buffer(out, con->conf.server_tag);
631: }
632:
633: buffer_append_string_len(out, CONST_STR_LEN(
634: "</div>\n"
635: "</body>\n"
636: "</html>\n"
637: ));
638: }
639: }
640:
641: static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
642: DIR *dp;
643: buffer *out;
644: struct dirent *dent;
645: struct stat st;
646: char *path, *path_file;
647: size_t i;
648: int hide_dotfiles = p->conf.hide_dot_files;
649: dirls_list_t dirs, files, *list;
650: dirls_entry_t *tmp;
651: char sizebuf[sizeof("999.9K")];
652: char datebuf[sizeof("2005-Jan-01 22:23:24")];
653: size_t k;
654: const char *content_type;
655: long name_max;
1.1.1.3 ! misho 656: #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
1.1 misho 657: char attrval[128];
658: int attrlen;
659: #endif
660: #ifdef HAVE_LOCALTIME_R
661: struct tm tm;
662: #endif
663:
1.1.1.3 ! misho 664: if (buffer_string_is_empty(dir)) return -1;
1.1 misho 665:
1.1.1.3 ! misho 666: i = buffer_string_length(dir);
1.1 misho 667:
668: #ifdef HAVE_PATHCONF
669: if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
670: /* some broken fs (fuse) return 0 instead of -1 */
671: #ifdef NAME_MAX
672: name_max = NAME_MAX;
673: #else
674: name_max = 255; /* stupid default */
675: #endif
676: }
677: #elif defined __WIN32
678: name_max = FILENAME_MAX;
679: #else
680: name_max = NAME_MAX;
681: #endif
682:
1.1.1.3 ! misho 683: path = malloc(i + name_max + 1);
! 684: force_assert(NULL != path);
! 685: memcpy(path, dir->ptr, i+1);
1.1 misho 686: path_file = path + i;
687:
688: if (NULL == (dp = opendir(path))) {
689: log_error_write(srv, __FILE__, __LINE__, "sbs",
690: "opendir failed:", dir, strerror(errno));
691:
692: free(path);
693: return -1;
694: }
695:
696: dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
1.1.1.2 misho 697: force_assert(dirs.ent);
1.1 misho 698: dirs.size = DIRLIST_BLOB_SIZE;
699: dirs.used = 0;
700: files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
1.1.1.2 misho 701: force_assert(files.ent);
1.1 misho 702: files.size = DIRLIST_BLOB_SIZE;
703: files.used = 0;
704:
705: while ((dent = readdir(dp)) != NULL) {
706: unsigned short exclude_match = 0;
707:
708: if (dent->d_name[0] == '.') {
709: if (hide_dotfiles)
710: continue;
711: if (dent->d_name[1] == '\0')
712: continue;
713: if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
714: continue;
715: }
716:
717: if (p->conf.hide_readme_file) {
718: if (strcmp(dent->d_name, "README.txt") == 0)
719: continue;
720: }
721: if (p->conf.hide_header_file) {
722: if (strcmp(dent->d_name, "HEADER.txt") == 0)
723: continue;
724: }
725:
726: /* compare d_name against excludes array
727: * elements, skipping any that match.
728: */
729: #ifdef HAVE_PCRE_H
730: for(i = 0; i < p->conf.excludes->used; i++) {
731: int n;
732: #define N 10
733: int ovec[N * 3];
734: pcre *regex = p->conf.excludes->ptr[i]->regex;
735:
736: if ((n = pcre_exec(regex, NULL, dent->d_name,
737: strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
738: if (n != PCRE_ERROR_NOMATCH) {
739: log_error_write(srv, __FILE__, __LINE__, "sd",
740: "execution error while matching:", n);
741:
1.1.1.2 misho 742: /* aborting would require a lot of manual cleanup here.
743: * skip instead (to not leak names that break pcre matching)
744: */
745: exclude_match = 1;
746: break;
1.1 misho 747: }
748: }
749: else {
750: exclude_match = 1;
751: break;
752: }
753: }
754:
755: if (exclude_match) {
756: continue;
757: }
758: #endif
759:
760: i = strlen(dent->d_name);
761:
762: /* NOTE: the manual says, d_name is never more than NAME_MAX
763: * so this should actually not be a buffer-overflow-risk
764: */
765: if (i > (size_t)name_max) continue;
766:
767: memcpy(path_file, dent->d_name, i + 1);
768: if (stat(path, &st) != 0)
769: continue;
770:
771: list = &files;
772: if (S_ISDIR(st.st_mode))
773: list = &dirs;
774:
775: if (list->used == list->size) {
776: list->size += DIRLIST_BLOB_SIZE;
777: list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
1.1.1.2 misho 778: force_assert(list->ent);
1.1 misho 779: }
780:
781: tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
782: tmp->mtime = st.st_mtime;
783: tmp->size = st.st_size;
784: tmp->namelen = i;
785: memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
786:
787: list->ent[list->used++] = tmp;
788: }
789: closedir(dp);
790:
791: if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
792:
793: if (files.used) http_dirls_sort(files.ent, files.used);
794:
1.1.1.3 ! misho 795: out = buffer_init();
1.1 misho 796: buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
1.1.1.3 ! misho 797: if (buffer_string_is_empty(p->conf.encoding)) {
1.1 misho 798: buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
799: } else {
800: buffer_append_string_buffer(out, p->conf.encoding);
801: }
802: buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
803: http_list_directory_header(srv, con, p, out);
804:
805: /* directories */
806: for (i = 0; i < dirs.used; i++) {
807: tmp = dirs.ent[i];
808:
809: #ifdef HAVE_LOCALTIME_R
810: localtime_r(&(tmp->mtime), &tm);
811: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
812: #else
813: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
814: #endif
815:
1.1.1.3 ! misho 816: buffer_append_string_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\""));
1.1 misho 817: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
818: buffer_append_string_len(out, CONST_STR_LEN("/\">"));
819: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
820: buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
821: buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
822: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"));
823:
824: free(tmp);
825: }
826:
827: /* files */
828: for (i = 0; i < files.used; i++) {
829: tmp = files.ent[i];
830:
831: content_type = NULL;
1.1.1.3 ! misho 832: #if defined(HAVE_XATTR)
1.1 misho 833: if (con->conf.use_xattr) {
834: memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
835: attrlen = sizeof(attrval) - 1;
1.1.1.3 ! misho 836: if (attr_get(path, srv->srvconf.xattr_name->ptr, attrval, &attrlen, 0) == 0) {
! 837: attrval[attrlen] = '\0';
! 838: content_type = attrval;
! 839: }
! 840: }
! 841: #elif defined(HAVE_EXTATTR)
! 842: if (con->conf.use_xattr) {
! 843: memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
! 844: if(-1 != (attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, srv->srvconf.xattr_name->ptr, attrval, sizeof(attrval)-1))) {
1.1 misho 845: attrval[attrlen] = '\0';
846: content_type = attrval;
847: }
848: }
849: #endif
850:
851: if (content_type == NULL) {
852: content_type = "application/octet-stream";
853: for (k = 0; k < con->conf.mimetypes->used; k++) {
854: data_string *ds = (data_string *)con->conf.mimetypes->data[k];
855: size_t ct_len;
856:
1.1.1.3 ! misho 857: if (buffer_is_empty(ds->key))
1.1 misho 858: continue;
859:
1.1.1.3 ! misho 860: ct_len = buffer_string_length(ds->key);
1.1 misho 861: if (tmp->namelen < ct_len)
862: continue;
863:
864: if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
865: content_type = ds->value->ptr;
866: break;
867: }
868: }
869: }
870:
871: #ifdef HAVE_LOCALTIME_R
872: localtime_r(&(tmp->mtime), &tm);
873: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
874: #else
875: strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
876: #endif
1.1.1.3 ! misho 877: http_list_directory_sizefmt(sizebuf, sizeof(sizebuf), tmp->size);
1.1 misho 878:
879: buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
880: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
881: buffer_append_string_len(out, CONST_STR_LEN("\">"));
882: buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
883: buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
884: buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
885: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
886: buffer_append_string(out, sizebuf);
887: buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
888: buffer_append_string(out, content_type);
889: buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
890:
891: free(tmp);
892: }
893:
894: free(files.ent);
895: free(dirs.ent);
896: free(path);
897:
898: http_list_directory_footer(srv, con, p, out);
899:
900: /* Insert possible charset to Content-Type */
1.1.1.3 ! misho 901: if (buffer_string_is_empty(p->conf.encoding)) {
1.1 misho 902: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
903: } else {
904: buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
905: buffer_append_string_buffer(p->content_charset, p->conf.encoding);
906: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
907: }
908:
909: con->file_finished = 1;
1.1.1.3 ! misho 910: chunkqueue_append_buffer(con->write_queue, out);
! 911: buffer_free(out);
1.1 misho 912:
913: return 0;
914: }
915:
916:
917:
918: URIHANDLER_FUNC(mod_dirlisting_subrequest) {
919: plugin_data *p = p_d;
920: stat_cache_entry *sce = NULL;
921:
922: UNUSED(srv);
923:
924: /* we only handle GET, POST and HEAD */
925: switch(con->request.http_method) {
926: case HTTP_METHOD_GET:
927: case HTTP_METHOD_POST:
928: case HTTP_METHOD_HEAD:
929: break;
930: default:
931: return HANDLER_GO_ON;
932: }
933:
934: if (con->mode != DIRECT) return HANDLER_GO_ON;
935:
1.1.1.3 ! misho 936: if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
! 937: if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON;
! 938: if (con->uri.path->ptr[buffer_string_length(con->uri.path) - 1] != '/') return HANDLER_GO_ON;
1.1 misho 939:
940: mod_dirlisting_patch_connection(srv, con, p);
941:
942: if (!p->conf.dir_listing) return HANDLER_GO_ON;
943:
944: if (con->conf.log_request_handling) {
945: log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Dir-Listing");
946: log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path);
947: }
948:
949: if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
950: log_error_write(srv, __FILE__, __LINE__, "SB", "stat_cache_get_entry failed: ", con->physical.path);
951: SEGFAULT();
952: }
953:
954: if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
955:
956: if (http_list_directory(srv, con, p, con->physical.path)) {
957: /* dirlisting failed */
958: con->http_status = 403;
959: }
960:
961: buffer_reset(con->physical.path);
962:
963: /* not found */
964: return HANDLER_FINISHED;
965: }
966:
967: /* this function is called at dlopen() time and inits the callbacks */
968:
969: int mod_dirlisting_plugin_init(plugin *p);
970: int mod_dirlisting_plugin_init(plugin *p) {
971: p->version = LIGHTTPD_VERSION_ID;
972: p->name = buffer_init_string("dirlisting");
973:
974: p->init = mod_dirlisting_init;
975: p->handle_subrequest_start = mod_dirlisting_subrequest;
976: p->set_defaults = mod_dirlisting_set_defaults;
977: p->cleanup = mod_dirlisting_free;
978:
979: p->data = NULL;
980:
981: return 0;
982: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>