Annotation of embedaddon/lighttpd/src/mod_staticfile.c, revision 1.1.1.2

1.1       misho       1: #include "base.h"
                      2: #include "log.h"
                      3: #include "buffer.h"
                      4: 
                      5: #include "plugin.h"
                      6: 
                      7: #include "stat_cache.h"
                      8: #include "etag.h"
                      9: #include "http_chunk.h"
                     10: #include "response.h"
                     11: 
                     12: #include <ctype.h>
                     13: #include <stdlib.h>
                     14: #include <stdio.h>
                     15: #include <string.h>
                     16: 
                     17: /**
                     18:  * this is a staticfile for a lighttpd plugin
                     19:  *
                     20:  */
                     21: 
                     22: 
                     23: 
                     24: /* plugin config for all request/connections */
                     25: 
                     26: typedef struct {
                     27:        array *exclude_ext;
                     28:        unsigned short etags_used;
                     29:        unsigned short disable_pathinfo;
                     30: } plugin_config;
                     31: 
                     32: typedef struct {
                     33:        PLUGIN_DATA;
                     34: 
                     35:        buffer *range_buf;
                     36: 
                     37:        plugin_config **config_storage;
                     38: 
                     39:        plugin_config conf;
                     40: } plugin_data;
                     41: 
                     42: /* init the plugin data */
                     43: INIT_FUNC(mod_staticfile_init) {
                     44:        plugin_data *p;
                     45: 
                     46:        p = calloc(1, sizeof(*p));
                     47: 
                     48:        p->range_buf = buffer_init();
                     49: 
                     50:        return p;
                     51: }
                     52: 
                     53: /* detroy the plugin data */
                     54: FREE_FUNC(mod_staticfile_free) {
                     55:        plugin_data *p = p_d;
                     56: 
                     57:        UNUSED(srv);
                     58: 
                     59:        if (!p) return HANDLER_GO_ON;
                     60: 
                     61:        if (p->config_storage) {
                     62:                size_t i;
                     63:                for (i = 0; i < srv->config_context->used; i++) {
                     64:                        plugin_config *s = p->config_storage[i];
                     65: 
                     66:                        array_free(s->exclude_ext);
                     67: 
                     68:                        free(s);
                     69:                }
                     70:                free(p->config_storage);
                     71:        }
                     72:        buffer_free(p->range_buf);
                     73: 
                     74:        free(p);
                     75: 
                     76:        return HANDLER_GO_ON;
                     77: }
                     78: 
                     79: /* handle plugin config and check values */
                     80: 
                     81: SETDEFAULTS_FUNC(mod_staticfile_set_defaults) {
                     82:        plugin_data *p = p_d;
                     83:        size_t i = 0;
                     84: 
                     85:        config_values_t cv[] = {
                     86:                { "static-file.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
                     87:                { "static-file.etags",    NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
                     88:                { "static-file.disable-pathinfo", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
                     89:                { NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
                     90:        };
                     91: 
                     92:        if (!p) return HANDLER_ERROR;
                     93: 
1.1.1.2 ! misho      94:        p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1       misho      95: 
                     96:        for (i = 0; i < srv->config_context->used; i++) {
                     97:                plugin_config *s;
                     98: 
                     99:                s = calloc(1, sizeof(plugin_config));
                    100:                s->exclude_ext    = array_init();
                    101:                s->etags_used     = 1;
                    102:                s->disable_pathinfo = 0;
                    103: 
                    104:                cv[0].destination = s->exclude_ext;
                    105:                cv[1].destination = &(s->etags_used);
                    106:                cv[2].destination = &(s->disable_pathinfo);
                    107: 
                    108:                p->config_storage[i] = s;
                    109: 
                    110:                if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
                    111:                        return HANDLER_ERROR;
                    112:                }
                    113:        }
                    114: 
                    115:        return HANDLER_GO_ON;
                    116: }
                    117: 
                    118: #define PATCH(x) \
                    119:        p->conf.x = s->x;
                    120: static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) {
                    121:        size_t i, j;
                    122:        plugin_config *s = p->config_storage[0];
                    123: 
                    124:        PATCH(exclude_ext);
                    125:        PATCH(etags_used);
                    126:        PATCH(disable_pathinfo);
                    127: 
                    128:        /* skip the first, the global context */
                    129:        for (i = 1; i < srv->config_context->used; i++) {
                    130:                data_config *dc = (data_config *)srv->config_context->data[i];
                    131:                s = p->config_storage[i];
                    132: 
                    133:                /* condition didn't match */
                    134:                if (!config_check_cond(srv, con, dc)) continue;
                    135: 
                    136:                /* merge config */
                    137:                for (j = 0; j < dc->value->used; j++) {
                    138:                        data_unset *du = dc->value->data[j];
                    139: 
                    140:                        if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.exclude-extensions"))) {
                    141:                                PATCH(exclude_ext);
                    142:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.etags"))) {
                    143:                                PATCH(etags_used);
                    144:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.disable-pathinfo"))) {
                    145:                                PATCH(disable_pathinfo);
                    146:                        }
                    147:                }
                    148:        }
                    149: 
                    150:        return 0;
                    151: }
                    152: #undef PATCH
                    153: 
                    154: static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
                    155:        int multipart = 0;
                    156:        int error;
                    157:        off_t start, end;
                    158:        const char *s, *minus;
                    159:        char *boundary = "fkj49sn38dcn3";
                    160:        data_string *ds;
                    161:        stat_cache_entry *sce = NULL;
                    162:        buffer *content_type = NULL;
                    163: 
                    164:        if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
                    165:                SEGFAULT();
                    166:        }
                    167: 
                    168:        start = 0;
                    169:        end = sce->st.st_size - 1;
                    170: 
                    171:        con->response.content_length = 0;
                    172: 
                    173:        if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
                    174:                content_type = ds->value;
                    175:        }
                    176: 
                    177:        for (s = con->request.http_range, error = 0;
                    178:             !error && *s && NULL != (minus = strchr(s, '-')); ) {
                    179:                char *err;
                    180:                off_t la, le;
                    181: 
                    182:                if (s == minus) {
                    183:                        /* -<stop> */
                    184: 
                    185:                        le = strtoll(s, &err, 10);
                    186: 
                    187:                        if (le == 0) {
                    188:                                /* RFC 2616 - 14.35.1 */
                    189: 
                    190:                                con->http_status = 416;
                    191:                                error = 1;
                    192:                        } else if (*err == '\0') {
                    193:                                /* end */
                    194:                                s = err;
                    195: 
                    196:                                end = sce->st.st_size - 1;
                    197:                                start = sce->st.st_size + le;
                    198:                        } else if (*err == ',') {
                    199:                                multipart = 1;
                    200:                                s = err + 1;
                    201: 
                    202:                                end = sce->st.st_size - 1;
                    203:                                start = sce->st.st_size + le;
                    204:                        } else {
                    205:                                error = 1;
                    206:                        }
                    207: 
                    208:                } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
                    209:                        /* <start>- */
                    210: 
                    211:                        la = strtoll(s, &err, 10);
                    212: 
                    213:                        if (err == minus) {
                    214:                                /* ok */
                    215: 
                    216:                                if (*(err + 1) == '\0') {
                    217:                                        s = err + 1;
                    218: 
                    219:                                        end = sce->st.st_size - 1;
                    220:                                        start = la;
                    221: 
                    222:                                } else if (*(err + 1) == ',') {
                    223:                                        multipart = 1;
                    224:                                        s = err + 2;
                    225: 
                    226:                                        end = sce->st.st_size - 1;
                    227:                                        start = la;
                    228:                                } else {
                    229:                                        error = 1;
                    230:                                }
                    231:                        } else {
                    232:                                /* error */
                    233:                                error = 1;
                    234:                        }
                    235:                } else {
                    236:                        /* <start>-<stop> */
                    237: 
                    238:                        la = strtoll(s, &err, 10);
                    239: 
                    240:                        if (err == minus) {
                    241:                                le = strtoll(minus+1, &err, 10);
                    242: 
                    243:                                /* RFC 2616 - 14.35.1 */
                    244:                                if (la > le) {
                    245:                                        error = 1;
                    246:                                }
                    247: 
                    248:                                if (*err == '\0') {
                    249:                                        /* ok, end*/
                    250:                                        s = err;
                    251: 
                    252:                                        end = le;
                    253:                                        start = la;
                    254:                                } else if (*err == ',') {
                    255:                                        multipart = 1;
                    256:                                        s = err + 1;
                    257: 
                    258:                                        end = le;
                    259:                                        start = la;
                    260:                                } else {
                    261:                                        /* error */
                    262: 
                    263:                                        error = 1;
                    264:                                }
                    265:                        } else {
                    266:                                /* error */
                    267: 
                    268:                                error = 1;
                    269:                        }
                    270:                }
                    271: 
                    272:                if (!error) {
                    273:                        if (start < 0) start = 0;
                    274: 
                    275:                        /* RFC 2616 - 14.35.1 */
                    276:                        if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
                    277: 
                    278:                        if (start > sce->st.st_size - 1) {
                    279:                                error = 1;
                    280: 
                    281:                                con->http_status = 416;
                    282:                        }
                    283:                }
                    284: 
                    285:                if (!error) {
                    286:                        if (multipart) {
                    287:                                /* write boundary-header */
                    288:                                buffer *b;
                    289: 
                    290:                                b = chunkqueue_get_append_buffer(con->write_queue);
                    291: 
                    292:                                buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
                    293:                                buffer_append_string(b, boundary);
                    294: 
                    295:                                /* write Content-Range */
                    296:                                buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
                    297:                                buffer_append_off_t(b, start);
                    298:                                buffer_append_string_len(b, CONST_STR_LEN("-"));
                    299:                                buffer_append_off_t(b, end);
                    300:                                buffer_append_string_len(b, CONST_STR_LEN("/"));
                    301:                                buffer_append_off_t(b, sce->st.st_size);
                    302: 
                    303:                                buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
                    304:                                buffer_append_string_buffer(b, content_type);
                    305: 
                    306:                                /* write END-OF-HEADER */
                    307:                                buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
                    308: 
                    309:                                con->response.content_length += b->used - 1;
                    310: 
                    311:                        }
                    312: 
                    313:                        chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
                    314:                        con->response.content_length += end - start + 1;
                    315:                }
                    316:        }
                    317: 
                    318:        /* something went wrong */
                    319:        if (error) return -1;
                    320: 
                    321:        if (multipart) {
                    322:                /* add boundary end */
                    323:                buffer *b;
                    324: 
                    325:                b = chunkqueue_get_append_buffer(con->write_queue);
                    326: 
                    327:                buffer_copy_string_len(b, "\r\n--", 4);
                    328:                buffer_append_string(b, boundary);
                    329:                buffer_append_string_len(b, "--\r\n", 4);
                    330: 
                    331:                con->response.content_length += b->used - 1;
                    332: 
                    333:                /* set header-fields */
                    334: 
                    335:                buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary="));
                    336:                buffer_append_string(p->range_buf, boundary);
                    337: 
                    338:                /* overwrite content-type */
                    339:                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
                    340:        } else {
                    341:                /* add Content-Range-header */
                    342: 
                    343:                buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes "));
                    344:                buffer_append_off_t(p->range_buf, start);
                    345:                buffer_append_string_len(p->range_buf, CONST_STR_LEN("-"));
                    346:                buffer_append_off_t(p->range_buf, end);
                    347:                buffer_append_string_len(p->range_buf, CONST_STR_LEN("/"));
                    348:                buffer_append_off_t(p->range_buf, sce->st.st_size);
                    349: 
                    350:                response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
                    351:        }
                    352: 
                    353:        /* ok, the file is set-up */
                    354:        return 0;
                    355: }
                    356: 
                    357: URIHANDLER_FUNC(mod_staticfile_subrequest) {
                    358:        plugin_data *p = p_d;
                    359:        size_t k;
                    360:        stat_cache_entry *sce = NULL;
                    361:        buffer *mtime = NULL;
                    362:        data_string *ds;
                    363:        int allow_caching = 1;
                    364: 
                    365:        /* someone else has done a decision for us */
                    366:        if (con->http_status != 0) return HANDLER_GO_ON;
                    367:        if (con->uri.path->used == 0) return HANDLER_GO_ON;
                    368:        if (con->physical.path->used == 0) return HANDLER_GO_ON;
                    369: 
                    370:        /* someone else has handled this request */
                    371:        if (con->mode != DIRECT) return HANDLER_GO_ON;
                    372: 
                    373:        /* we only handle GET, POST and HEAD */
                    374:        switch(con->request.http_method) {
                    375:        case HTTP_METHOD_GET:
                    376:        case HTTP_METHOD_POST:
                    377:        case HTTP_METHOD_HEAD:
                    378:                break;
                    379:        default:
                    380:                return HANDLER_GO_ON;
                    381:        }
                    382: 
                    383:        mod_staticfile_patch_connection(srv, con, p);
                    384: 
                    385:        if (p->conf.disable_pathinfo && 0 != con->request.pathinfo->used) {
                    386:                if (con->conf.log_request_handling) {
                    387:                        log_error_write(srv, __FILE__, __LINE__,  "s",  "-- NOT handling file as static file, pathinfo forbidden");
                    388:                }
                    389:                return HANDLER_GO_ON;
                    390:        }
                    391: 
                    392:        /* ignore certain extensions */
                    393:        for (k = 0; k < p->conf.exclude_ext->used; k++) {
                    394:                ds = (data_string *)p->conf.exclude_ext->data[k];
                    395: 
                    396:                if (ds->value->used == 0) continue;
                    397: 
                    398:                if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
                    399:                        if (con->conf.log_request_handling) {
                    400:                                log_error_write(srv, __FILE__, __LINE__,  "s",  "-- NOT handling file as static file, extension forbidden");
                    401:                        }
                    402:                        return HANDLER_GO_ON;
                    403:                }
                    404:        }
                    405: 
                    406: 
                    407:        if (con->conf.log_request_handling) {
                    408:                log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling file as static file");
                    409:        }
                    410: 
                    411:        if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
                    412:                con->http_status = 403;
                    413: 
                    414:                log_error_write(srv, __FILE__, __LINE__, "sbsb",
                    415:                                "not a regular file:", con->uri.path,
                    416:                                "->", con->physical.path);
                    417: 
                    418:                return HANDLER_FINISHED;
                    419:        }
                    420: 
                    421:        /* we only handline regular files */
                    422: #ifdef HAVE_LSTAT
                    423:        if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
                    424:                con->http_status = 403;
                    425: 
                    426:                if (con->conf.log_request_handling) {
                    427:                        log_error_write(srv, __FILE__, __LINE__,  "s",  "-- access denied due symlink restriction");
                    428:                        log_error_write(srv, __FILE__, __LINE__,  "sb", "Path         :", con->physical.path);
                    429:                }
                    430: 
                    431:                buffer_reset(con->physical.path);
                    432:                return HANDLER_FINISHED;
                    433:        }
                    434: #endif
                    435:        if (!S_ISREG(sce->st.st_mode)) {
                    436:                con->http_status = 404;
                    437: 
                    438:                if (con->conf.log_file_not_found) {
                    439:                        log_error_write(srv, __FILE__, __LINE__, "sbsb",
                    440:                                        "not a regular file:", con->uri.path,
                    441:                                        "->", sce->name);
                    442:                }
                    443: 
                    444:                return HANDLER_FINISHED;
                    445:        }
                    446: 
                    447:        /* mod_compress might set several data directly, don't overwrite them */
                    448: 
                    449:        /* set response content-type, if not set already */
                    450: 
                    451:        if (NULL == array_get_element(con->response.headers, "Content-Type")) {
                    452:                if (buffer_is_empty(sce->content_type)) {
                    453:                        /* we are setting application/octet-stream, but also announce that
                    454:                         * this header field might change in the seconds few requests 
                    455:                         *
                    456:                         * This should fix the aggressive caching of FF and the script download
                    457:                         * seen by the first installations
                    458:                         */
                    459:                        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
                    460: 
                    461:                        allow_caching = 0;
                    462:                } else {
                    463:                        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
                    464:                }
                    465:        }
                    466: 
                    467:        if (con->conf.range_requests) {
                    468:                response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
                    469:        }
                    470: 
                    471:        if (allow_caching) {
                    472:                if (p->conf.etags_used && con->etag_flags != 0 && !buffer_is_empty(sce->etag)) {
                    473:                        if (NULL == array_get_element(con->response.headers, "ETag")) {
                    474:                                /* generate e-tag */
                    475:                                etag_mutate(con->physical.etag, sce->etag);
                    476: 
                    477:                                response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
                    478:                        }
                    479:                }
                    480: 
                    481:                /* prepare header */
                    482:                if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
                    483:                        mtime = strftime_cache_get(srv, sce->st.st_mtime);
                    484:                        response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
                    485:                } else {
                    486:                        mtime = ds->value;
                    487:                }
                    488: 
                    489:                if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
                    490:                        return HANDLER_FINISHED;
                    491:                }
                    492:        }
                    493: 
                    494:        if (con->request.http_range && con->conf.range_requests) {
                    495:                int do_range_request = 1;
                    496:                /* check if we have a conditional GET */
                    497: 
                    498:                if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
                    499:                        /* if the value is the same as our ETag, we do a Range-request,
                    500:                         * otherwise a full 200 */
                    501: 
                    502:                        if (ds->value->ptr[0] == '"') {
                    503:                                /**
                    504:                                 * client wants a ETag
                    505:                                 */
                    506:                                if (!con->physical.etag) {
                    507:                                        do_range_request = 0;
                    508:                                } else if (!buffer_is_equal(ds->value, con->physical.etag)) {
                    509:                                        do_range_request = 0;
                    510:                                }
                    511:                        } else if (!mtime) {
                    512:                                /**
                    513:                                 * we don't have a Last-Modified and can match the If-Range: 
                    514:                                 *
                    515:                                 * sending all
                    516:                                 */
                    517:                                do_range_request = 0;
                    518:                        } else if (!buffer_is_equal(ds->value, mtime)) {
                    519:                                do_range_request = 0;
                    520:                        }
                    521:                }
                    522: 
                    523:                if (do_range_request) {
                    524:                        /* content prepared, I'm done */
                    525:                        con->file_finished = 1;
                    526: 
                    527:                        if (0 == http_response_parse_range(srv, con, p)) {
                    528:                                con->http_status = 206;
                    529:                        }
                    530:                        return HANDLER_FINISHED;
                    531:                }
                    532:        }
                    533: 
                    534:        /* if we are still here, prepare body */
                    535: 
                    536:        /* we add it here for all requests
                    537:         * the HEAD request will drop it afterwards again
                    538:         */
                    539:        http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
                    540: 
                    541:        con->http_status = 200;
                    542:        con->file_finished = 1;
                    543: 
                    544:        return HANDLER_FINISHED;
                    545: }
                    546: 
                    547: /* this function is called at dlopen() time and inits the callbacks */
                    548: 
                    549: int mod_staticfile_plugin_init(plugin *p);
                    550: int mod_staticfile_plugin_init(plugin *p) {
                    551:        p->version     = LIGHTTPD_VERSION_ID;
                    552:        p->name        = buffer_init_string("staticfile");
                    553: 
                    554:        p->init        = mod_staticfile_init;
                    555:        p->handle_subrequest_start = mod_staticfile_subrequest;
                    556:        p->set_defaults  = mod_staticfile_set_defaults;
                    557:        p->cleanup     = mod_staticfile_free;
                    558: 
                    559:        p->data        = NULL;
                    560: 
                    561:        return 0;
                    562: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>