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>