Project

General

Profile

mod_dirlisting.c

molyland, 2011-05-12 11:18

 
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 *external_js;
67
        buffer *encoding;
68
        buffer *set_footer;
69
} plugin_config;
70

    
71
typedef struct {
72
        PLUGIN_DATA;
73

    
74
        buffer *tmp_buf;
75
        buffer *content_charset;
76

    
77
        plugin_config **config_storage;
78

    
79
        plugin_config conf;
80
} plugin_data;
81

    
82
static excludes_buffer *excludes_buffer_init(void) {
83
        excludes_buffer *exb;
84

    
85
        exb = calloc(1, sizeof(*exb));
86

    
87
        return exb;
88
}
89

    
90
static int excludes_buffer_append(excludes_buffer *exb, buffer *string) {
91
#ifdef HAVE_PCRE_H
92
        size_t i;
93
        const char *errptr;
94
        int erroff;
95

    
96
        if (!string) return -1;
97

    
98
        if (exb->size == 0) {
99
                exb->size = 4;
100
                exb->used = 0;
101

    
102
                exb->ptr = malloc(exb->size * sizeof(*exb->ptr));
103

    
104
                for(i = 0; i < exb->size ; i++) {
105
                        exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
106
                }
107
        } else if (exb->used == exb->size) {
108
                exb->size += 4;
109

    
110
                exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr));
111

    
112
                for(i = exb->used; i < exb->size; i++) {
113
                        exb->ptr[i] = calloc(1, sizeof(**exb->ptr));
114
                }
115
        }
116

    
117

    
118
        if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0,
119
                                                    &errptr, &erroff, NULL))) {
120
                return -1;
121
        }
122

    
123
        exb->ptr[exb->used]->string = buffer_init();
124
        buffer_copy_string_buffer(exb->ptr[exb->used]->string, string);
125

    
126
        exb->used++;
127

    
128
        return 0;
129
#else
130
        UNUSED(exb);
131
        UNUSED(string);
132

    
133
        return -1;
134
#endif
135
}
136

    
137
static void excludes_buffer_free(excludes_buffer *exb) {
138
#ifdef HAVE_PCRE_H
139
        size_t i;
140

    
141
        for (i = 0; i < exb->size; i++) {
142
                if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex);
143
                if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string);
144
                free(exb->ptr[i]);
145
        }
146

    
147
        if (exb->ptr) free(exb->ptr);
148
#endif
149

    
150
        free(exb);
