1 /* Copyright 2000-2004 Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 * mod_deflate.c: Perform deflate transfer-encoding on the fly
19 * Written by Ian Holsman
24 * Portions of this software are based upon public domain software
25 * (zlib functions gz_open and gzwrite)
29 #include "http_config.h"
31 #include "apr_strings.h"
32 #include "apr_general.h"
33 #include "util_filter.h"
34 #include "apr_buckets.h"
35 #include "http_request.h"
36 #define APR_WANT_STRFUNC
44 /* As part of the encoding process, we must send what our OS_CODE is
45 * (or so it seems based on what I can tell of how gzip encoding works).
47 * zutil.h is not always included with zlib distributions (it is a private
48 * header), so this is straight from zlib 1.1.3's zutil.h.
54 #ifdef WIN32 /* Window 95 & Windows NT */
58 #if defined(VAXC) || defined(VMS)
66 #if defined(ATARI) || defined(atarist)
70 #if defined(MACOS) || defined(TARGET_OS_MAC)
74 #ifdef __50SERIES /* Prime/PRIMOS */
83 #define OS_CODE 0x03 /* assume Unix */
87 static const char deflateFilterName[] = "DEFLATE";
88 module AP_MODULE_DECLARE_DATA deflate_module;
90 typedef struct deflate_filter_config_t
95 apr_size_t bufferSize;
96 char *note_ratio_name;
97 char *note_input_name;
98 char *note_output_name;
99 } deflate_filter_config;
101 /* windowsize is negative to suppress Zlib header */
102 #define DEFAULT_COMPRESSION Z_DEFAULT_COMPRESSION
103 #define DEFAULT_WINDOWSIZE -15
104 #define DEFAULT_MEMLEVEL 9
105 #define DEFAULT_BUFFERSIZE 8096
107 /* Outputs a long in LSB order to the given file
108 * only the bottom 4 bits are required for the deflate file format.
110 static void putLong(unsigned char *string, unsigned long x)
112 string[0] = (unsigned char)(x & 0xff);
113 string[1] = (unsigned char)((x & 0xff00) >> 8);
114 string[2] = (unsigned char)((x & 0xff0000) >> 16);
115 string[3] = (unsigned char)((x & 0xff000000) >> 24);
118 /* Inputs a string and returns a long.
120 static unsigned long getLong(unsigned char *string)
122 return ((unsigned long)string[0])
123 | (((unsigned long)string[1]) << 8)
124 | (((unsigned long)string[2]) << 16)
125 | (((unsigned long)string[3]) << 24);
128 static void *create_deflate_server_config(apr_pool_t *p, server_rec *s)
130 deflate_filter_config *c = apr_pcalloc(p, sizeof *c);
132 c->memlevel = DEFAULT_MEMLEVEL;
133 c->windowSize = DEFAULT_WINDOWSIZE;
134 c->bufferSize = DEFAULT_BUFFERSIZE;
135 c->compressionlevel = DEFAULT_COMPRESSION;
140 static const char *deflate_set_window_size(cmd_parms *cmd, void *dummy,
143 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
150 return "DeflateWindowSize must be between 1 and 15";
152 c->windowSize = i * -1;
157 static const char *deflate_set_buffer_size(cmd_parms *cmd, void *dummy,
160 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
165 return "DeflateBufferSize should be positive";
168 c->bufferSize = (apr_size_t)n;
172 static const char *deflate_set_note(cmd_parms *cmd, void *dummy,
173 const char *arg1, const char *arg2)
175 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
179 c->note_ratio_name = apr_pstrdup(cmd->pool, arg1);
181 else if (!strcasecmp(arg1, "ratio")) {
182 c->note_ratio_name = apr_pstrdup(cmd->pool, arg2);
184 else if (!strcasecmp(arg1, "input")) {
185 c->note_input_name = apr_pstrdup(cmd->pool, arg2);
187 else if (!strcasecmp(arg1, "output")) {
188 c->note_output_name = apr_pstrdup(cmd->pool, arg2);
191 return apr_psprintf(cmd->pool, "Unknown note type %s", arg1);
197 static const char *deflate_set_memlevel(cmd_parms *cmd, void *dummy,
200 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
207 return "DeflateMemLevel must be between 1 and 9";
214 static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
217 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
224 return "Compression Level must be between 1 and 9";
226 c->compressionlevel = i;
232 static char deflate_magic[2] = { '\037', '\213' };
234 typedef struct deflate_ctx_t
237 unsigned char *buffer;
239 apr_bucket_brigade *bb, *proc_bb;
242 static apr_status_t deflate_out_filter(ap_filter_t *f,
243 apr_bucket_brigade *bb)
246 request_rec *r = f->r;
247 deflate_ctx *ctx = f->ctx;
249 deflate_filter_config *c = ap_get_module_config(r->server->module_config,
252 /* If we don't have a context, we need to ensure that it is okay to send
253 * the deflated content. If we have a context, that means we've done
254 * this before and we liked it.
255 * This could be not so nice if we always fail. But, if we succeed,
256 * we're in better shape.
260 const char *encoding, *accepts;
262 /* only work on main request/no subrequests */
264 ap_remove_output_filter(f);
265 return ap_pass_brigade(f->next, bb);
268 /* some browsers might have problems, so set no-gzip
269 * (with browsermatch) for them
271 if (apr_table_get(r->subprocess_env, "no-gzip")) {
272 ap_remove_output_filter(f);
273 return ap_pass_brigade(f->next, bb);
276 /* Some browsers might have problems with content types
277 * other than text/html, so set gzip-only-text/html
278 * (with browsermatch) for them
280 if (r->content_type == NULL
281 || strncmp(r->content_type, "text/html", 9)) {
282 const char *env_value = apr_table_get(r->subprocess_env,
283 "gzip-only-text/html");
284 if ( env_value && (strcmp(env_value,"1") == 0) ) {
285 ap_remove_output_filter(f);
286 return ap_pass_brigade(f->next, bb);
290 /* Let's see what our current Content-Encoding is.
291 * If it's already encoded, don't compress again.
292 * (We could, but let's not.)
294 encoding = apr_table_get(r->headers_out, "Content-Encoding");
298 err_enc = apr_table_get(r->err_headers_out, "Content-Encoding");
300 encoding = apr_pstrcat(r->pool, encoding, ",", err_enc, NULL);
304 encoding = apr_table_get(r->err_headers_out, "Content-Encoding");
307 if (r->content_encoding) {
308 encoding = encoding ? apr_pstrcat(r->pool, encoding, ",",
309 r->content_encoding, NULL)
310 : r->content_encoding;
314 const char *tmp = encoding;
316 token = ap_get_token(r->pool, &tmp, 0);
317 while (token && *token) {
318 /* stolen from mod_negotiation: */
319 if (strcmp(token, "identity") && strcmp(token, "7bit") &&
320 strcmp(token, "8bit") && strcmp(token, "binary")) {
322 ap_remove_output_filter(f);
323 return ap_pass_brigade(f->next, bb);
326 /* Otherwise, skip token */
330 token = (*tmp) ? ap_get_token(r->pool, &tmp, 0) : NULL;
334 /* Even if we don't accept this request based on it not having
335 * the Accept-Encoding, we need to note that we were looking
336 * for this header and downstream proxies should be aware of that.
338 apr_table_setn(r->headers_out, "Vary", "Accept-Encoding");
340 /* if they don't have the line, then they can't play */
341 accepts = apr_table_get(r->headers_in, "Accept-Encoding");
342 if (accepts == NULL) {
343 ap_remove_output_filter(f);
344 return ap_pass_brigade(f->next, bb);
347 token = ap_get_token(r->pool, &accepts, 0);
348 while (token && token[0] && strcasecmp(token, "gzip")) {
349 /* skip parameters, XXX: ;q=foo evaluation? */
350 while (*accepts == ';') {
352 token = ap_get_token(r->pool, &accepts, 1);
355 /* retrieve next token */
356 if (*accepts == ',') {
359 token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
362 /* No acceptable token found. */
363 if (token == NULL || token[0] == '\0') {
364 ap_remove_output_filter(f);
365 return ap_pass_brigade(f->next, bb);
368 /* We're cool with filtering this. */
369 ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
370 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
371 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
373 zRC = deflateInit2(&ctx->stream, c->compressionlevel, Z_DEFLATED,
374 c->windowSize, c->memlevel,
379 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
380 "unable to init Zlib: "
381 "deflateInit2 returned %d: URL %s",
383 return ap_pass_brigade(f->next, bb);
386 /* RFC 1952 Section 2.3 dictates the gzip header:
388 * +---+---+---+---+---+---+---+---+---+---+
389 * |ID1|ID2|CM |FLG| MTIME |XFL|OS |
390 * +---+---+---+---+---+---+---+---+---+---+
392 * If we wish to populate in MTIME (as hinted in RFC 1952), do:
393 * putLong(date_array, apr_time_now() / APR_USEC_PER_SEC);
394 * where date_array is a char[4] and then print date_array in the
395 * MTIME position. WARNING: ENDIANNESS ISSUE HERE.
397 buf = apr_psprintf(r->pool, "%c%c%c%c%c%c%c%c%c%c", deflate_magic[0],
398 deflate_magic[1], Z_DEFLATED, 0 /* flags */,
399 0, 0, 0, 0 /* 4 chars for mtime */,
400 0 /* xflags */, OS_CODE);
401 e = apr_bucket_pool_create(buf, 10, r->pool, f->c->bucket_alloc);
402 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
404 /* If the entire Content-Encoding is "identity", we can replace it. */
405 if (!encoding || !strcasecmp(encoding, "identity")) {
406 apr_table_setn(r->headers_out, "Content-Encoding", "gzip");
409 apr_table_mergen(r->headers_out, "Content-Encoding", "gzip");
411 apr_table_unset(r->headers_out, "Content-Length");
413 /* initialize deflate output buffer */
414 ctx->stream.next_out = ctx->buffer;
415 ctx->stream.avail_out = c->bufferSize;
418 for (e = APR_BRIGADE_FIRST(bb);
419 e != APR_BRIGADE_SENTINEL(bb);
420 e = APR_BUCKET_NEXT(e))
428 if (APR_BUCKET_IS_EOS(e)) {
430 unsigned int deflate_len;
432 ctx->stream.avail_in = 0; /* should be zero already anyway */
434 deflate_len = c->bufferSize - ctx->stream.avail_out;
436 if (deflate_len != 0) {
437 b = apr_bucket_heap_create((char *)ctx->buffer,
440 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
441 ctx->stream.next_out = ctx->buffer;
442 ctx->stream.avail_out = c->bufferSize;
449 zRC = deflate(&ctx->stream, Z_FINISH);
451 if (deflate_len == 0 && zRC == Z_BUF_ERROR) {
455 done = (ctx->stream.avail_out != 0 || zRC == Z_STREAM_END);
457 if (zRC != Z_OK && zRC != Z_STREAM_END) {
462 buf = apr_palloc(r->pool, 8);
463 putLong((unsigned char *)&buf[0], ctx->crc);
464 putLong((unsigned char *)&buf[4], ctx->stream.total_in);
466 b = apr_bucket_pool_create(buf, 8, r->pool, f->c->bucket_alloc);
467 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
468 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
469 "Zlib: Compressed %ld to %ld : URL %s",
470 ctx->stream.total_in, ctx->stream.total_out, r->uri);
472 /* leave notes for logging */
473 if (c->note_input_name) {
474 apr_table_setn(r->notes, c->note_input_name,
475 (ctx->stream.total_in > 0)
476 ? apr_off_t_toa(r->pool,
477 ctx->stream.total_in)
481 if (c->note_output_name) {
482 apr_table_setn(r->notes, c->note_output_name,
483 (ctx->stream.total_in > 0)
484 ? apr_off_t_toa(r->pool,
485 ctx->stream.total_out)
489 if (c->note_ratio_name) {
490 apr_table_setn(r->notes, c->note_ratio_name,
491 (ctx->stream.total_in > 0)
493 (int)(ctx->stream.total_out
495 / ctx->stream.total_in))
499 deflateEnd(&ctx->stream);
501 /* Remove EOS from the old list, and insert into the new. */
502 APR_BUCKET_REMOVE(e);
503 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
505 /* Okay, we've seen the EOS.
506 * Time to pass it along down the chain.
508 return ap_pass_brigade(f->next, ctx->bb);
511 if (APR_BUCKET_IS_FLUSH(e)) {
514 if (ctx->stream.avail_in > 0) {
515 zRC = deflate(&(ctx->stream), Z_SYNC_FLUSH);
521 ctx->stream.next_out = ctx->buffer;
522 len = c->bufferSize - ctx->stream.avail_out;
524 b = apr_bucket_heap_create((char *)ctx->buffer, len,
525 NULL, f->c->bucket_alloc);
526 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
527 ctx->stream.avail_out = c->bufferSize;
529 bkt = apr_bucket_flush_create(f->c->bucket_alloc);
530 APR_BRIGADE_INSERT_TAIL(ctx->bb, bkt);
531 rv = ap_pass_brigade(f->next, ctx->bb);
532 if (rv != APR_SUCCESS) {
539 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
541 /* This crc32 function is from zlib. */
542 ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
545 ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
546 * but we'll just have to
548 ctx->stream.avail_in = len;
550 while (ctx->stream.avail_in != 0) {
551 if (ctx->stream.avail_out == 0) {
554 ctx->stream.next_out = ctx->buffer;
555 len = c->bufferSize - ctx->stream.avail_out;
557 b = apr_bucket_heap_create((char *)ctx->buffer, len,
558 NULL, f->c->bucket_alloc);
559 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
560 ctx->stream.avail_out = c->bufferSize;
561 /* Send what we have right now to the next filter. */
562 rv = ap_pass_brigade(f->next, ctx->bb);
563 if (rv != APR_SUCCESS) {
568 zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
575 apr_brigade_cleanup(bb);
579 /* This is the deflate input filter (inflates). */
580 static apr_status_t deflate_in_filter(ap_filter_t *f,
581 apr_bucket_brigade *bb,
582 ap_input_mode_t mode,
583 apr_read_type_e block,
587 request_rec *r = f->r;
588 deflate_ctx *ctx = f->ctx;
591 deflate_filter_config *c;
593 /* just get out of the way of things we don't want. */
594 if (mode != AP_MODE_READBYTES) {
595 return ap_get_brigade(f->next, bb, mode, block, readbytes);
598 c = ap_get_module_config(r->server->module_config, &deflate_module);
602 char *token, deflate_hdr[10];
603 const char *encoding;
606 /* only work on main request/no subrequests */
608 ap_remove_input_filter(f);
609 return ap_get_brigade(f->next, bb, mode, block, readbytes);
612 /* Let's see what our current Content-Encoding is.
613 * If gzip is present, don't gzip again. (We could, but let's not.)
615 encoding = apr_table_get(r->headers_in, "Content-Encoding");
617 const char *tmp = encoding;
619 token = ap_get_token(r->pool, &tmp, 0);
620 while (token && token[0]) {
621 if (!strcasecmp(token, "gzip")) {
625 /* Otherwise, skip token */
627 token = ap_get_token(r->pool, &tmp, 0);
632 ap_remove_input_filter(f);
633 return ap_get_brigade(f->next, bb, mode, block, readbytes);
636 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
637 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
638 ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
639 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
641 rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
642 if (rv != APR_SUCCESS) {
647 rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len);
648 if (rv != APR_SUCCESS) {
652 /* We didn't get the magic bytes. */
654 deflate_hdr[0] != deflate_magic[0] ||
655 deflate_hdr[1] != deflate_magic[1]) {
659 /* We can't handle flags for now. */
660 if (deflate_hdr[3] != 0) {
664 zRC = inflateInit2(&ctx->stream, c->windowSize);
668 inflateEnd(&ctx->stream);
669 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
670 "unable to init Zlib: "
671 "inflateInit2 returned %d: URL %s",
673 ap_remove_input_filter(f);
674 return ap_get_brigade(f->next, bb, mode, block, readbytes);
677 /* initialize deflate output buffer */
678 ctx->stream.next_out = ctx->buffer;
679 ctx->stream.avail_out = c->bufferSize;
681 apr_brigade_cleanup(ctx->bb);
684 if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
685 rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
687 if (rv != APR_SUCCESS) {
688 /* What about APR_EAGAIN errors? */
689 inflateEnd(&ctx->stream);
693 for (bkt = APR_BRIGADE_FIRST(ctx->bb);
694 bkt != APR_BRIGADE_SENTINEL(ctx->bb);
695 bkt = APR_BUCKET_NEXT(bkt))
700 /* If we actually see the EOS, that means we screwed up! */
701 if (APR_BUCKET_IS_EOS(bkt)) {
702 inflateEnd(&ctx->stream);
706 if (APR_BUCKET_IS_FLUSH(bkt)) {
707 apr_bucket *tmp_heap;
708 zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
710 inflateEnd(&ctx->stream);
714 ctx->stream.next_out = ctx->buffer;
715 len = c->bufferSize - ctx->stream.avail_out;
717 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
718 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
719 NULL, f->c->bucket_alloc);
720 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
721 ctx->stream.avail_out = c->bufferSize;
723 /* Move everything to the returning brigade. */
724 APR_BUCKET_REMOVE(bkt);
725 APR_BRIGADE_CONCAT(bb, ctx->bb);
730 apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
732 /* pass through zlib inflate. */
733 ctx->stream.next_in = (unsigned char *)data;
734 ctx->stream.avail_in = len;
738 while (ctx->stream.avail_in != 0) {
739 if (ctx->stream.avail_out == 0) {
740 apr_bucket *tmp_heap;
741 ctx->stream.next_out = ctx->buffer;
742 len = c->bufferSize - ctx->stream.avail_out;
744 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
745 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
746 NULL, f->c->bucket_alloc);
747 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
748 ctx->stream.avail_out = c->bufferSize;
751 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
753 if (zRC == Z_STREAM_END) {
758 inflateEnd(&ctx->stream);
762 if (zRC == Z_STREAM_END) {
763 apr_bucket *tmp_heap, *eos;
765 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
766 "Zlib: Inflated %ld to %ld : URL %s",
767 ctx->stream.total_in, ctx->stream.total_out,
770 len = c->bufferSize - ctx->stream.avail_out;
772 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
773 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
774 NULL, f->c->bucket_alloc);
775 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
776 ctx->stream.avail_out = c->bufferSize;
778 /* Is the remaining 8 bytes already in the avail stream? */
779 if (ctx->stream.avail_in >= 8) {
780 unsigned long compCRC, compLen;
781 compCRC = getLong(ctx->stream.next_in);
782 if (ctx->crc != compCRC) {
783 inflateEnd(&ctx->stream);
786 ctx->stream.next_in += 4;
787 compLen = getLong(ctx->stream.next_in);
788 if (ctx->stream.total_out != compLen) {
789 inflateEnd(&ctx->stream);
794 /* FIXME: We need to grab the 8 verification bytes
796 inflateEnd(&ctx->stream);
800 inflateEnd(&ctx->stream);
802 eos = apr_bucket_eos_create(f->c->bucket_alloc);
803 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos);
808 apr_brigade_cleanup(ctx->bb);
811 /* If we are about to return nothing for a 'blocking' read and we have
812 * some data in our zlib buffer, flush it out so we can return something.
814 if (block == APR_BLOCK_READ &&
815 APR_BRIGADE_EMPTY(ctx->proc_bb) &&
816 ctx->stream.avail_out < c->bufferSize) {
817 apr_bucket *tmp_heap;
819 ctx->stream.next_out = ctx->buffer;
820 len = c->bufferSize - ctx->stream.avail_out;
822 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
823 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
824 NULL, f->c->bucket_alloc);
825 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
826 ctx->stream.avail_out = c->bufferSize;
829 if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
830 apr_bucket_brigade *newbb;
832 /* May return APR_INCOMPLETE which is fine by us. */
833 apr_brigade_partition(ctx->proc_bb, readbytes, &bkt);
835 newbb = apr_brigade_split(ctx->proc_bb, bkt);
836 APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
837 APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
843 static void register_hooks(apr_pool_t *p)
845 ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
846 AP_FTYPE_CONTENT_SET);
847 ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
848 AP_FTYPE_CONTENT_SET);
851 static const command_rec deflate_filter_cmds[] = {
852 AP_INIT_TAKE12("DeflateFilterNote", deflate_set_note, NULL, RSRC_CONF,
853 "Set a note to report on compression ratio"),
854 AP_INIT_TAKE1("DeflateWindowSize", deflate_set_window_size, NULL,
855 RSRC_CONF, "Set the Deflate window size (1-15)"),
856 AP_INIT_TAKE1("DeflateBufferSize", deflate_set_buffer_size, NULL, RSRC_CONF,
857 "Set the Deflate Buffer Size"),
858 AP_INIT_TAKE1("DeflateMemLevel", deflate_set_memlevel, NULL, RSRC_CONF,
859 "Set the Deflate Memory Level (1-9)"),
860 AP_INIT_TAKE1("DeflateCompressionLevel", deflate_set_compressionlevel, NULL, RSRC_CONF,
861 "Set the Deflate Compression Level (1-9)"),
865 module AP_MODULE_DECLARE_DATA deflate_module = {
866 STANDARD20_MODULE_STUFF,
867 NULL, /* dir config creater */
868 NULL, /* dir merger --- default is to override */
869 create_deflate_server_config, /* server config */
870 NULL, /* merge server config */
871 deflate_filter_cmds, /* command table */
872 register_hooks /* register hooks */