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

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: 
        !            94:        p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
        !            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>