151
}
152

    
153
/* init the plugin data */
154
INIT_FUNC(mod_dirlisting_init) {
155
        plugin_data *p;
156

    
157
        p = calloc(1, sizeof(*p));
158

    
159
        p->tmp_buf = buffer_init();
160
        p->content_charset = buffer_init();
161

    
162
        return p;
163
}
164

    
165
/* detroy the plugin data */
166
FREE_FUNC(mod_dirlisting_free) {
167
        plugin_data *p = p_d;
168

    
169
        UNUSED(srv);
170

    
171
        if (!p) return HANDLER_GO_ON;
172

    
173
        if (p->config_storage) {
174
                size_t i;
175
                for (i = 0; i < srv->config_context->used; i++) {
176
                        plugin_config *s = p->config_storage[i];
177

    
178
                        if (!s) continue;
179

    
180
                        excludes_buffer_free(s->excludes);
181
                        buffer_free(s->external_css);
182
      buffer_free(s->external_js);
183
                        buffer_free(s->encoding);
184
                        buffer_free(s->set_footer);
185

    
186
                        free(s);
187
                }
188
                free(p->config_storage);
189
        }
190

    
191
        buffer_free(p->tmp_buf);
192
        buffer_free(p->content_charset);
193

    
194
        free(p);
195

    
196
        return HANDLER_GO_ON;
197
}
198

    
199
static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) {
200
        data_unset *du;
201

    
202
        if (NULL != (du = array_get_element(ca, option))) {
203
                data_array *da;
204
                size_t j;
205

    
206
                if (du->type != TYPE_ARRAY) {
207
                        log_error_write(srv, __FILE__, __LINE__, "sss",
208
                                "unexpected type for key: ", option, "array of strings");
209

    
210
                        return HANDLER_ERROR;
211
                }
212

    
213
                da = (data_array *)du;
214

    
215
                for (j = 0; j < da->value->used; j++) {
216
                        if (da->value->data[j]->type != TYPE_STRING) {
217
                                log_error_write(srv, __FILE__, __LINE__, "sssbs",
218
                                        "unexpected type for key: ", option, "[",
219
                                        da->value->data[j]->key, "](string)");
220

    
221
                                return HANDLER_ERROR;
222
                        }
223

    
224
                        if (0 != excludes_buffer_append(s->excludes,
225
                                    ((data_string *)(da->value->data[j]))->value)) {
226
#ifdef HAVE_PCRE_H
227
                                log_error_write(srv, __FILE__, __LINE__, "sb",
228
                                                "pcre-compile failed for", ((data_string *)(da->value->data[j]))->value);
229
#else
230
                                log_error_write(srv, __FILE__, __LINE__, "s",
231
                                                "pcre support is missing, please install libpcre and the headers");
232
#endif
233
                        }
234
                }
235
        }
236

    
237
        return 0;
238
}
239

    
240
/* handle plugin config and check values */
241

    
242
#define CONFIG_EXCLUDE          "dir-listing.exclude"
243
#define CONFIG_ACTIVATE         "dir-listing.activate"
244
#define CONFIG_HIDE_DOTFILES    "dir-listing.hide-dotfiles"
245
#define CONFIG_EXTERNAL_CSS     "dir-listing.external-css"
246
#define CONFIG_EXTERNAL_JS      "dir-listing.external-js"
247
#define CONFIG_ENCODING         "dir-listing.encoding"
248
#define CONFIG_SHOW_README      "dir-listing.show-readme"
249
#define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
250
#define CONFIG_SHOW_HEADER      "dir-listing.show-header"
251
#define CONFIG_HIDE_HEADER_FILE "dir-listing.hide-header-file"
252
#define CONFIG_DIR_LISTING      "server.dir-listing"
253
#define CONFIG_SET_FOOTER       "dir-listing.set-footer"
254
#define CONFIG_ENCODE_README    "dir-listing.encode-readme"
255
#define CONFIG_ENCODE_HEADER    "dir-listing.encode-header"
256
#define CONFIG_AUTO_LAYOUT      "dir-listing.auto-layout"
257

    
258

    
259
SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
260
        plugin_data *p = p_d;
261
        size_t i = 0;
