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\">&nbsp;</td>"
                    597:                        "<td class=\"s\">- &nbsp;</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\">- &nbsp;</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>