File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src / mod_compress.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 20:20:06 2014 UTC (10 years ago) by misho
Branches: lighttpd, MAIN
CVS tags: v1_4_35p0, v1_4_35, HEAD
lighttpd 1.4.35

    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 <assert.h>
   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: 
  165: 	p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
  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;
  416: 	int ret;
  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: 
  529: 	ret = -1;
  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;
  862: 					} else {
  863: 						force_assert(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>