262

    
263
        config_values_t cv[] = {
264
                { CONFIG_EXCLUDE,          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },   /* 0 */
265
                { CONFIG_ACTIVATE,         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
266
                { CONFIG_HIDE_DOTFILES,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
267
                { CONFIG_EXTERNAL_CSS,     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 3 */
268
    { CONFIG_EXTERNAL_JS,      NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 4 */
269
                { CONFIG_ENCODING,         NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 5 */
270
                { CONFIG_SHOW_README,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
271
                { CONFIG_HIDE_README_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
272
                { CONFIG_SHOW_HEADER,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
273
                { CONFIG_HIDE_HEADER_FILE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
274
                { CONFIG_DIR_LISTING,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
275
                { CONFIG_SET_FOOTER,       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },  /* 11 */
276
                { CONFIG_ENCODE_README,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
277
                { CONFIG_ENCODE_HEADER,    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
278
                { CONFIG_AUTO_LAYOUT,      NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
279

    
280
                { NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
281
        };
282

    
283
        if (!p) return HANDLER_ERROR;
284

    
285
        p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
286

    
287
        for (i = 0; i < srv->config_context->used; i++) {
288
                plugin_config *s;
289
                array *ca;
290

    
291
                s = calloc(1, sizeof(plugin_config));
292
                s->excludes = excludes_buffer_init();
293
                s->dir_listing = 0;
294
                s->external_css = buffer_init();
295
                s->external_js = buffer_init();
296
                s->hide_dot_files = 0;
297
                s->show_readme = 0;
298
                s->hide_readme_file = 0;
299
                s->show_header = 0;
300
                s->hide_header_file = 0;
301
                s->encode_readme = 1;
302
                s->encode_header = 1;
303
                s->auto_layout = 1;
304

    
305
                s->encoding = buffer_init();
306
                s->set_footer = buffer_init();
307

    
308
                cv[0].destination = s->excludes;
309
                cv[1].destination = &(s->dir_listing);
310
                cv[2].destination = &(s->hide_dot_files);
311
                cv[3].destination = s->external_css;
312
                cv[4].destination = s->external_js;
313
                cv[5].destination = s->encoding;
314
                cv[6].destination = &(s->show_readme);
315
                cv[7].destination = &(s->hide_readme_file);
316
                cv[8].destination = &(s->show_header);
317
                cv[9].destination = &(s->hide_header_file);
318
                cv[10].destination = &(s->dir_listing); /* old name */
319
                cv[11].destination = s->set_footer;
320
                cv[12].destination = &(s->encode_readme);
321
                cv[13].destination = &(s->encode_header);
322
                cv[14].destination = &(s->auto_layout);
323

    
324
                p->config_storage[i] = s;
325
                ca = ((data_config *)srv->config_context->data[i])->value;
326

    
327
                if (0 != config_insert_values_global(srv, ca, cv)) {
328
                        return HANDLER_ERROR;
329
                }
330

    
331
                parse_config_entry(srv, s, ca, CONFIG_EXCLUDE);
332
        }
333

    
334
        return HANDLER_GO_ON;
335
}
336

    
337
#define PATCH(x) \
338
        p->conf.x = s->x;
339
static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) {
340
        size_t i, j;
341
        plugin_config *s = p->config_storage[0];
342

    
343
        PATCH(dir_listing);
344
        PATCH(external_css);
345
        PATCH(external_js);
346
        PATCH(hide_dot_files);
347
        PATCH(encoding);
348
        PATCH(show_readme);
349
        PATCH(hide_readme_file);
350
        PATCH(show_header);
351
        PATCH(hide_header_file);
352
        PATCH(excludes);
353
        PATCH(set_footer);
354
        PATCH(encode_readme);
355
        PATCH(encode_header);
356
        PATCH(auto_layout);
357

    
358
        /* skip the first, the global context */
359
        for (i = 1; i < srv->config_context->used; i++) {
360
                data_config *dc = (data_config *)srv->config_context->data[i];
361
                s = p->config_storage[i];
362

    
363
                /* condition didn't match */
364
                if (!config_check_cond(srv, con, dc)) continue;
365

    
366
                /* merge config */
367
                for (j = 0; j < dc->value->used; j++) {
368
                        data_unset *du = dc->value->data[j];
369

    
370
                        if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ACTIVATE)) ||
371
                            buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_DIR_LISTING))) {
372
                                PATCH(dir_listing);
373
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_DOTFILES))) {
374
                                PATCH(hide_dot_files);
375
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
376
                                PATCH(external_css);
377
      } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_JS))) {
378
        PATCH(external_js);
379
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
380
                                PATCH(encoding);
381
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
382
                                PATCH(show_readme);
383
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_README_FILE))) {
384
                                PATCH(hide_readme_file);
385
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_HEADER))) {
386
                                PATCH(show_header);
387
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_HIDE_HEADER_FILE))) {
388
                                PATCH(hide_header_file);
389
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SET_FOOTER))) {
390
                                PATCH(set_footer);
391
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXCLUDE))) {
392
                                PATCH(excludes);
393
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_README))) {
394
                                PATCH(encode_readme);
395
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODE_HEADER))) {
396
                                PATCH(encode_header);
397
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_AUTO_LAYOUT))) {
398
                                PATCH(auto_layout);
399
                        }
