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