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

1.1     ! misho       1: #include "base.h"
        !             2: #include "log.h"
        !             3: #include "buffer.h"
        !             4: #include "response.h"
        !             5: #include "stat_cache.h"
        !             6: 
        !             7: #include "plugin.h"
        !             8: 
        !             9: #include "crc32.h"
        !            10: #include "etag.h"
        !            11: 
        !            12: #include <sys/types.h>
        !            13: #include <sys/stat.h>
        !            14: 
        !            15: #include <fcntl.h>
        !            16: #include <unistd.h>
        !            17: #include <ctype.h>
        !            18: #include <stdlib.h>
        !            19: #include <string.h>
        !            20: #include <errno.h>
        !            21: #include <time.h>
        !            22: 
        !            23: #if defined HAVE_ZLIB_H && defined HAVE_LIBZ
        !            24: # define USE_ZLIB
        !            25: # include <zlib.h>
        !            26: #endif
        !            27: 
        !            28: #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
        !            29: # define USE_BZ2LIB
        !            30: /* we don't need stdio interface */
        !            31: # define BZ_NO_STDIO
        !            32: # include <bzlib.h>
        !            33: #endif
        !            34: 
        !            35: #include "sys-mmap.h"
        !            36: 
        !            37: /* request: accept-encoding */
        !            38: #define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
        !            39: #define HTTP_ACCEPT_ENCODING_GZIP     BV(1)
        !            40: #define HTTP_ACCEPT_ENCODING_DEFLATE  BV(2)
        !            41: #define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
        !            42: #define HTTP_ACCEPT_ENCODING_BZIP2    BV(4)
        !            43: #define HTTP_ACCEPT_ENCODING_X_GZIP   BV(5)
        !            44: #define HTTP_ACCEPT_ENCODING_X_BZIP2  BV(6)
        !            45: 
        !            46: #ifdef __WIN32
        !            47: # define mkdir(x,y) mkdir(x)
        !            48: #endif
        !            49: 
        !            50: typedef struct {
        !            51:        buffer *compress_cache_dir;
        !            52:        array  *compress;
        !            53:        off_t   compress_max_filesize; /** max filesize in kb */
        !            54:        int     allowed_encodings;
        !            55: } plugin_config;
        !            56: 
        !            57: typedef struct {
        !            58:        PLUGIN_DATA;
        !            59:        buffer *ofn;
        !            60:        buffer *b;
        !            61: 
        !            62:        plugin_config **config_storage;
        !            63:        plugin_config conf;
        !            64: } plugin_data;
        !            65: 
        !            66: INIT_FUNC(mod_compress_init) {
        !            67:        plugin_data *p;
        !            68: 
        !            69:        p = calloc(1, sizeof(*p));
        !            70: 
        !            71:        p->ofn = buffer_init();
        !            72:        p->b = buffer_init();
        !            73: 
        !            74:        return p;
        !            75: }
        !            76: 
        !            77: FREE_FUNC(mod_compress_free) {
        !            78:        plugin_data *p = p_d;
        !            79: 
        !            80:        UNUSED(srv);
        !            81: 
        !            82:        if (!p) return HANDLER_GO_ON;
        !            83: 
        !            84:        buffer_free(p->ofn);
        !            85:        buffer_free(p->b);
        !            86: 
        !            87:        if (p->config_storage) {
        !            88:                size_t i;
        !            89:                for (i = 0; i < srv->config_context->used; i++) {
        !            90:                        plugin_config *s = p->config_storage[i];
        !            91: 
        !            92:                        if (!s) continue;
        !            93: 
        !            94:                        array_free(s->compress);
        !            95:                        buffer_free(s->compress_cache_dir);
        !            96: 
        !            97:                        free(s);
        !            98:                }
        !            99:                free(p->config_storage);
        !           100:        }
        !           101: 
        !           102: 
        !           103:        free(p);
        !           104: 
        !           105:        return HANDLER_GO_ON;
        !           106: }
        !           107: 
        !           108: /* 0 on success, -1 for error */
        !           109: static int mkdir_recursive(char *dir) {
        !           110:        char *p = dir;
        !           111: 
        !           112:        if (!dir || !dir[0])
        !           113:                return 0;
        !           114: 
        !           115:        while ((p = strchr(p + 1, '/')) != NULL) {
        !           116: 
        !           117:                *p = '\0';
        !           118:                if ((mkdir(dir, 0700) != 0) && (errno != EEXIST)) {
        !           119:                        *p = '/';
        !           120:                        return -1;
        !           121:                }
        !           122: 
        !           123:                *p++ = '/';
        !           124:                if (!*p) return 0; /* Ignore trailing slash */
        !           125:        }
        !           126: 
        !           127:        return (mkdir(dir, 0700) != 0) && (errno != EEXIST) ? -1 : 0;
        !           128: }
        !           129: 
        !           130: /* 0 on success, -1 for error */
        !           131: static int mkdir_for_file(char *filename) {
        !           132:        char *p = filename;
        !           133: 
        !           134:        if (!filename || !filename[0])
        !           135:                return -1;
        !           136: 
        !           137:        while ((p = strchr(p + 1, '/')) != NULL) {
        !           138: 
        !           139:                *p = '\0';
        !           140:                if ((mkdir(filename, 0700) != 0) && (errno != EEXIST)) {
        !           141:                        *p = '/';
        !           142:                        return -1;
        !           143:                }
        !           144: 
        !           145:                *p++ = '/';
        !           146:                if (!*p) return -1; /* Unexpected trailing slash in filename */
        !           147:        }
        !           148: 
        !           149:        return 0;
        !           150: }
        !           151: 
        !           152: SETDEFAULTS_FUNC(mod_compress_setdefaults) {
        !           153:        plugin_data *p = p_d;
        !           154:        size_t i = 0;
        !           155: 
        !           156:        config_values_t cv[] = {
        !           157:                { "compress.cache-dir",             NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
        !           158:                { "compress.filetype",              NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
        !           159:                { "compress.max-filesize",          NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
        !           160:                { "compress.allowed-encodings",     NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
        !           161:                { NULL,                             NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
        !           162:        };
        !           163: 
        !           164:        p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
        !           165: 
        !           166:        for (i = 0; i < srv->config_context->used; i++) {
        !           167:                plugin_config *s;
        !           168:                array  *encodings_arr = array_init();
        !           169: 
        !           170:                s = calloc(1, sizeof(plugin_config));
        !           171:                s->compress_cache_dir = buffer_init();
        !           172:                s->compress = array_init();
        !           173:                s->compress_max_filesize = 0;
        !           174:                s->allowed_encodings = 0;
        !           175: 
        !           176:                cv[0].destination = s->compress_cache_dir;
        !           177:                cv[1].destination = s->compress;
        !           178:                cv[2].destination = &(s->compress_max_filesize);
        !           179:                cv[3].destination = encodings_arr; /* temp array for allowed encodings list */
        !           180: 
        !           181:                p->config_storage[i] = s;
        !           182: 
        !           183:                if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
        !           184:                        return HANDLER_ERROR;
        !           185:                }
        !           186: 
        !           187:                if (encodings_arr->used) {
        !           188:                        size_t j = 0;
        !           189:                        for (j = 0; j < encodings_arr->used; j++) {
        !           190:                                data_string *ds = (data_string *)encodings_arr->data[j];
        !           191: #ifdef USE_ZLIB
        !           192:                                if (NULL != strstr(ds->value->ptr, "gzip"))
        !           193:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP | HTTP_ACCEPT_ENCODING_X_GZIP;
        !           194:                                if (NULL != strstr(ds->value->ptr, "x-gzip"))
        !           195:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_X_GZIP;
        !           196:                                if (NULL != strstr(ds->value->ptr, "deflate"))
        !           197:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
        !           198:                                /*
        !           199:                                if (NULL != strstr(ds->value->ptr, "compress"))
        !           200:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_COMPRESS;
        !           201:                                */
        !           202: #endif
        !           203: #ifdef USE_BZ2LIB
        !           204:                                if (NULL != strstr(ds->value->ptr, "bzip2"))
        !           205:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2 | HTTP_ACCEPT_ENCODING_X_BZIP2;
        !           206:                                if (NULL != strstr(ds->value->ptr, "x-bzip2"))
        !           207:                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_X_BZIP2;
        !           208: #endif
        !           209:                        }
        !           210:                } else {
        !           211:                        /* default encodings */
        !           212:                        s->allowed_encodings = 0
        !           213: #ifdef USE_ZLIB
        !           214:                                | HTTP_ACCEPT_ENCODING_GZIP | HTTP_ACCEPT_ENCODING_X_GZIP | HTTP_ACCEPT_ENCODING_DEFLATE
        !           215: #endif
        !           216: #ifdef USE_BZ2LIB
        !           217:                                | HTTP_ACCEPT_ENCODING_BZIP2 | HTTP_ACCEPT_ENCODING_X_BZIP2
        !           218: #endif
        !           219:                                ;
        !           220:                }
        !           221: 
        !           222:                array_free(encodings_arr);
        !           223: 
        !           224:                if (!buffer_is_empty(s->compress_cache_dir)) {
        !           225:                        struct stat st;
        !           226:                        mkdir_recursive(s->compress_cache_dir->ptr);
        !           227: 
        !           228:                        if (0 != stat(s->compress_cache_dir->ptr, &st)) {
        !           229:                                log_error_write(srv, __FILE__, __LINE__, "sbs", "can't stat compress.cache-dir",
        !           230:                                                s->compress_cache_dir, strerror(errno));
        !           231: 
        !           232:                                return HANDLER_ERROR;
        !           233:                        }
        !           234:                }
        !           235:        }
        !           236: 
        !           237:        return HANDLER_GO_ON;
        !           238: 
        !           239: }
        !           240: 
        !           241: #ifdef USE_ZLIB
        !           242: static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, char *start, off_t st_size, time_t mtime) {
        !           243:        unsigned char *c;
        !           244:        unsigned long crc;
        !           245:        z_stream z;
        !           246: 
        !           247:        UNUSED(srv);
        !           248:        UNUSED(con);
        !           249: 
        !           250:        z.zalloc = Z_NULL;
        !           251:        z.zfree = Z_NULL;
        !           252:        z.opaque = Z_NULL;
        !           253: 
        !           254:        if (Z_OK != deflateInit2(&z,
        !           255:                                 Z_DEFAULT_COMPRESSION,
        !           256:                                 Z_DEFLATED,
        !           257:                                 -MAX_WBITS,  /* supress zlib-header */
        !           258:                                 8,
        !           259:                                 Z_DEFAULT_STRATEGY)) {
        !           260:                return -1;
        !           261:        }
        !           262: 
        !           263:        z.next_in = (unsigned char *)start;
        !           264:        z.avail_in = st_size;
        !           265:        z.total_in = 0;
        !           266: 
        !           267: 
        !           268:        buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12 + 18);
        !           269: 
        !           270:        /* write gzip header */
        !           271: 
        !           272:        c = (unsigned char *)p->b->ptr;
        !           273:        c[0] = 0x1f;
        !           274:        c[1] = 0x8b;
        !           275:        c[2] = Z_DEFLATED;
        !           276:        c[3] = 0; /* options */
        !           277:        c[4] = (mtime >>  0) & 0xff;
        !           278:        c[5] = (mtime >>  8) & 0xff;
        !           279:        c[6] = (mtime >> 16) & 0xff;
        !           280:        c[7] = (mtime >> 24) & 0xff;
        !           281:        c[8] = 0x00; /* extra flags */
        !           282:        c[9] = 0x03; /* UNIX */
        !           283: 
        !           284:        p->b->used = 10;
        !           285:        z.next_out = (unsigned char *)p->b->ptr + p->b->used;
        !           286:        z.avail_out = p->b->size - p->b->used - 8;
        !           287:        z.total_out = 0;
        !           288: 
        !           289:        if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
        !           290:                deflateEnd(&z);
        !           291:                return -1;
        !           292:        }
        !           293: 
        !           294:        /* trailer */
        !           295:        p->b->used += z.total_out;
        !           296: 
        !           297:        crc = generate_crc32c(start, st_size);
        !           298: 
        !           299:        c = (unsigned char *)p->b->ptr + p->b->used;
        !           300: 
        !           301:        c[0] = (crc >>  0) & 0xff;
        !           302:        c[1] = (crc >>  8) & 0xff;
        !           303:        c[2] = (crc >> 16) & 0xff;
        !           304:        c[3] = (crc >> 24) & 0xff;
        !           305:        c[4] = (z.total_in >>  0) & 0xff;
        !           306:        c[5] = (z.total_in >>  8) & 0xff;
        !           307:        c[6] = (z.total_in >> 16) & 0xff;
        !           308:        c[7] = (z.total_in >> 24) & 0xff;
        !           309:        p->b->used += 8;
        !           310: 
        !           311:        if (Z_OK != deflateEnd(&z)) {
        !           312:                return -1;
        !           313:        }
        !           314: 
        !           315:        return 0;
        !           316: }
        !           317: 
        !           318: static int deflate_file_to_buffer_deflate(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
        !           319:        z_stream z;
        !           320: 
        !           321:        UNUSED(srv);
        !           322:        UNUSED(con);
        !           323: 
        !           324:        z.zalloc = Z_NULL;
        !           325:        z.zfree = Z_NULL;
        !           326:        z.opaque = Z_NULL;
        !           327: 
        !           328:        if (Z_OK != deflateInit2(&z,
        !           329:                                 Z_DEFAULT_COMPRESSION,
        !           330:                                 Z_DEFLATED,
        !           331:                                 -MAX_WBITS,  /* supress zlib-header */
        !           332:                                 8,
        !           333:                                 Z_DEFAULT_STRATEGY)) {
        !           334:                return -1;
        !           335:        }
        !           336: 
        !           337:        z.next_in = start;
        !           338:        z.avail_in = st_size;
        !           339:        z.total_in = 0;
        !           340: 
        !           341:        buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12);
        !           342: 
        !           343:        z.next_out = (unsigned char *)p->b->ptr;
        !           344:        z.avail_out = p->b->size;
        !           345:        z.total_out = 0;
        !           346: 
        !           347:        if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
        !           348:                deflateEnd(&z);
        !           349:                return -1;
        !           350:        }
        !           351: 
        !           352:        /* trailer */
        !           353:        p->b->used += z.total_out;
        !           354: 
        !           355:        if (Z_OK != deflateEnd(&z)) {
        !           356:                return -1;
        !           357:        }
        !           358: 
        !           359:        return 0;
        !           360: }
        !           361: 
        !           362: #endif
        !           363: 
        !           364: #ifdef USE_BZ2LIB
        !           365: static int deflate_file_to_buffer_bzip2(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
        !           366:        bz_stream bz;
        !           367: 
        !           368:        UNUSED(srv);
        !           369:        UNUSED(con);
        !           370: 
        !           371:        bz.bzalloc = NULL;
        !           372:        bz.bzfree = NULL;
        !           373:        bz.opaque = NULL;
        !           374: 
        !           375:        if (BZ_OK != BZ2_bzCompressInit(&bz,
        !           376:                                        9, /* blocksize = 900k */
        !           377:                                        0, /* no output */
        !           378:                                        0)) { /* workFactor: default */
        !           379:                return -1;
        !           380:        }
        !           381: 
        !           382:        bz.next_in = (char *)start;
        !           383:        bz.avail_in = st_size;
        !           384:        bz.total_in_lo32 = 0;
        !           385:        bz.total_in_hi32 = 0;
        !           386: 
        !           387:        buffer_prepare_copy(p->b, (bz.avail_in * 1.1) + 12);
        !           388: 
        !           389:        bz.next_out = p->b->ptr;
        !           390:        bz.avail_out = p->b->size;
        !           391:        bz.total_out_lo32 = 0;
        !           392:        bz.total_out_hi32 = 0;
        !           393: 
        !           394:        if (BZ_STREAM_END != BZ2_bzCompress(&bz, BZ_FINISH)) {
        !           395:                BZ2_bzCompressEnd(&bz);
        !           396:                return -1;
        !           397:        }
        !           398: 
        !           399:        /* file is too large for now */
        !           400:        if (bz.total_out_hi32) return -1;
        !           401: 
        !           402:        /* trailer */
        !           403:        p->b->used = bz.total_out_lo32;
        !           404: 
        !           405:        if (BZ_OK != BZ2_bzCompressEnd(&bz)) {
        !           406:                return -1;
        !           407:        }
        !           408: 
        !           409:        return 0;
        !           410: }
        !           411: #endif
        !           412: 
        !           413: static int deflate_file_to_file(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
        !           414:        int ifd, ofd;
        !           415:        int ret = -1;
        !           416:        void *start;
        !           417:        const char *filename = fn->ptr;
        !           418:        ssize_t r;
        !           419: 
        !           420:        /* overflow */
        !           421:        if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
        !           422: 
        !           423:        /* don't mmap files > 128Mb
        !           424:         *
        !           425:         * we could use a sliding window, but currently there is no need for it
        !           426:         */
        !           427: 
        !           428:        if (sce->st.st_size > 128 * 1024 * 1024) return -1;
        !           429: 
        !           430:        buffer_reset(p->ofn);
        !           431:        buffer_copy_string_buffer(p->ofn, p->conf.compress_cache_dir);
        !           432:        BUFFER_APPEND_SLASH(p->ofn);
        !           433: 
        !           434:        if (0 == strncmp(con->physical.path->ptr, con->physical.doc_root->ptr, con->physical.doc_root->used-1)) {
        !           435:                buffer_append_string(p->ofn, con->physical.path->ptr + con->physical.doc_root->used - 1);
        !           436:                buffer_copy_string_buffer(p->b, p->ofn);
        !           437:        } else {
        !           438:                buffer_append_string_buffer(p->ofn, con->uri.path);
        !           439:        }
        !           440: 
        !           441:        switch(type) {
        !           442:        case HTTP_ACCEPT_ENCODING_GZIP:
        !           443:        case HTTP_ACCEPT_ENCODING_X_GZIP:
        !           444:                buffer_append_string_len(p->ofn, CONST_STR_LEN("-gzip-"));
        !           445:                break;
        !           446:        case HTTP_ACCEPT_ENCODING_DEFLATE:
        !           447:                buffer_append_string_len(p->ofn, CONST_STR_LEN("-deflate-"));
        !           448:                break;
        !           449:        case HTTP_ACCEPT_ENCODING_BZIP2:
        !           450:        case HTTP_ACCEPT_ENCODING_X_BZIP2:
        !           451:                buffer_append_string_len(p->ofn, CONST_STR_LEN("-bzip2-"));
        !           452:                break;
        !           453:        default:
        !           454:                log_error_write(srv, __FILE__, __LINE__, "sd", "unknown compression type", type);
        !           455:                return -1;
        !           456:        }
        !           457: 
        !           458:        buffer_append_string_buffer(p->ofn, sce->etag);
        !           459: 
        !           460:        if (-1 == mkdir_for_file(p->ofn->ptr)) {
        !           461:                log_error_write(srv, __FILE__, __LINE__, "sb", "couldn't create directory for file", p->ofn);
        !           462:                return -1;
        !           463:        }
        !           464: 
        !           465:        if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600))) {
        !           466:                if (errno == EEXIST) {
        !           467:                        /* cache-entry exists */
        !           468: #if 0
        !           469:                        log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache hit");
        !           470: #endif
        !           471:                        buffer_copy_string_buffer(con->physical.path, p->ofn);
        !           472: 
        !           473:                        return 0;
        !           474:                }
        !           475: 
        !           476:                log_error_write(srv, __FILE__, __LINE__, "sbss", "creating cachefile", p->ofn, "failed", strerror(errno));
        !           477: 
        !           478:                return -1;
        !           479:        }
        !           480: #if 0
        !           481:        log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache miss");
        !           482: #endif
        !           483:        if (-1 == (ifd = open(filename, O_RDONLY | O_BINARY))) {
        !           484:                log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
        !           485: 
        !           486:                close(ofd);
        !           487: 
        !           488:                /* Remove the incomplete cache file, so that later hits aren't served from it */
        !           489:                if (-1 == unlink(p->ofn->ptr)) {
        !           490:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "unlinking incomplete cachefile", p->ofn, "failed:", strerror(errno));
        !           491:                }
        !           492: 
        !           493:                return -1;
        !           494:        }
        !           495: 
        !           496: #ifdef USE_MMAP
        !           497:        if (MAP_FAILED == (start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
        !           498:                log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
        !           499: 
        !           500:                close(ofd);
        !           501:                close(ifd);
        !           502: 
        !           503:                /* Remove the incomplete cache file, so that later hits aren't served from it */
        !           504:                if (-1 == unlink(p->ofn->ptr)) {
        !           505:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "unlinking incomplete cachefile", p->ofn, "failed:", strerror(errno));
        !           506:                }
        !           507: 
        !           508:                return -1;
        !           509:        }
        !           510: #else
        !           511:        start = malloc(sce->st.st_size);
        !           512:        if (NULL == start || sce->st.st_size != read(ifd, start, sce->st.st_size)) {
        !           513:                log_error_write(srv, __FILE__, __LINE__, "sbss", "reading", fn, "failed", strerror(errno));
        !           514: 
        !           515:                close(ofd);
        !           516:                close(ifd);
        !           517:                free(start);
        !           518: 
        !           519:                /* Remove the incomplete cache file, so that later hits aren't served from it */
        !           520:                if (-1 == unlink(p->ofn->ptr)) {
        !           521:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "unlinking incomplete cachefile", p->ofn, "failed:", strerror(errno));
        !           522:                }
        !           523: 
        !           524:                return -1;
        !           525:        }
        !           526: #endif
        !           527: 
        !           528:        switch(type) {
        !           529: #ifdef USE_ZLIB
        !           530:        case HTTP_ACCEPT_ENCODING_GZIP:
        !           531:        case HTTP_ACCEPT_ENCODING_X_GZIP:
        !           532:                ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
        !           533:                break;
        !           534:        case HTTP_ACCEPT_ENCODING_DEFLATE:
        !           535:                ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
        !           536:                break;
        !           537: #endif
        !           538: #ifdef USE_BZ2LIB
        !           539:        case HTTP_ACCEPT_ENCODING_BZIP2:
        !           540:        case HTTP_ACCEPT_ENCODING_X_BZIP2:
        !           541:                ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
        !           542:                break;
        !           543: #endif
        !           544:        default:
        !           545:                ret = -1;
        !           546:                break;
        !           547:        }
        !           548: 
        !           549:        if (ret == 0) {
        !           550:                r = write(ofd, p->b->ptr, p->b->used);
        !           551:                if (-1 == r) {
        !           552:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "writing cachefile", p->ofn, "failed:", strerror(errno));
        !           553:                        ret = -1;
        !           554:                } else if ((size_t)r != p->b->used) {
        !           555:                        log_error_write(srv, __FILE__, __LINE__, "sbs", "writing cachefile", p->ofn, "failed: not enough bytes written");
        !           556:                        ret = -1;
        !           557:                }
        !           558:        }
        !           559: 
        !           560: #ifdef USE_MMAP
        !           561:        munmap(start, sce->st.st_size);
        !           562: #else
        !           563:        free(start);
        !           564: #endif
        !           565: 
        !           566:        close(ofd);
        !           567:        close(ifd);
        !           568: 
        !           569:        if (ret != 0) {
        !           570:                /* Remove the incomplete cache file, so that later hits aren't served from it */
        !           571:                if (-1 == unlink(p->ofn->ptr)) {
        !           572:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "unlinking incomplete cachefile", p->ofn, "failed:", strerror(errno));
        !           573:                }
        !           574: 
        !           575:                return -1;
        !           576:        }
        !           577: 
        !           578:        buffer_copy_string_buffer(con->physical.path, p->ofn);
        !           579: 
        !           580:        return 0;
        !           581: }
        !           582: 
        !           583: static int deflate_file_to_buffer(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
        !           584:        int ifd;
        !           585:        int ret = -1;
        !           586:        void *start;
        !           587:        buffer *b;
        !           588: 
        !           589:        /* overflow */
        !           590:        if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
        !           591: 
        !           592:        /* don't mmap files > 128M
        !           593:         *
        !           594:         * we could use a sliding window, but currently there is no need for it
        !           595:         */
        !           596: 
        !           597:        if (sce->st.st_size > 128 * 1024 * 1024) return -1;
        !           598: 
        !           599: 
        !           600:        if (-1 == (ifd = open(fn->ptr, O_RDONLY | O_BINARY))) {
        !           601:                log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
        !           602: 
        !           603:                return -1;
        !           604:        }
        !           605: 
        !           606: #ifdef USE_MMAP
        !           607:        if (MAP_FAILED == (start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
        !           608:                log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
        !           609: 
        !           610:                close(ifd);
        !           611:                return -1;
        !           612:        }
        !           613: #else
        !           614:        start = malloc(sce->st.st_size);
        !           615:        if (NULL == start || sce->st.st_size != read(ifd, start, sce->st.st_size)) {
        !           616:                log_error_write(srv, __FILE__, __LINE__, "sbss", "reading", fn, "failed", strerror(errno));
        !           617: 
        !           618:                close(ifd);
        !           619:                free(start);
        !           620:                return -1;
        !           621:        }
        !           622: #endif
        !           623: 
        !           624:        switch(type) {
        !           625: #ifdef USE_ZLIB
        !           626:        case HTTP_ACCEPT_ENCODING_GZIP:
        !           627:        case HTTP_ACCEPT_ENCODING_X_GZIP:
        !           628:                ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
        !           629:                break;
        !           630:        case HTTP_ACCEPT_ENCODING_DEFLATE:
        !           631:                ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
        !           632:                break;
        !           633: #endif
        !           634: #ifdef USE_BZ2LIB
        !           635:        case HTTP_ACCEPT_ENCODING_BZIP2:
        !           636:        case HTTP_ACCEPT_ENCODING_X_BZIP2:
        !           637:                ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
        !           638:                break;
        !           639: #endif
        !           640:        default:
        !           641:                ret = -1;
        !           642:                break;
        !           643:        }
        !           644: 
        !           645: #ifdef USE_MMAP
        !           646:        munmap(start, sce->st.st_size);
        !           647: #else
        !           648:        free(start);
        !           649: #endif
        !           650:        close(ifd);
        !           651: 
        !           652:        if (ret != 0) return -1;
        !           653: 
        !           654:        chunkqueue_reset(con->write_queue);
        !           655:        b = chunkqueue_get_append_buffer(con->write_queue);
        !           656:        buffer_copy_memory(b, p->b->ptr, p->b->used + 1);
        !           657: 
        !           658:        buffer_reset(con->physical.path);
        !           659: 
        !           660:        con->file_finished = 1;
        !           661:        con->file_started  = 1;
        !           662: 
        !           663:        return 0;
        !           664: }
        !           665: 
        !           666: 
        !           667: #define PATCH(x) \
        !           668:        p->conf.x = s->x;
        !           669: static int mod_compress_patch_connection(server *srv, connection *con, plugin_data *p) {
        !           670:        size_t i, j;
        !           671:        plugin_config *s = p->config_storage[0];
        !           672: 
        !           673:        PATCH(compress_cache_dir);
        !           674:        PATCH(compress);
        !           675:        PATCH(compress_max_filesize);
        !           676:        PATCH(allowed_encodings);
        !           677: 
        !           678:        /* skip the first, the global context */
        !           679:        for (i = 1; i < srv->config_context->used; i++) {
        !           680:                data_config *dc = (data_config *)srv->config_context->data[i];
        !           681:                s = p->config_storage[i];
        !           682: 
        !           683:                /* condition didn't match */
        !           684:                if (!config_check_cond(srv, con, dc)) continue;
        !           685: 
        !           686:                /* merge config */
        !           687:                for (j = 0; j < dc->value->used; j++) {
        !           688:                        data_unset *du = dc->value->data[j];
        !           689: 
        !           690:                        if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.cache-dir"))) {
        !           691:                                PATCH(compress_cache_dir);
        !           692:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.filetype"))) {
        !           693:                                PATCH(compress);
        !           694:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.max-filesize"))) {
        !           695:                                PATCH(compress_max_filesize);
        !           696:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.allowed-encodings"))) {
        !           697:                                PATCH(allowed_encodings);
        !           698:                        }
        !           699:                }
        !           700:        }
        !           701: 
        !           702:        return 0;
        !           703: }
        !           704: #undef PATCH
        !           705: 
        !           706: static int mod_compress_contains_encoding(const char *headervalue, const char *encoding) {
        !           707:        const char *m;
        !           708:        for ( ;; ) {
        !           709:                m = strstr(headervalue, encoding);
        !           710:                if (NULL == m) return 0;
        !           711:                if (m == headervalue || m[-1] == ' ' || m[-1] == ',') return 1;
        !           712: 
        !           713:                /* only partial match, search for next value */
        !           714:                m = strchr(m, ',');
        !           715:                if (NULL == m) return 0;
        !           716:                headervalue = m + 1;
        !           717:        }
        !           718: }
        !           719: 
        !           720: PHYSICALPATH_FUNC(mod_compress_physical) {
        !           721:        plugin_data *p = p_d;
        !           722:        size_t m;
        !           723:        off_t max_fsize;
        !           724:        stat_cache_entry *sce = NULL;
        !           725:        buffer *mtime = NULL;
        !           726:        buffer *content_type;
        !           727: 
        !           728:        if (con->mode != DIRECT || con->http_status) return HANDLER_GO_ON;
        !           729: 
        !           730:        /* only GET and POST can get compressed */
        !           731:        if (con->request.http_method != HTTP_METHOD_GET &&
        !           732:            con->request.http_method != HTTP_METHOD_POST) {
        !           733:                return HANDLER_GO_ON;
        !           734:        }
        !           735: 
        !           736:        if (buffer_is_empty(con->physical.path)) {
        !           737:                return HANDLER_GO_ON;
        !           738:        }
        !           739: 
        !           740:        mod_compress_patch_connection(srv, con, p);
        !           741: 
        !           742:        max_fsize = p->conf.compress_max_filesize;
        !           743: 
        !           744:        if (con->conf.log_request_handling) {
        !           745:                log_error_write(srv, __FILE__, __LINE__,  "s",  "-- handling file as static file");
        !           746:        }
        !           747: 
        !           748:        if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
        !           749:                con->http_status = 403;
        !           750: 
        !           751:                log_error_write(srv, __FILE__, __LINE__, "sbsb",
        !           752:                                "not a regular file:", con->uri.path,
        !           753:                                "->", con->physical.path);
        !           754: 
        !           755:                return HANDLER_FINISHED;
        !           756:        }
        !           757: 
        !           758:        /* we only handle regular files */
        !           759: #ifdef HAVE_LSTAT
        !           760:        if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
        !           761:                return HANDLER_GO_ON;
        !           762:        }
        !           763: #endif
        !           764:        if (!S_ISREG(sce->st.st_mode)) {
        !           765:                return HANDLER_GO_ON;
        !           766:        }
        !           767: 
        !           768:        /* don't compress files that are too large as we need to much time to handle them */
        !           769:        if (max_fsize && (sce->st.st_size >> 10) > max_fsize) return HANDLER_GO_ON;
        !           770: 
        !           771:        /* don't try to compress files less than 128 bytes
        !           772:         *
        !           773:         * - extra overhead for compression
        !           774:         * - mmap() fails for st_size = 0 :)
        !           775:         */
        !           776:        if (sce->st.st_size < 128) return HANDLER_GO_ON;
        !           777: 
        !           778:        /* check if mimetype is in compress-config */
        !           779:        content_type = NULL;
        !           780:        if (sce->content_type->ptr) {
        !           781:                char *c;
        !           782:                if ( (c = strchr(sce->content_type->ptr, ';')) != NULL) {
        !           783:                        content_type = srv->tmp_buf;
        !           784:                        buffer_copy_string_len(content_type, sce->content_type->ptr, c - sce->content_type->ptr);
        !           785:                }
        !           786:        }
        !           787: 
        !           788:        for (m = 0; m < p->conf.compress->used; m++) {
        !           789:                data_string *compress_ds = (data_string *)p->conf.compress->data[m];
        !           790: 
        !           791:                if (!compress_ds) {
        !           792:                        log_error_write(srv, __FILE__, __LINE__, "sbb", "evil", con->physical.path, con->uri.path);
        !           793: 
        !           794:                        return HANDLER_GO_ON;
        !           795:                }
        !           796: 
        !           797:                if (buffer_is_equal(compress_ds->value, sce->content_type)
        !           798:                    || (content_type && buffer_is_equal(compress_ds->value, content_type))) {
        !           799:                        /* mimetype found */
        !           800:                        data_string *ds;
        !           801: 
        !           802:                        /* the response might change according to Accept-Encoding */
        !           803:                        response_header_insert(srv, con, CONST_STR_LEN("Vary"), CONST_STR_LEN("Accept-Encoding"));
        !           804: 
        !           805:                        if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Accept-Encoding"))) {
        !           806:                                int accept_encoding = 0;
        !           807:                                char *value = ds->value->ptr;
        !           808:                                int matched_encodings = 0;
        !           809:                                int use_etag = sce->etag != NULL && sce->etag->ptr != NULL;
        !           810: 
        !           811:                                /* get client side support encodings */
        !           812: #ifdef USE_ZLIB
        !           813:                                if (mod_compress_contains_encoding(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
        !           814:                                if (mod_compress_contains_encoding(value, "x-gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_X_GZIP;
        !           815:                                if (mod_compress_contains_encoding(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
        !           816:                                if (mod_compress_contains_encoding(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
        !           817: #endif
        !           818: #ifdef USE_BZ2LIB
        !           819:                                if (mod_compress_contains_encoding(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
        !           820:                                if (mod_compress_contains_encoding(value, "x-bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_X_BZIP2;
        !           821: #endif
        !           822:                                if (mod_compress_contains_encoding(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
        !           823: 
        !           824:                                /* find matching entries */
        !           825:                                matched_encodings = accept_encoding & p->conf.allowed_encodings;
        !           826: 
        !           827:                                if (matched_encodings) {
        !           828:                                        static const char dflt_gzip[] = "gzip";
        !           829:                                        static const char dflt_x_gzip[] = "x-gzip";
        !           830:                                        static const char dflt_deflate[] = "deflate";
        !           831:                                        static const char dflt_bzip2[] = "bzip2";
        !           832:                                        static const char dflt_x_bzip2[] = "x-bzip2";
        !           833: 
        !           834:                                        const char *compression_name = NULL;
        !           835:                                        int compression_type = 0;
        !           836: 
        !           837:                                        mtime = strftime_cache_get(srv, sce->st.st_mtime);
        !           838: 
        !           839:                                        /* try matching original etag of uncompressed version */
        !           840:                                        if (use_etag) {
        !           841:                                                etag_mutate(con->physical.etag, sce->etag);
        !           842:                                                if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
        !           843:                                                        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
        !           844:                                                        response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
        !           845:                                                        response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
        !           846:                                                        return HANDLER_FINISHED;
        !           847:                                                }
        !           848:                                        }
        !           849: 
        !           850:                                        /* select best matching encoding */
        !           851:                                        if (matched_encodings & HTTP_ACCEPT_ENCODING_BZIP2) {
        !           852:                                                compression_type = HTTP_ACCEPT_ENCODING_BZIP2;
        !           853:                                                compression_name = dflt_bzip2;
        !           854:                                        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_X_BZIP2) {
        !           855:                                                compression_type = HTTP_ACCEPT_ENCODING_X_BZIP2;
        !           856:                                                compression_name = dflt_x_bzip2;
        !           857:                                        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_GZIP) {
        !           858:                                                compression_type = HTTP_ACCEPT_ENCODING_GZIP;
        !           859:                                                compression_name = dflt_gzip;
        !           860:                                        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_X_GZIP) {
        !           861:                                                compression_type = HTTP_ACCEPT_ENCODING_X_GZIP;
        !           862:                                                compression_name = dflt_x_gzip;
        !           863:                                        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_DEFLATE) {
        !           864:                                                compression_type = HTTP_ACCEPT_ENCODING_DEFLATE;
        !           865:                                                compression_name = dflt_deflate;
        !           866:                                        }
        !           867: 
        !           868:                                        if (use_etag) {
        !           869:                                                /* try matching etag of compressed version */
        !           870:                                                buffer_copy_string_buffer(srv->tmp_buf, sce->etag);
        !           871:                                                buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("-"));
        !           872:                                                buffer_append_string(srv->tmp_buf, compression_name);
        !           873:                                                etag_mutate(con->physical.etag, srv->tmp_buf);
        !           874:                                        }
        !           875: 
        !           876:                                        if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
        !           877:                                                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
        !           878:                                                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
        !           879:                                                response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
        !           880:                                                if (use_etag) {
        !           881:                                                        response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
        !           882:                                                }
        !           883:                                                return HANDLER_FINISHED;
        !           884:                                        }
        !           885: 
        !           886:                                        /* deflate it */
        !           887:                                        if (use_etag && p->conf.compress_cache_dir->used) {
        !           888:                                                if (0 != deflate_file_to_file(srv, con, p, con->physical.path, sce, compression_type))
        !           889:                                                        return HANDLER_GO_ON;
        !           890:                                        } else {
        !           891:                                                if (0 != deflate_file_to_buffer(srv, con, p, con->physical.path, sce, compression_type))
        !           892:                                                        return HANDLER_GO_ON;
        !           893:                                        }
        !           894:                                        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
        !           895:                                        response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
        !           896:                                        if (use_etag) {
        !           897:                                                response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
        !           898:                                        }
        !           899:                                        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
        !           900:                                        /* let mod_staticfile handle the cached compressed files, physical path was modified */
        !           901:                                        return (use_etag && p->conf.compress_cache_dir->used) ? HANDLER_GO_ON : HANDLER_FINISHED;
        !           902:                                }
        !           903:                        }
        !           904:                }
        !           905:        }
        !           906: 
        !           907:        return HANDLER_GO_ON;
        !           908: }
        !           909: 
        !           910: int mod_compress_plugin_init(plugin *p);
        !           911: int mod_compress_plugin_init(plugin *p) {
        !           912:        p->version     = LIGHTTPD_VERSION_ID;
        !           913:        p->name        = buffer_init_string("compress");
        !           914: 
        !           915:        p->init        = mod_compress_init;
        !           916:        p->set_defaults = mod_compress_setdefaults;
        !           917:        p->handle_subrequest_start  = mod_compress_physical;
        !           918:        p->cleanup     = mod_compress_free;
        !           919: 
        !           920:        p->data        = NULL;
        !           921: 
        !           922:        return 0;
        !           923: }

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