400
                }
401
        }
402

    
403
        return 0;
404
}
405
#undef PATCH
406

    
407
typedef struct {
408
        size_t  namelen;
409
        time_t  mtime;
410
        off_t   size;
411
} dirls_entry_t;
412

    
413
typedef struct {
414
        dirls_entry_t **ent;
415
        size_t used;
416
        size_t size;
417
} dirls_list_t;
418

    
419
#define DIRLIST_ENT_NAME(ent)        ((char*)(ent) + sizeof(dirls_entry_t))
420
#define DIRLIST_BLOB_SIZE                16
421

    
422
/* simple combsort algorithm */
423
static void http_dirls_sort(dirls_entry_t **ent, int num) {
424
        int gap = num;
425
        int i, j;
426
        int swapped;
427
        dirls_entry_t *tmp;
428

    
429
        do {
430
                gap = (gap * 10) / 13;
431
                if (gap == 9 || gap == 10)
432
                        gap = 11;
433
                if (gap < 1)
434
                        gap = 1;
435
                swapped = 0;
436

    
437
                for (i = 0; i < num - gap; i++) {
438
                        j = i + gap;
439
                        if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) {
440
                                tmp = ent[i];
441
                                ent[i] = ent[j];
442
                                ent[j] = tmp;
443
                                swapped = 1;
444
                        }
445
                }
446

    
447
        } while (gap > 1 || swapped);
448
}
449

    
450
/* buffer must be able to hold "999.9K"
451
 * conversion is simple but not perfect
452
 */
453
static int http_list_directory_sizefmt(char *buf, off_t size) {
454
        const char unit[] = "KMGTPE";        /* Kilo, Mega, Tera, Peta, Exa */
455
        const char *u = unit - 1;                /* u will always increment at least once */
456
        int remain;
457
        char *out = buf;
458

    
459
        if (size < 100)
460
                size += 99;
461
        if (size < 100)
462
                size = 0;
463

    
464
        while (1) {
465
                remain = (int) size & 1023;
466
                size >>= 10;
467
                u++;
468
                if ((size & (~0 ^ 1023)) == 0)
469
                        break;
470
        }
471

    
472
        remain /= 100;
473
        if (remain > 9)
474
                remain = 9;
475
        if (size > 999) {
476
                size   = 0;
477
                remain = 9;
478
                u++;
479
        }
480

    
481
        out   += LI_ltostr(out, size);
482
        out[0] = '.';
483
        out[1] = remain + '0';
484
        out[2] = *u;
485
        out[3] = '\0';
486

    
487
        return (out + 3 - buf);
488
}
489

    
490
static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
491
        UNUSED(srv);
