Annotation of embedaddon/lighttpd/src/mod_compress.c, revision 1.1.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>