diff options
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | docs/manual/mod/mod_deflate.xml | 28 | ||||
-rw-r--r-- | modules/filters/mod_deflate.c | 275 |
3 files changed, 281 insertions, 26 deletions
@@ -2,6 +2,10 @@ Changes with Apache 2.1.0-dev [Remove entries to the current 2.0 section below, when backported] + *) mod_deflate: New option for DEFLATE output file (force-gzip), + new output filter 'INFLATE' for uncompressing responses. + [Nick Kew <Nick at WebThing dot com>, Ian Holsman] + *) Added new module mod_version, which provides version dependent configuration containers. [André Malo] diff --git a/docs/manual/mod/mod_deflate.xml b/docs/manual/mod/mod_deflate.xml index f81f69a917..6612187caa 100644 --- a/docs/manual/mod/mod_deflate.xml +++ b/docs/manual/mod/mod_deflate.xml @@ -147,8 +147,36 @@ client</description> The <code>DEFLATE</code> filter is always inserted after RESOURCE filters like PHP or SSI. It never touches internal subrequests. </note> + <note><title>Note</title> + There is a environment variable <code>force-gzip</code>, + set via <directive module="core">SetEnv</directive>, which + will ignore the accept-encoding setting of your browser and will + send compressed output. + </note> + </section> + <section id="inflate"><title>Output Decompression</title> + <p>The <module>mod_deflate</module> module also provides a filter for + inflating/uncompressing a gzip compressed response body. In order to activate + this feature you have to insert the <code>INFLATE</code> filter into + the outputfilter chain using <directive module="core" + >SetOutputFilter</directive> or <directive module="mod_mime" + >AddOutputFilter</directive>, for example:</p> + <example> + <Location /dav-area><br /> + <indent> + ProxyPass http://example.com/<br /> + SetOutputFilter INFLATE<br /> + </indent> + </Location> + </example> + + <p>This Example will uncompress gzip'ed output from example.com, so other + filters can do further processing with it. + </p> + + </section> <section id="input"><title>Input Decompression</title> <p>The <module>mod_deflate</module> module also provides a filter for decompressing a gzip compressed request body . In order to activate diff --git a/modules/filters/mod_deflate.c b/modules/filters/mod_deflate.c index f90e85b7e4..dc0bd5f812 100644 --- a/modules/filters/mod_deflate.c +++ b/modules/filters/mod_deflate.c @@ -16,8 +16,7 @@ /* * mod_deflate.c: Perform deflate transfer-encoding on the fly * - * Written by Ian Holsman - * + * Written by Ian Holsman, Justin Erenkrantz, and Nick Kew */ /* @@ -257,7 +256,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f, */ if (!ctx) { char *buf, *token; - const char *encoding, *accepts; + const char *encoding; /* only work on main request/no subrequests */ if (r->main) { @@ -320,7 +319,7 @@ static apr_status_t deflate_out_filter(ap_filter_t *f, strcmp(token, "8bit") && strcmp(token, "binary")) { ap_remove_output_filter(f); - return ap_pass_brigade(f->next, bb); + return ap_pass_brigade(f->next, bb); } /* Otherwise, skip token */ @@ -337,34 +336,40 @@ static apr_status_t deflate_out_filter(ap_filter_t *f, */ apr_table_setn(r->headers_out, "Vary", "Accept-Encoding"); - /* if they don't have the line, then they can't play */ - accepts = apr_table_get(r->headers_in, "Accept-Encoding"); - if (accepts == NULL) { - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, bb); - } - token = ap_get_token(r->pool, &accepts, 0); - while (token && token[0] && strcasecmp(token, "gzip")) { - /* skip parameters, XXX: ;q=foo evaluation? */ - while (*accepts == ';') { - ++accepts; - token = ap_get_token(r->pool, &accepts, 1); + /* force-gzip will just force it out regardless if the browser + * can actually do anything with it. + */ + if (apr_table_get(r->subprocess_env, "force-gzip") != NULL) { + const char *accepts; + /* if they don't have the line, then they can't play */ + accepts = apr_table_get(r->headers_in, "Accept-Encoding"); + if (accepts == NULL) { + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); } - /* retrieve next token */ - if (*accepts == ',') { - ++accepts; + token = ap_get_token(r->pool, &accepts, 0); + while (token && token[0] && strcasecmp(token, "gzip")) { + /* skip parameters, XXX: ;q=foo evaluation? */ + while (*accepts == ';') { + ++accepts; + token = ap_get_token(r->pool, &accepts, 1); + } + + /* retrieve next token */ + if (*accepts == ',') { + ++accepts; + } + token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL; } - token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL; - } - /* No acceptable token found. */ - if (token == NULL || token[0] == '\0') { - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, bb); + /* No acceptable token found. */ + if (token == NULL || token[0] == '\0') { + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); + } } - /* We're cool with filtering this. */ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx)); ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc); @@ -840,10 +845,228 @@ static apr_status_t deflate_in_filter(ap_filter_t *f, return APR_SUCCESS; } + +/* Filter to inflate for a content-transforming proxy. */ +static apr_status_t inflate_out_filter(ap_filter_t *f, + apr_bucket_brigade *bb) +{ + /* have we read the zlib header in yet? assume we have in a previous pass */ + int deflate_init = 1; + apr_bucket *bkt; + request_rec *r = f->r; + deflate_ctx *ctx = f->ctx; + int zRC; + apr_status_t rv; + deflate_filter_config *c; + + c = ap_get_module_config(r->server->module_config, &deflate_module); + + if (!ctx) { + int found = 0; + char *token, deflate_hdr[10]; + const char *encoding; + apr_size_t len; + + /* only work on main request/no subrequests */ + if (r->main) { + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); + } + + /* Let's see what our current Content-Encoding is. + * If gzip is present, don't gzip again. (We could, but let's not.) + */ + encoding = apr_table_get(r->headers_out, "Content-Encoding"); + if (encoding) { + const char *tmp = encoding; + + token = ap_get_token(r->pool, &tmp, 0); + while (token && token[0]) { + if (!strcasecmp(token, "gzip")) { + found = 1; + break; + } + /* Otherwise, skip token */ + tmp++; + token = ap_get_token(r->pool, &tmp, 0); + } + } + + if (found == 0) { + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); + } + + f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); + ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc); + ctx->buffer = apr_palloc(r->pool, c->bufferSize); + + + zRC = inflateInit2(&ctx->stream, c->windowSize); + + if (zRC != Z_OK) { + f->ctx = NULL; + inflateEnd(&ctx->stream); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "unable to init Zlib: " + "inflateInit2 returned %d: URL %s", + zRC, r->uri); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); + } + + /* initialize deflate output buffer */ + ctx->stream.next_out = ctx->buffer; + ctx->stream.avail_out = c->bufferSize; + + deflate_init = 0; + } + + for (bkt = APR_BRIGADE_FIRST(bb); + bkt != APR_BRIGADE_SENTINEL(bb); + bkt = APR_BUCKET_NEXT(bkt)) + { + const char *data; + apr_size_t len; + + /* If we actually see the EOS, that means we screwed up! */ + if (APR_BUCKET_IS_EOS(bkt)) { + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + + if (APR_BUCKET_IS_FLUSH(bkt)) { + apr_bucket *tmp_heap; + zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH); + if (zRC != Z_OK) { + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + + ctx->stream.next_out = ctx->buffer; + len = c->bufferSize - ctx->stream.avail_out; + + ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len); + tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len, + NULL, f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap); + ctx->stream.avail_out = c->bufferSize; + + /* Move everything to the returning brigade. */ + APR_BUCKET_REMOVE(bkt); + break; + } + + /* read */ + apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ); + + /* first bucket contains zlib header */ + if ( ! deflate_init++ ) { + if ( len < 10 ) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Insufficient data for inflate"); + return APR_EGENERAL ; + } + else { + if (data[0] != deflate_magic[0] || + data[1] != deflate_magic[1]) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "deflate: bad header"); + return APR_EGENERAL ; + } + data += 10 ; + len -= 10 ; + } + } + + /* pass through zlib inflate. */ + ctx->stream.next_in = (unsigned char *)data; + ctx->stream.avail_in = len; + + zRC = Z_OK; + + while (ctx->stream.avail_in != 0) { + if (ctx->stream.avail_out == 0) { + apr_bucket *tmp_heap; + ctx->stream.next_out = ctx->buffer; + len = c->bufferSize - ctx->stream.avail_out; + + ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len); + tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len, + NULL, f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap); + ctx->stream.avail_out = c->bufferSize; + } + + zRC = inflate(&ctx->stream, Z_NO_FLUSH); + + if (zRC == Z_STREAM_END) { + break; + } + + if (zRC != Z_OK) { + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + } + if (zRC == Z_STREAM_END) { + apr_bucket *tmp_heap, *eos; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "Zlib: Inflated %ld to %ld : URL %s", + ctx->stream.total_in, ctx->stream.total_out, + r->uri); + + len = c->bufferSize - ctx->stream.avail_out; + + ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len); + tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len, + NULL, f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap); + ctx->stream.avail_out = c->bufferSize; + + /* Is the remaining 8 bytes already in the avail stream? */ + if (ctx->stream.avail_in >= 8) { + unsigned long compCRC, compLen; + compCRC = getLong(ctx->stream.next_in); + if (ctx->crc != compCRC) { + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + ctx->stream.next_in += 4; + compLen = getLong(ctx->stream.next_in); + if (ctx->stream.total_out != compLen) { + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + } + else { + /* FIXME: We need to grab the 8 verification bytes + * from the wire! */ + inflateEnd(&ctx->stream); + return APR_EGENERAL; + } + + inflateEnd(&ctx->stream); + + eos = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos); + break; + } + + } + + rv = ap_pass_brigade(f->next, ctx->proc_bb); + apr_brigade_cleanup(ctx->proc_bb); + return rv ; +} + static void register_hooks(apr_pool_t *p) { ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL, AP_FTYPE_CONTENT_SET); + ap_register_output_filter("INFLATE", inflate_out_filter, NULL, + AP_FTYPE_RESOURCE-1); ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL, AP_FTYPE_CONTENT_SET); } |