492

    
493
        if (p->conf.auto_layout) {
494
                buffer_append_string_len(out, CONST_STR_LEN(
495
                        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
496
                        "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"
497
                        "<head>\n"
498
                        "<title>Index of "
499
                ));
500
                buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
501
                buffer_append_string_len(out, CONST_STR_LEN("</title>\n"));
502

    
503
    if (p->conf.external_js->used > 1) {
504
      buffer_append_string_len(out, CONST_STR_LEN("<script type=\"text/javascript\" src=\""));
505
      buffer_append_string_buffer(out, p->conf.external_js);
506
      buffer_append_string_len(out, CONST_STR_LEN("\" ></script>\n"));
507
    }
508

    
509
                if (p->conf.external_css->used > 1) {
510
                        buffer_append_string_len(out, CONST_STR_LEN("<link rel=\"stylesheet\" type=\"text/css\" href=\""));
511
                        buffer_append_string_buffer(out, p->conf.external_css);
512
                        buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
513
                } else {
514
                        buffer_append_string_len(out, CONST_STR_LEN(
515
                                "<style type=\"text/css\">\n"
516
                                "a, a:active {text-decoration: none; color: blue;}\n"
517
                                "a:visited {color: #48468F;}\n"
518
                                "a:hover, a:focus {text-decoration: underline; color: red;}\n"
519
                                "body {background-color: #F5F5F5;}\n"
520
                                "h2 {margin-bottom: 12px;}\n"
521
                                "table {margin-left: 12px;}\n"
522
                                "th, td {"
523
                                " font: 90% monospace;"
524
                                " text-align: left;"
525
                                "}\n"
526
                                "th {"
527
                                " font-weight: bold;"
528
                                " padding-right: 14px;"
529
                                " padding-bottom: 3px;"
530
                                "}\n"
531
                                "td {padding-right: 14px;}\n"
532
                                "td.s, th.s {text-align: right;}\n"
533
                                "div.list {"
534
                                " background-color: white;"
535
                                " border-top: 1px solid #646464;"
536
                                " border-bottom: 1px solid #646464;"
537
                                " padding-top: 10px;"
538
                                " padding-bottom: 14px;"
539
                                "}\n"
540
                                "div.foot {"
541
                                " font: 90% monospace;"
542
                                " color: #787878;"
543
                                " padding-top: 4px;"
544
                                "}\n"
545
                                "</style>\n"
546
                        ));
547
                }
548

    
549
                buffer_append_string_len(out, CONST_STR_LEN("</head>\n<body>\n"));
550
        }
551

    
552
        /* HEADER.txt */
553
        if (p->conf.show_header) {
554
                stream s;
555
                /* if we have a HEADER file, display it in <pre class="header"></pre> */
556

    
557
                buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
558
                BUFFER_APPEND_SLASH(p->tmp_buf);
559
                buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("HEADER.txt"));
560

    
561
                if (-1 != stream_open(&s, p->tmp_buf)) {
562
                        if (p->conf.encode_header) {
563
                                buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"header\">"));
564
                                buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
565
                                buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
566
                        } else {
567
                                buffer_append_string_len(out, s.start, s.size);
568
                        }
569
                }
570
                stream_close(&s);
571
        }
572

    
573
        buffer_append_string_len(out, CONST_STR_LEN("<h2>Index of "));
574
        buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML);
575
        buffer_append_string_len(out, CONST_STR_LEN(
576
                "</h2>\n"
577
                "<div class=\"list\">\n"
578
                "<table summary=\"Directory Listing\" cellpadding=\"0\" cellspacing=\"0\">\n"
579
                "<thead>"
580
                "<tr>"
581
                        "<th class=\"n\">Name</th>"
582
                        "<th class=\"m\">Last Modified</th>"
583
                        "<th class=\"s\">Size</th>"
584
                        "<th class=\"t\">Type</th>"
585
                "</tr>"
586
                "</thead>\n"
587
                "<tbody>\n"
588
                "<tr>"
589
                        "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
590
                        "<td class=\"m\">&nbsp;</td>"
591
                        "<td class=\"s\">- &nbsp;</td>"
592
                        "<td class=\"t\">Directory</td>"
593
                "</tr>\n"
594
        ));
595
}
596

    
597
static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) {
598
        UNUSED(srv);
599

    
600
        buffer_append_string_len(out, CONST_STR_LEN(
601
                "</tbody>\n"
602
                "</table>\n"
603
                "</div>\n"
604
        ));
605

    
606
        if (p->conf.show_readme) {
607
                stream s;
608
                /* if we have a README file, display it in <pre class="readme"></pre> */
609

    
610
                buffer_copy_string_buffer(p->tmp_buf,  con->physical.path);
611
                BUFFER_APPEND_SLASH(p->tmp_buf);
612
                buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("README.txt"));
613

    
614
                if (-1 != stream_open(&s, p->tmp_buf)) {
615
                        if (p->conf.encode_readme) {
616
                                buffer_append_string_len(out, CONST_STR_LEN("<pre class=\"readme\">"));
617
                                buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML);
618
                                buffer_append_string_len(out, CONST_STR_LEN("</pre>"));
619
                        } else {
620
                                buffer_append_string_len(out, s.start, s.size);
621
                        }
622
                }
