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