623
                stream_close(&s);
624
        }
625

    
626
        if(p->conf.auto_layout) {
627
                buffer_append_string_len(out, CONST_STR_LEN(
628
                        "<div class=\"foot\">"
629
                ));
630

    
631
                if (p->conf.set_footer->used > 1) {
632
                        buffer_append_string_buffer(out, p->conf.set_footer);
633
                } else if (buffer_is_empty(con->conf.server_tag)) {
634
                        buffer_append_string_len(out, CONST_STR_LEN(PACKAGE_DESC));
635
                } else {
636
                        buffer_append_string_buffer(out, con->conf.server_tag);
637
                }
638

    
639
                buffer_append_string_len(out, CONST_STR_LEN(
640
                        "</div>\n"
641
                        "</body>\n"
642
                        "</html>\n"
643
                ));
644
        }
645
}
646

    
647
static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) {
648
        DIR *dp;
649
        buffer *out;
650
        struct dirent *dent;
651
        struct stat st;
652
        char *path, *path_file;
653
        size_t i;
654
        int hide_dotfiles = p->conf.hide_dot_files;
655
        dirls_list_t dirs, files, *list;
656
        dirls_entry_t *tmp;
657
        char sizebuf[sizeof("999.9K")];
658
        char datebuf[sizeof("2005-Jan-01 22:23:24")];
659
        size_t k;
660
        const char *content_type;
661
        long name_max;
662
#ifdef HAVE_XATTR
663
        char attrval[128];
664
        int attrlen;
665
#endif
666
#ifdef HAVE_LOCALTIME_R
667
        struct tm tm;
668
#endif
669

    
670
        if (dir->used == 0) return -1;
671

    
672
        i = dir->used - 1;
673

    
674
#ifdef HAVE_PATHCONF
675
        if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) {
676
#ifdef NAME_MAX
677
                name_max = NAME_MAX;
678
#else
679
                name_max = 255; /* stupid default */
680
#endif
681
        }
682
#elif defined __WIN32
683
        name_max = FILENAME_MAX;
684
#else
685
        name_max = NAME_MAX;
686
#endif
687

    
688
        path = malloc(dir->used + name_max);
689
        assert(path);
690
        strcpy(path, dir->ptr);
691
        path_file = path + i;
692

    
693
        if (NULL == (dp = opendir(path))) {
694
                log_error_write(srv, __FILE__, __LINE__, "sbs",
695
                        "opendir failed:", dir, strerror(errno));
696

    
697
                free(path);
698
                return -1;
699
        }
700

    
701
        dirs.ent   = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
702
        assert(dirs.ent);
703
        dirs.size  = DIRLIST_BLOB_SIZE;
704
        dirs.used  = 0;
705
        files.ent  = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE);
706
        assert(files.ent);
707
        files.size = DIRLIST_BLOB_SIZE;
708
        files.used = 0;
709

    
710
        while ((dent = readdir(dp)) != NULL) {
711
                unsigned short exclude_match = 0;
712

    
713
                if (dent->d_name[0] == '.') {
714
                        if (hide_dotfiles)
715
                                continue;
716
                        if (dent->d_name[1] == '\0')
717
                                continue;
718
                        if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
719
                                continue;
720
                }
721

    
722
                if (p->conf.hide_readme_file) {
723
                        if (strcmp(dent->d_name, "README.txt") == 0)
724
                                continue;
725
                }
726
                if (p->conf.hide_header_file) {
727
                        if (strcmp(dent->d_name, "HEADER.txt") == 0)
728
                                continue;
729
                }
730

    
731
                /* compare d_name against excludes array
732
                 * elements, skipping any that match.
733
                 */
734
#ifdef HAVE_PCRE_H
735
                for(i = 0; i < p->conf.excludes->used; i++) {
736
                        int n;
737
#define N 10
738
                        int ovec[N * 3];
739
                        pcre *regex = p->conf.excludes->ptr[i]->regex;
740

    
741
                        if ((n = pcre_exec(regex, NULL, dent->d_name,
742
                                    strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) {
743
                                if (n != PCRE_ERROR_NOMATCH) {
744
                                        log_error_write(srv, __FILE__, __LINE__, "sd",
745
                                                "execution error while matching:", n);
746

    
747
                                        return -1;
748
                                }
749
                        }
750
                        else {
751
                                exclude_match = 1;
752
                                break;
753
                        }
754
                }
755

    
756
                if (exclude_match) {
757
                        continue;
758
                }
759
#endif
760

    
761
                i = strlen(dent->d_name);
762

    
763
                /* NOTE: the manual says, d_name is never more than NAME_MAX
764
                 *       so this should actually not be a buffer-overflow-risk
765
                 */
766
                if (i > (size_t)name_max) continue;
767

    
768
                memcpy(path_file, dent->d_name, i + 1);
769
                if (stat(path, &st) != 0)
770
                        continue;
771

    
772
                list = &files;
773
                if (S_ISDIR(st.st_mode))
774
                        list = &dirs;
775

    
776
                if (list->used == list->size) {
777
                        list->size += DIRLIST_BLOB_SIZE;
778
                        list->ent   = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size);
779
                        assert(list->ent);
780
                }
781

    
782
                tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i);
783
                tmp->mtime = st.st_mtime;
784
                tmp->size  = st.st_size;
785
                tmp->namelen = i;
786
                memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1);
787

    
788
                list->ent[list->used++] = tmp;
789
        }
790
        closedir(dp);
791

    
792
        if (dirs.used) http_dirls_sort(dirs.ent, dirs.used);
793

    
794
        if (files.used) http_dirls_sort(files.ent, files.used);
795

    
796
        out = chunkqueue_get_append_buffer(con->write_queue);
797
        buffer_copy_string_len(out, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\""));
798
        if (buffer_is_empty(p->conf.encoding)) {
799
                buffer_append_string_len(out, CONST_STR_LEN("iso-8859-1"));
800
        } else {
801
                buffer_append_string_buffer(out, p->conf.encoding);
802
        }
803
        buffer_append_string_len(out, CONST_STR_LEN("\"?>\n"));
804
        http_list_directory_header(srv, con, p, out);
805

    
806
        /* directories */
807
        for (i = 0; i < dirs.used; i++) {
808
                tmp = dirs.ent[i];
809

    
810
#ifdef HAVE_LOCALTIME_R
811
                localtime_r(&(tmp->mtime), &tm);
812
                strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
813
#else
814
                strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
815
#endif
816

    
817
                buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
818
                buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
819
                buffer_append_string_len(out, CONST_STR_LEN("/\">"));
820
                buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
821
                buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">"));
822
                buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
823
                buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- &nbsp;</td><td class=\"t\">Directory</td></tr>\n"));
824

    
825
                free(tmp);
826
        }
827

    
828
        /* files */
829
        for (i = 0; i < files.used; i++) {
830
                tmp = files.ent[i];
831

    
832
                content_type = NULL;
833
#ifdef HAVE_XATTR
834

    
835
                if (con->conf.use_xattr) {
836
                        memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1);
837
                        attrlen = sizeof(attrval) - 1;
838
                        if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) {
839
                                attrval[attrlen] = '\0';
840
                                content_type = attrval;
841
                        }
842
                }
843
#endif
844

    
845
                if (content_type == NULL) {
846
                        content_type = "application/octet-stream";
847
                        for (k = 0; k < con->conf.mimetypes->used; k++) {
848
                                data_string *ds = (data_string *)con->conf.mimetypes->data[k];
849
                                size_t ct_len;
850

    
851
                                if (ds->key->used == 0)
852
                                        continue;
853

    
854
                                ct_len = ds->key->used - 1;
855
                                if (tmp->namelen < ct_len)
856
                                        continue;
857

    
858
                                if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) {
859
                                        content_type = ds->value->ptr;
860
                                        break;
861
                                }
862
                        }
863
                }
864

    
865
#ifdef HAVE_LOCALTIME_R
866
                localtime_r(&(tmp->mtime), &tm);
867
                strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm);
868
#else
869
                strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime)));
870
#endif
871
                http_list_directory_sizefmt(sizebuf, tmp->size);
872

    
873
                buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\""));
874
                buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART);
875
                buffer_append_string_len(out, CONST_STR_LEN("\">"));
876
                buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML);
877
                buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">"));
878
                buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1);
879
                buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">"));
880
                buffer_append_string(out, sizebuf);
881
                buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">"));
882
                buffer_append_string(out, content_type);
883
                buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n"));
884

    
885
                free(tmp);
886
        }
887

    
888
        free(files.ent);
889
        free(dirs.ent);
890
        free(path);
891

    
892
        http_list_directory_footer(srv, con, p, out);
893

    
894
        /* Insert possible charset to Content-Type */
895
        if (buffer_is_empty(p->conf.encoding)) {
896
                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
897
        } else {
898
                buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset="));
899
                buffer_append_string_buffer(p->content_charset, p->conf.encoding);
900
                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset));
901
        }
902

    
903
        con->file_finished = 1;
904

    
905
        return 0;
906
}
907

    
908

    
909

    
910
URIHANDLER_FUNC(mod_dirlisting_subrequest) {
911
        plugin_data *p = p_d;
912
        stat_cache_entry *sce = NULL;
913

    
914
        UNUSED(srv);
915

    
916
        /* we only handle GET, POST and HEAD */
917
        switch(con->request.http_method) {
918
        case HTTP_METHOD_GET:
919
        case HTTP_METHOD_POST:
920
        case HTTP_METHOD_HEAD:
921
                break;
922
        default:
923
                return HANDLER_GO_ON;
924
        }
925

    
926
        if (con->mode != DIRECT) return HANDLER_GO_ON;
927

    
928
        if (con->physical.path->used == 0) return HANDLER_GO_ON;
929
        if (con->uri.path->used == 0) return HANDLER_GO_ON;
930
        if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON;
931

    
932
        mod_dirlisting_patch_connection(srv, con, p);
933

    
934
        if (!p->conf.dir_listing) return HANDLER_GO_ON;
935

    
936
        if (con->conf.log_request_handling) {
937
                log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling the request as Dir-Listing");
938
                log_error_write(srv, __FILE__, __LINE__,  "sb", "URI          :", con->uri.path);
939
        }
940

    
941
        if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
942
                log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
943
                SEGFAULT();
944
        }
945

    
946
        if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON;
947

    
948
        if (http_list_directory(srv, con, p, con->physical.path)) {
949
                /* dirlisting failed */
950
                con->http_status = 403;
951
        }
952

    
953
        buffer_reset(con->physical.path);
954

    
955
        /* not found */
956
        return HANDLER_FINISHED;
957
}
958

    
959
/* this function is called at dlopen() time and inits the callbacks */
960

    
961
int mod_dirlisting_plugin_init(plugin *p);
962
int mod_dirlisting_plugin_init(plugin *p) {
963
        p->version     = LIGHTTPD_VERSION_ID;
964
        p->name        = buffer_init_string("dirlisting");
965

    
966
        p->init        = mod_dirlisting_init;
967
        p->handle_subrequest_start  = mod_dirlisting_subrequest;
968
        p->set_defaults  = mod_dirlisting_set_defaults;
969
        p->cleanup     = mod_dirlisting_free;
970

    
971
        p->data        = NULL;
972

    
973
        return 0;
974
}