/[Apache-SVN]/httpd/httpd/trunk/modules/filters/mod_deflate.c
ViewVC logotype

Contents of /httpd/httpd/trunk/modules/filters/mod_deflate.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 563230 - (show annotations)
Mon Aug 6 19:24:10 2007 UTC (2 years, 3 months ago) by rpluem
File MIME type: text/plain
File size: 47368 byte(s)
* Also unset Content-MD5 in the deflate_out_filter
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /*
18 * mod_deflate.c: Perform deflate content-encoding on the fly
19 *
20 * Written by Ian Holsman, Justin Erenkrantz, and Nick Kew
21 */
22
23 /*
24 * Portions of this software are based upon zlib code by Jean-loup Gailly
25 * (zlib functions gz_open and gzwrite, check_header)
26 */
27
28 /* zlib flags */
29 #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
30 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
31 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
32 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
33 #define COMMENT 0x10 /* bit 4 set: file comment present */
34 #define RESERVED 0xE0 /* bits 5..7: reserved */
35
36
37 #include "httpd.h"
38 #include "http_config.h"
39 #include "http_log.h"
40 #include "apr_lib.h"
41 #include "apr_strings.h"
42 #include "apr_general.h"
43 #include "util_filter.h"
44 #include "apr_buckets.h"
45 #include "http_request.h"
46 #define APR_WANT_STRFUNC
47 #include "apr_want.h"
48
49 #include "zlib.h"
50
51 static const char deflateFilterName[] = "DEFLATE";
52 module AP_MODULE_DECLARE_DATA deflate_module;
53
54 typedef struct deflate_filter_config_t
55 {
56 int windowSize;
57 int memlevel;
58 int compressionlevel;
59 apr_size_t bufferSize;
60 char *note_ratio_name;
61 char *note_input_name;
62 char *note_output_name;
63 } deflate_filter_config;
64
65 /* RFC 1952 Section 2.3 defines the gzip header:
66 *
67 * +---+---+---+---+---+---+---+---+---+---+
68 * |ID1|ID2|CM |FLG| MTIME |XFL|OS |
69 * +---+---+---+---+---+---+---+---+---+---+
70 */
71 static const char gzip_header[10] =
72 { '\037', '\213', Z_DEFLATED, 0,
73 0, 0, 0, 0, /* mtime */
74 0, 0x03 /* Unix OS_CODE */
75 };
76
77 /* magic header */
78 static const char deflate_magic[2] = { '\037', '\213' };
79
80 /* windowsize is negative to suppress Zlib header */
81 #define DEFAULT_COMPRESSION Z_DEFAULT_COMPRESSION
82 #define DEFAULT_WINDOWSIZE -15
83 #define DEFAULT_MEMLEVEL 9
84 #define DEFAULT_BUFFERSIZE 8096
85
86
87 /* Check whether a request is gzipped, so we can un-gzip it.
88 * If a request has multiple encodings, we need the gzip
89 * to be the outermost non-identity encoding.
90 */
91 static int check_gzip(apr_pool_t *pool, apr_table_t *hdrs)
92 {
93 int found = 0;
94 const char *encoding = apr_table_get(hdrs, "Content-Encoding");
95 if (encoding && *encoding) {
96
97 /* check the usual/simple case first */
98 if (!strcasecmp(encoding, "gzip")
99 || !strcasecmp(encoding, "x-gzip")) {
100 found = 1;
101 apr_table_unset(hdrs, "Content-Encoding");
102 }
103 else if (ap_strchr_c(encoding, ',') != NULL) {
104 /* If the outermost encoding isn't gzip, there's nowt
105 * we can do. So only check the last non-identity token
106 */
107 char *new_encoding = apr_pstrdup(pool, encoding);
108 char *ptr;
109 for(;;) {
110 char *token = ap_strrchr(new_encoding, ',');
111 if (!token) { /* gzip:identity or other:identity */
112 if (!strcasecmp(new_encoding, "gzip")
113 || !strcasecmp(new_encoding, "x-gzip")) {
114 apr_table_unset(hdrs, "Content-Encoding");
115 found = 1;
116 }
117 break; /* seen all tokens */
118 }
119 for (ptr=token+1; apr_isspace(*ptr); ++ptr);
120 if (!strcasecmp(ptr, "gzip")
121 || !strcasecmp(ptr, "x-gzip")) {
122 *token = '\0';
123 apr_table_setn(hdrs, "Content-Encoding", new_encoding);
124 found = 1;
125 }
126 else if (!ptr[0] || !strcasecmp(ptr, "identity")) {
127 *token = '\0';
128 continue; /* strip the token and find the next one */
129 }
130 break; /* found a non-identity token */
131 }
132 }
133 }
134 return found;
135 }
136
137 /* Outputs a long in LSB order to the given file
138 * only the bottom 4 bits are required for the deflate file format.
139 */
140 static void putLong(unsigned char *string, unsigned long x)
141 {
142 string[0] = (unsigned char)(x & 0xff);
143 string[1] = (unsigned char)((x & 0xff00) >> 8);
144 string[2] = (unsigned char)((x & 0xff0000) >> 16);
145 string[3] = (unsigned char)((x & 0xff000000) >> 24);
146 }
147
148 /* Inputs a string and returns a long.
149 */
150 static unsigned long getLong(unsigned char *string)
151 {
152 return ((unsigned long)string[0])
153 | (((unsigned long)string[1]) << 8)
154 | (((unsigned long)string[2]) << 16)
155 | (((unsigned long)string[3]) << 24);
156 }
157
158 static void *create_deflate_server_config(apr_pool_t *p, server_rec *s)
159 {
160 deflate_filter_config *c = apr_pcalloc(p, sizeof *c);
161
162 c->memlevel = DEFAULT_MEMLEVEL;
163 c->windowSize = DEFAULT_WINDOWSIZE;
164 c->bufferSize = DEFAULT_BUFFERSIZE;
165 c->compressionlevel = DEFAULT_COMPRESSION;
166
167 return c;
168 }
169
170 static const char *deflate_set_window_size(cmd_parms *cmd, void *dummy,
171 const char *arg)
172 {
173 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
174 &deflate_module);
175 int i;
176
177 i = atoi(arg);
178
179 if (i < 1 || i > 15)
180 return "DeflateWindowSize must be between 1 and 15";
181
182 c->windowSize = i * -1;
183
184 return NULL;
185 }
186
187 static const char *deflate_set_buffer_size(cmd_parms *cmd, void *dummy,
188 const char *arg)
189 {
190 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
191 &deflate_module);
192 int n = atoi(arg);
193
194 if (n <= 0) {
195 return "DeflateBufferSize should be positive";
196 }
197
198 c->bufferSize = (apr_size_t)n;
199
200 return NULL;
201 }
202 static const char *deflate_set_note(cmd_parms *cmd, void *dummy,
203 const char *arg1, const char *arg2)
204 {
205 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
206 &deflate_module);
207
208 if (arg2 == NULL) {
209 c->note_ratio_name = apr_pstrdup(cmd->pool, arg1);
210 }
211 else if (!strcasecmp(arg1, "ratio")) {
212 c->note_ratio_name = apr_pstrdup(cmd->pool, arg2);
213 }
214 else if (!strcasecmp(arg1, "input")) {
215 c->note_input_name = apr_pstrdup(cmd->pool, arg2);
216 }
217 else if (!strcasecmp(arg1, "output")) {
218 c->note_output_name = apr_pstrdup(cmd->pool, arg2);
219 }
220 else {
221 return apr_psprintf(cmd->pool, "Unknown note type %s", arg1);
222 }
223
224 return NULL;
225 }
226
227 static const char *deflate_set_memlevel(cmd_parms *cmd, void *dummy,
228 const char *arg)
229 {
230 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
231 &deflate_module);
232 int i;
233
234 i = atoi(arg);
235
236 if (i < 1 || i > 9)
237 return "DeflateMemLevel must be between 1 and 9";
238
239 c->memlevel = i;
240
241 return NULL;
242 }
243
244 static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
245 const char *arg)
246 {
247 deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
248 &deflate_module);
249 int i;
250
251 i = atoi(arg);
252
253 if (i < 1 || i > 9)
254 return "Compression Level must be between 1 and 9";
255
256 c->compressionlevel = i;
257
258 return NULL;
259 }
260
261 typedef struct deflate_ctx_t
262 {
263 z_stream stream;
264 unsigned char *buffer;
265 unsigned long crc;
266 apr_bucket_brigade *bb, *proc_bb;
267 int (*libz_end_func)(z_streamp);
268 unsigned char *validation_buffer;
269 apr_size_t validation_buffer_length;
270 } deflate_ctx;
271
272 /* Number of validation bytes (CRC and length) after the compressed data */
273 #define VALIDATION_SIZE 8
274 /* Do not update ctx->crc, see comment in flush_libz_buffer */
275 #define NO_UPDATE_CRC 0
276 /* Do update ctx->crc, see comment in flush_libz_buffer */
277 #define UPDATE_CRC 1
278
279 static int flush_libz_buffer(deflate_ctx *ctx, deflate_filter_config *c,
280 struct apr_bucket_alloc_t *bucket_alloc,
281 int (*libz_func)(z_streamp, int), int flush,
282 int crc)
283 {
284 int zRC = Z_OK;
285 int done = 0;
286 unsigned int deflate_len;
287 apr_bucket *b;
288
289 for (;;) {
290 deflate_len = c->bufferSize - ctx->stream.avail_out;
291
292 if (deflate_len != 0) {
293 /*
294 * Do we need to update ctx->crc? Usually this is the case for
295 * inflate action where we need to do a crc on the output, whereas
296 * in the deflate case we need to do a crc on the input
297 */
298 if (crc) {
299 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer,
300 deflate_len);
301 }
302 b = apr_bucket_heap_create((char *)ctx->buffer,
303 deflate_len, NULL,
304 bucket_alloc);
305 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
306 ctx->stream.next_out = ctx->buffer;
307 ctx->stream.avail_out = c->bufferSize;
308 }
309
310 if (done)
311 break;
312
313 zRC = libz_func(&ctx->stream, flush);
314
315 /*
316 * We can ignore Z_BUF_ERROR because:
317 * When we call libz_func we can assume that
318 *
319 * - avail_in is zero (due to the surrounding code that calls
320 * flush_libz_buffer)
321 * - avail_out is non zero due to our actions some lines above
322 *
323 * So the only reason for Z_BUF_ERROR is that the internal libz
324 * buffers are now empty and thus we called libz_func one time
325 * too often. This does not hurt. It simply says that we are done.
326 */
327 if (zRC == Z_BUF_ERROR) {
328 zRC = Z_OK;
329 break;
330 }
331
332 done = (ctx->stream.avail_out != 0 || zRC == Z_STREAM_END);
333
334 if (zRC != Z_OK && zRC != Z_STREAM_END)
335 break;
336 }
337 return zRC;
338 }
339
340 static apr_status_t deflate_ctx_cleanup(void *data)
341 {
342 deflate_ctx *ctx = (deflate_ctx *)data;
343
344 if (ctx)
345 ctx->libz_end_func(&ctx->stream);
346 return APR_SUCCESS;
347 }
348
349 static apr_status_t deflate_out_filter(ap_filter_t *f,
350 apr_bucket_brigade *bb)
351 {
352 apr_bucket *e;
353 request_rec *r = f->r;
354 deflate_ctx *ctx = f->ctx;
355 int zRC;
356 deflate_filter_config *c;
357
358 /* Do nothing if asked to filter nothing. */
359 if (APR_BRIGADE_EMPTY(bb)) {
360 return ap_pass_brigade(f->next, bb);
361 }
362
363 c = ap_get_module_config(r->server->module_config,
364 &deflate_module);
365
366 /* If we don't have a context, we need to ensure that it is okay to send
367 * the deflated content. If we have a context, that means we've done
368 * this before and we liked it.
369 * This could be not so nice if we always fail. But, if we succeed,
370 * we're in better shape.
371 */
372 if (!ctx) {
373 char *token;
374 const char *encoding;
375
376 /* only work on main request/no subrequests */
377 if (r->main != NULL) {
378 ap_remove_output_filter(f);
379 return ap_pass_brigade(f->next, bb);
380 }
381
382 /* some browsers might have problems, so set no-gzip
383 * (with browsermatch) for them
384 */
385 if (apr_table_get(r->subprocess_env, "no-gzip")) {
386 ap_remove_output_filter(f);
387 return ap_pass_brigade(f->next, bb);
388 }
389
390 /* We can't operate on Content-Ranges */
391 if (apr_table_get(r->headers_out, "Content-Range") != NULL) {
392 ap_remove_output_filter(f);
393 return ap_pass_brigade(f->next, bb);
394 }
395
396 /* Some browsers might have problems with content types
397 * other than text/html, so set gzip-only-text/html
398 * (with browsermatch) for them
399 */
400 if (r->content_type == NULL
401 || strncmp(r->content_type, "text/html", 9)) {
402 const char *env_value = apr_table_get(r->subprocess_env,
403 "gzip-only-text/html");
404 if ( env_value && (strcmp(env_value,"1") == 0) ) {
405 ap_remove_output_filter(f);
406 return ap_pass_brigade(f->next, bb);
407 }
408 }
409
410 /* Let's see what our current Content-Encoding is.
411 * If it's already encoded, don't compress again.
412 * (We could, but let's not.)
413 */
414 encoding = apr_table_get(r->headers_out, "Content-Encoding");
415 if (encoding) {
416 const char *err_enc;
417
418 err_enc = apr_table_get(r->err_headers_out, "Content-Encoding");
419 if (err_enc) {
420 encoding = apr_pstrcat(r->pool, encoding, ",", err_enc, NULL);
421 }
422 }
423 else {
424 encoding = apr_table_get(r->err_headers_out, "Content-Encoding");
425 }
426
427 if (r->content_encoding) {
428 encoding = encoding ? apr_pstrcat(r->pool, encoding, ",",
429 r->content_encoding, NULL)
430 : r->content_encoding;
431 }
432
433 if (encoding) {
434 const char *tmp = encoding;
435
436 token = ap_get_token(r->pool, &tmp, 0);
437 while (token && *token) {
438 /* stolen from mod_negotiation: */
439 if (strcmp(token, "identity") && strcmp(token, "7bit") &&
440 strcmp(token, "8bit") && strcmp(token, "binary")) {
441
442 ap_remove_output_filter(f);
443 return ap_pass_brigade(f->next, bb);
444 }
445
446 /* Otherwise, skip token */
447 if (*tmp) {
448 ++tmp;
449 }
450 token = (*tmp) ? ap_get_token(r->pool, &tmp, 0) : NULL;
451 }
452 }
453
454 /* Even if we don't accept this request based on it not having
455 * the Accept-Encoding, we need to note that we were looking
456 * for this header and downstream proxies should be aware of that.
457 */
458 apr_table_mergen(r->headers_out, "Vary", "Accept-Encoding");
459
460 /* force-gzip will just force it out regardless if the browser
461 * can actually do anything with it.
462 */
463 if (!apr_table_get(r->subprocess_env, "force-gzip")) {
464 const char *accepts;
465 /* if they don't have the line, then they can't play */
466 accepts = apr_table_get(r->headers_in, "Accept-Encoding");
467 if (accepts == NULL) {
468 ap_remove_output_filter(f);
469 return ap_pass_brigade(f->next, bb);
470 }
471
472 token = ap_get_token(r->pool, &accepts, 0);
473 while (token && token[0] && strcasecmp(token, "gzip")) {
474 /* skip parameters, XXX: ;q=foo evaluation? */
475 while (*accepts == ';') {
476 ++accepts;
477 token = ap_get_token(r->pool, &accepts, 1);
478 }
479
480 /* retrieve next token */
481 if (*accepts == ',') {
482 ++accepts;
483 }
484 token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
485 }
486
487 /* No acceptable token found. */
488 if (token == NULL || token[0] == '\0') {
489 ap_remove_output_filter(f);
490 return ap_pass_brigade(f->next, bb);
491 }
492 }
493
494 /* For a 304 or 204 response there is no entity included in
495 * the response and hence nothing to deflate. */
496 if (r->status == HTTP_NOT_MODIFIED || r->status == HTTP_NO_CONTENT) {
497 ap_remove_output_filter(f);
498 return ap_pass_brigade(f->next, bb);
499 }
500
501 /* We're cool with filtering this. */
502 ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
503 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
504 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
505 ctx->libz_end_func = deflateEnd;
506
507 zRC = deflateInit2(&ctx->stream, c->compressionlevel, Z_DEFLATED,
508 c->windowSize, c->memlevel,
509 Z_DEFAULT_STRATEGY);
510
511 if (zRC != Z_OK) {
512 deflateEnd(&ctx->stream);
513 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
514 "unable to init Zlib: "
515 "deflateInit2 returned %d: URL %s",
516 zRC, r->uri);
517 /*
518 * Remove ourselves as it does not make sense to return:
519 * We are not able to init libz and pass data down the chain
520 * uncompressed.
521 */
522 ap_remove_output_filter(f);
523 return ap_pass_brigade(f->next, bb);
524 }
525 /*
526 * Register a cleanup function to ensure that we cleanup the internal
527 * libz resources.
528 */
529 apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
530 apr_pool_cleanup_null);
531
532 /* add immortal gzip header */
533 e = apr_bucket_immortal_create(gzip_header, sizeof gzip_header,
534 f->c->bucket_alloc);
535 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
536
537 /* If the entire Content-Encoding is "identity", we can replace it. */
538 if (!encoding || !strcasecmp(encoding, "identity")) {
539 apr_table_setn(r->headers_out, "Content-Encoding", "gzip");
540 }
541 else {
542 apr_table_mergen(r->headers_out, "Content-Encoding", "gzip");
543 }
544 apr_table_unset(r->headers_out, "Content-Length");
545 apr_table_unset(r->headers_out, "Content-MD5");
546
547 /* initialize deflate output buffer */
548 ctx->stream.next_out = ctx->buffer;
549 ctx->stream.avail_out = c->bufferSize;
550 }
551
552 while (!APR_BRIGADE_EMPTY(bb))
553 {
554 const char *data;
555 apr_bucket *b;
556 apr_size_t len;
557
558 e = APR_BRIGADE_FIRST(bb);
559
560 if (APR_BUCKET_IS_EOS(e)) {
561 char *buf;
562
563 ctx->stream.avail_in = 0; /* should be zero already anyway */
564 /* flush the remaining data from the zlib buffers */
565 flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate, Z_FINISH,
566 NO_UPDATE_CRC);
567
568 buf = apr_palloc(r->pool, VALIDATION_SIZE);
569 putLong((unsigned char *)&buf[0], ctx->crc);
570 putLong((unsigned char *)&buf[4], ctx->stream.total_in);
571
572 b = apr_bucket_pool_create(buf, VALIDATION_SIZE, r->pool,
573 f->c->bucket_alloc);
574 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
575 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
576 "Zlib: Compressed %ld to %ld : URL %s",
577 ctx->stream.total_in, ctx->stream.total_out, r->uri);
578
579 /* leave notes for logging */
580 if (c->note_input_name) {
581 apr_table_setn(r->notes, c->note_input_name,
582 (ctx->stream.total_in > 0)
583 ? apr_off_t_toa(r->pool,
584 ctx->stream.total_in)
585 : "-");
586 }
587
588 if (c->note_output_name) {
589 apr_table_setn(r->notes, c->note_output_name,
590 (ctx->stream.total_in > 0)
591 ? apr_off_t_toa(r->pool,
592 ctx->stream.total_out)
593 : "-");
594 }
595
596 if (c->note_ratio_name) {
597 apr_table_setn(r->notes, c->note_ratio_name,
598 (ctx->stream.total_in > 0)
599 ? apr_itoa(r->pool,
600 (int)(ctx->stream.total_out
601 * 100
602 / ctx->stream.total_in))
603 : "-");
604 }
605
606 deflateEnd(&ctx->stream);
607 /* No need for cleanup any longer */
608 apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
609
610 /* Remove EOS from the old list, and insert into the new. */
611 APR_BUCKET_REMOVE(e);
612 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
613
614 /* Okay, we've seen the EOS.
615 * Time to pass it along down the chain.
616 */
617 return ap_pass_brigade(f->next, ctx->bb);
618 }
619
620 if (APR_BUCKET_IS_FLUSH(e)) {
621 apr_status_t rv;
622
623 /* flush the remaining data from the zlib buffers */
624 zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
625 Z_SYNC_FLUSH, NO_UPDATE_CRC);
626 if (zRC != Z_OK) {
627 return APR_EGENERAL;
628 }
629
630 /* Remove flush bucket from old brigade anf insert into the new. */
631 APR_BUCKET_REMOVE(e);
632 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
633 rv = ap_pass_brigade(f->next, ctx->bb);
634 if (rv != APR_SUCCESS) {
635 return rv;
636 }
637 continue;
638 }
639
640 if (APR_BUCKET_IS_METADATA(e)) {
641 /*
642 * Remove meta data bucket from old brigade and insert into the
643 * new.
644 */
645 APR_BUCKET_REMOVE(e);
646 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
647 continue;
648 }
649
650 /* read */
651 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
652
653 /* This crc32 function is from zlib. */
654 ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
655
656 /* write */
657 ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
658 * but we'll just have to
659 * trust zlib */
660 ctx->stream.avail_in = len;
661
662 while (ctx->stream.avail_in != 0) {
663 if (ctx->stream.avail_out == 0) {
664 apr_status_t rv;
665
666 ctx->stream.next_out = ctx->buffer;
667 len = c->bufferSize - ctx->stream.avail_out;
668
669 b = apr_bucket_heap_create((char *)ctx->buffer, len,
670 NULL, f->c->bucket_alloc);
671 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
672 ctx->stream.avail_out = c->bufferSize;
673 /* Send what we have right now to the next filter. */
674 rv = ap_pass_brigade(f->next, ctx->bb);
675 if (rv != APR_SUCCESS) {
676 return rv;
677 }
678 }
679
680 zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
681
682 if (zRC != Z_OK) {
683 return APR_EGENERAL;
684 }
685 }
686
687 apr_bucket_delete(e);
688 }
689
690 apr_brigade_cleanup(bb);
691 return APR_SUCCESS;
692 }
693
694 /* This is the deflate input filter (inflates). */
695 static apr_status_t deflate_in_filter(ap_filter_t *f,
696 apr_bucket_brigade *bb,
697 ap_input_mode_t mode,
698 apr_read_type_e block,
699 apr_off_t readbytes)
700 {
701 apr_bucket *bkt;
702 request_rec *r = f->r;
703 deflate_ctx *ctx = f->ctx;
704 int zRC;
705 apr_status_t rv;
706 deflate_filter_config *c;
707
708 /* just get out of the way of things we don't want. */
709 if (mode != AP_MODE_READBYTES) {
710 return ap_get_brigade(f->next, bb, mode, block, readbytes);
711 }
712
713 c = ap_get_module_config(r->server->module_config, &deflate_module);
714
715 if (!ctx) {
716 char deflate_hdr[10];
717 apr_size_t len;
718
719 /* only work on main request/no subrequests */
720 if (!ap_is_initial_req(r)) {
721 ap_remove_input_filter(f);
722 return ap_get_brigade(f->next, bb, mode, block, readbytes);
723 }
724
725 /* We can't operate on Content-Ranges */
726 if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
727 ap_remove_input_filter(f);
728 return ap_get_brigade(f->next, bb, mode, block, readbytes);
729 }
730
731 /* Check whether request body is gzipped.
732 *
733 * If it is, we're transforming the contents, invalidating
734 * some request headers including Content-Encoding.
735 *
736 * If not, we just remove ourself.
737 */
738 if (check_gzip(r->pool, r->headers_in) == 0) {
739 ap_remove_input_filter(f);
740 return ap_get_brigade(f->next, bb, mode, block, readbytes);
741 }
742
743 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
744 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
745 ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
746 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
747
748 rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
749 if (rv != APR_SUCCESS) {
750 return rv;
751 }
752
753 apr_table_unset(r->headers_in, "Content-Length");
754 apr_table_unset(r->headers_in, "Content-MD5");
755
756 len = 10;
757 rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len);
758 if (rv != APR_SUCCESS) {
759 return rv;
760 }
761
762 /* We didn't get the magic bytes. */
763 if (len != 10 ||
764 deflate_hdr[0] != deflate_magic[0] ||
765 deflate_hdr[1] != deflate_magic[1]) {
766 return APR_EGENERAL;
767 }
768
769 /* We can't handle flags for now. */
770 if (deflate_hdr[3] != 0) {
771 return APR_EGENERAL;
772 }
773
774 zRC = inflateInit2(&ctx->stream, c->windowSize);
775
776 if (zRC != Z_OK) {
777 f->ctx = NULL;
778 inflateEnd(&ctx->stream);
779 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
780 "unable to init Zlib: "
781 "inflateInit2 returned %d: URL %s",
782 zRC, r->uri);
783 ap_remove_input_filter(f);
784 return ap_get_brigade(f->next, bb, mode, block, readbytes);
785 }
786
787 /* initialize deflate output buffer */
788 ctx->stream.next_out = ctx->buffer;
789 ctx->stream.avail_out = c->bufferSize;
790
791 apr_brigade_cleanup(ctx->bb);
792 }
793
794 if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
795 rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
796
797 if (rv != APR_SUCCESS) {
798 /* What about APR_EAGAIN errors? */
799 inflateEnd(&ctx->stream);
800 return rv;
801 }
802
803 for (bkt = APR_BRIGADE_FIRST(ctx->bb);
804 bkt != APR_BRIGADE_SENTINEL(ctx->bb);
805 bkt = APR_BUCKET_NEXT(bkt))
806 {
807 const char *data;
808 apr_size_t len;
809
810 /* If we actually see the EOS, that means we screwed up! */
811 if (APR_BUCKET_IS_EOS(bkt)) {
812 inflateEnd(&ctx->stream);
813 return APR_EGENERAL;
814 }
815
816 if (APR_BUCKET_IS_FLUSH(bkt)) {
817 apr_bucket *tmp_heap;
818 zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
819 if (zRC != Z_OK) {
820 inflateEnd(&ctx->stream);
821 return APR_EGENERAL;
822 }
823
824 ctx->stream.next_out = ctx->buffer;
825 len = c->bufferSize - ctx->stream.avail_out;
826
827 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
828 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
829 NULL, f->c->bucket_alloc);
830 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
831 ctx->stream.avail_out = c->bufferSize;
832
833 /* Move everything to the returning brigade. */
834 APR_BUCKET_REMOVE(bkt);
835 APR_BRIGADE_CONCAT(bb, ctx->bb);
836 break;
837 }
838
839 /* read */
840 apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
841
842 /* pass through zlib inflate. */
843 ctx->stream.next_in = (unsigned char *)data;
844 ctx->stream.avail_in = len;
845
846 zRC = Z_OK;
847
848 while (ctx->stream.avail_in != 0) {
849 if (ctx->stream.avail_out == 0) {
850 apr_bucket *tmp_heap;
851 ctx->stream.next_out = ctx->buffer;
852 len = c->bufferSize - ctx->stream.avail_out;
853
854 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
855 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
856 NULL, f->c->bucket_alloc);
857 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
858 ctx->stream.avail_out = c->bufferSize;
859 }
860
861 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
862
863 if (zRC == Z_STREAM_END) {
864 break;
865 }
866
867 if (zRC != Z_OK) {
868 inflateEnd(&ctx->stream);
869 return APR_EGENERAL;
870 }
871 }
872 if (zRC == Z_STREAM_END) {
873 apr_bucket *tmp_heap, *eos;
874
875 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
876 "Zlib: Inflated %ld to %ld : URL %s",
877 ctx->stream.total_in, ctx->stream.total_out,
878 r->uri);
879
880 len = c->bufferSize - ctx->stream.avail_out;
881
882 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
883 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
884 NULL, f->c->bucket_alloc);
885 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
886 ctx->stream.avail_out = c->bufferSize;
887
888 /* Is the remaining 8 bytes already in the avail stream? */
889 if (ctx->stream.avail_in >= 8) {
890 unsigned long compCRC, compLen;
891 compCRC = getLong(ctx->stream.next_in);
892 if (ctx->crc != compCRC) {
893 inflateEnd(&ctx->stream);
894 return APR_EGENERAL;
895 }
896 ctx->stream.next_in += 4;
897 compLen = getLong(ctx->stream.next_in);
898 if (ctx->stream.total_out != compLen) {
899 inflateEnd(&ctx->stream);
900 return APR_EGENERAL;
901 }
902 }
903 else {
904 /* FIXME: We need to grab the 8 verification bytes
905 * from the wire! */
906 inflateEnd(&ctx->stream);
907 return APR_EGENERAL;
908 }
909
910 inflateEnd(&ctx->stream);
911
912 eos = apr_bucket_eos_create(f->c->bucket_alloc);
913 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos);
914 break;
915 }
916
917 }
918 apr_brigade_cleanup(ctx->bb);
919 }
920
921 /* If we are about to return nothing for a 'blocking' read and we have
922 * some data in our zlib buffer, flush it out so we can return something.
923 */
924 if (block == APR_BLOCK_READ &&
925 APR_BRIGADE_EMPTY(ctx->proc_bb) &&
926 ctx->stream.avail_out < c->bufferSize) {
927 apr_bucket *tmp_heap;
928 apr_size_t len;
929 ctx->stream.next_out = ctx->buffer;
930 len = c->bufferSize - ctx->stream.avail_out;
931
932 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
933 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
934 NULL, f->c->bucket_alloc);
935 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
936 ctx->stream.avail_out = c->bufferSize;
937 }
938
939 if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
940 apr_bucket_brigade *newbb;
941
942 /* May return APR_INCOMPLETE which is fine by us. */
943 apr_brigade_partition(ctx->proc_bb, readbytes, &bkt);
944
945 newbb = apr_brigade_split(ctx->proc_bb, bkt);
946 APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
947 APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
948 }
949
950 return APR_SUCCESS;
951 }
952
953
954 /* Filter to inflate for a content-transforming proxy. */
955 static apr_status_t inflate_out_filter(ap_filter_t *f,
956 apr_bucket_brigade *bb)
957 {
958 int zlib_method;
959 int zlib_flags;
960 int inflate_init = 1;
961 apr_bucket *e;
962 request_rec *r = f->r;
963 deflate_ctx *ctx = f->ctx;
964 int zRC;
965 apr_status_t rv;
966 deflate_filter_config *c;
967
968 /* Do nothing if asked to filter nothing. */
969 if (APR_BRIGADE_EMPTY(bb)) {
970 return ap_pass_brigade(f->next, bb);
971 }
972
973 c = ap_get_module_config(r->server->module_config, &deflate_module);
974
975 if (!ctx) {
976
977 /* only work on main request/no subrequests */
978 if (!ap_is_initial_req(r)) {
979 ap_remove_output_filter(f);
980 return ap_pass_brigade(f->next, bb);
981 }
982
983 /* We can't operate on Content-Ranges */
984 if (apr_table_get(r->headers_out, "Content-Range") != NULL) {
985 ap_remove_output_filter(f);
986 return ap_pass_brigade(f->next, bb);
987 }
988
989 /*
990 * Let's see what our current Content-Encoding is.
991 * Only inflate if gzipped.
992 */
993 if (check_gzip(r->pool, r->headers_out) == 0) {
994 ap_remove_output_filter(f);
995 return ap_pass_brigade(f->next, bb);
996 }
997
998 /* No need to inflate HEAD or 204/304 */
999 if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
1000 ap_remove_output_filter(f);
1001 return ap_pass_brigade(f->next, bb);
1002 }
1003
1004 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
1005 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
1006 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
1007 ctx->libz_end_func = inflateEnd;
1008 ctx->validation_buffer = NULL;
1009 ctx->validation_buffer_length = 0;
1010
1011 zRC = inflateInit2(&ctx->stream, c->windowSize);
1012
1013 if (zRC != Z_OK) {
1014 f->ctx = NULL;
1015 inflateEnd(&ctx->stream);
1016 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1017 "unable to init Zlib: "
1018 "inflateInit2 returned %d: URL %s",
1019 zRC, r->uri);
1020 /*
1021 * Remove ourselves as it does not make sense to return:
1022 * We are not able to init libz and pass data down the chain
1023 * compressed.
1024 */
1025 ap_remove_output_filter(f);
1026 return ap_pass_brigade(f->next, bb);
1027 }
1028
1029 /*
1030 * Register a cleanup function to ensure that we cleanup the internal
1031 * libz resources.
1032 */
1033 apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
1034 apr_pool_cleanup_null);
1035
1036 /* these are unlikely to be set anyway, but ... */
1037 apr_table_unset(r->headers_out, "Content-Length");
1038 apr_table_unset(r->headers_out, "Content-MD5");
1039
1040 /* initialize inflate output buffer */
1041 ctx->stream.next_out = ctx->buffer;
1042 ctx->stream.avail_out = c->bufferSize;
1043
1044 inflate_init = 0;
1045 }
1046
1047 while (!APR_BRIGADE_EMPTY(bb))
1048 {
1049 const char *data;
1050 apr_bucket *b;
1051 apr_size_t len;
1052
1053 e = APR_BRIGADE_FIRST(bb);
1054
1055 if (APR_BUCKET_IS_EOS(e)) {
1056 /*
1057 * We are really done now. Ensure that we never return here, even
1058 * if a second EOS bucket falls down the chain. Thus remove
1059 * ourselves.
1060 */
1061 ap_remove_output_filter(f);
1062 /* should be zero already anyway */
1063 ctx->stream.avail_in = 0;
1064 /*
1065 * Flush the remaining data from the zlib buffers. It is correct
1066 * to use Z_SYNC_FLUSH in this case and not Z_FINISH as in the
1067 * deflate case. In the inflate case Z_FINISH requires to have a
1068 * large enough output buffer to put ALL data in otherwise it
1069 * fails, whereas in the deflate case you can empty a filled output
1070 * buffer and call it again until no more output can be created.
1071 */
1072 flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate, Z_SYNC_FLUSH,
1073 UPDATE_CRC);
1074 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1075 "Zlib: Inflated %ld to %ld : URL %s",
1076 ctx->stream.total_in, ctx->stream.total_out, r->uri);
1077
1078 if (ctx->validation_buffer_length == VALIDATION_SIZE) {
1079 unsigned long compCRC, compLen;
1080 compCRC = getLong(ctx->validation_buffer);
1081 if (ctx->crc != compCRC) {
1082 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1083 "Zlib: Checksum of inflated stream invalid");
1084 return APR_EGENERAL;
1085 }
1086 ctx->validation_buffer += VALIDATION_SIZE / 2;
1087 compLen = getLong(ctx->validation_buffer);
1088 if (ctx->stream.total_out != compLen) {
1089 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1090 "Zlib: Length of inflated stream invalid");
1091 return APR_EGENERAL;
1092 }
1093 }
1094 else {
1095 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1096 "Zlib: Validation bytes not present");
1097 return APR_EGENERAL;
1098 }
1099
1100 inflateEnd(&ctx->stream);
1101 /* No need for cleanup any longer */
1102 apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
1103
1104 /* Remove EOS from the old list, and insert into the new. */
1105 APR_BUCKET_REMOVE(e);
1106 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1107
1108 /*
1109 * Okay, we've seen the EOS.
1110 * Time to pass it along down the chain.
1111 */
1112 return ap_pass_brigade(f->next, ctx->bb);
1113 }
1114
1115 if (APR_BUCKET_IS_FLUSH(e)) {
1116 apr_status_t rv;
1117
1118 /* flush the remaining data from the zlib buffers */
1119 zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate,
1120 Z_SYNC_FLUSH, UPDATE_CRC);
1121 if (zRC != Z_OK) {
1122 return APR_EGENERAL;
1123 }
1124
1125 /* Remove flush bucket from old brigade anf insert into the new. */
1126 APR_BUCKET_REMOVE(e);
1127 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1128 rv = ap_pass_brigade(f->next, ctx->bb);
1129 if (rv != APR_SUCCESS) {
1130 return rv;
1131 }
1132 continue;
1133 }
1134
1135 if (APR_BUCKET_IS_METADATA(e)) {
1136 /*
1137 * Remove meta data bucket from old brigade and insert into the
1138 * new.
1139 */
1140 APR_BUCKET_REMOVE(e);
1141 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1142 continue;
1143 }
1144
1145 /* read */
1146 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
1147
1148 /* first bucket contains zlib header */
1149 if (!inflate_init++) {
1150 if (len < 10) {
1151 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1152 "Insufficient data for inflate");
1153 return APR_EGENERAL;
1154 }
1155 else {
1156 zlib_method = data[2];
1157 zlib_flags = data[3];
1158 if (zlib_method != Z_DEFLATED) {
1159 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1160 "inflate: data not deflated!");
1161 ap_remove_output_filter(f);
1162 return ap_pass_brigade(f->next, bb);
1163 }
1164 if (data[0] != deflate_magic[0] ||
1165 data[1] != deflate_magic[1] ||
1166 (zlib_flags & RESERVED) != 0) {
1167 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1168 "inflate: bad header");
1169 return APR_EGENERAL ;
1170 }
1171 data += 10 ;
1172 len -= 10 ;
1173 }
1174 if (zlib_flags & EXTRA_FIELD) {
1175 unsigned int bytes = (unsigned int)(data[0]);
1176 bytes += ((unsigned int)(data[1])) << 8;
1177 bytes += 2;
1178 if (len < bytes) {
1179 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1180 "inflate: extra field too big (not "
1181 "supported)");
1182 return APR_EGENERAL;
1183 }
1184 data += bytes;
1185 len -= bytes;
1186 }
1187 if (zlib_flags & ORIG_NAME) {
1188 while (len-- && *data++);
1189 }
1190 if (zlib_flags & COMMENT) {
1191 while (len-- && *data++);
1192 }
1193 if (zlib_flags & HEAD_CRC) {
1194 len -= 2;
1195 data += 2;
1196 }
1197 }
1198
1199 /* pass through zlib inflate. */
1200 ctx->stream.next_in = (unsigned char *)data;
1201 ctx->stream.avail_in = len;
1202
1203 if (ctx->validation_buffer) {
1204 if (ctx->validation_buffer_length < VALIDATION_SIZE) {
1205 apr_size_t copy_size;
1206
1207 copy_size = VALIDATION_SIZE - ctx->validation_buffer_length;
1208 if (copy_size > ctx->stream.avail_in)
1209 copy_size = ctx->stream.avail_in;
1210 memcpy(ctx->validation_buffer + ctx->validation_buffer_length,
1211 ctx->stream.next_in, copy_size);
1212 /* Saved copy_size bytes */
1213 ctx->stream.avail_in -= copy_size;
1214 ctx->validation_buffer_length += copy_size;
1215 }
1216 if (ctx->stream.avail_in) {
1217 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1218 "Zlib: %d bytes of garbage at the end of "
1219 "compressed stream.", ctx->stream.avail_in);
1220 /*
1221 * There is nothing worth consuming for zlib left, because it is
1222 * either garbage data or the data has been copied to the
1223 * validation buffer (processing validation data is no business
1224 * for zlib). So set ctx->stream.avail_in to zero to indicate
1225 * this to the following while loop.
1226 */
1227 ctx->stream.avail_in = 0;
1228 }
1229 }
1230
1231 zRC = Z_OK;
1232
1233 while (ctx->stream.avail_in != 0) {
1234 if (ctx->stream.avail_out == 0) {
1235
1236 ctx->stream.next_out = ctx->buffer;
1237 len = c->bufferSize - ctx->stream.avail_out;
1238
1239 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
1240 b = apr_bucket_heap_create((char *)ctx->buffer, len,
1241 NULL, f->c->bucket_alloc);
1242 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1243 ctx->stream.avail_out = c->bufferSize;
1244 /* Send what we have right now to the next filter. */
1245 rv = ap_pass_brigade(f->next, ctx->bb);
1246 if (rv != APR_SUCCESS) {
1247 return rv;
1248 }
1249 }
1250
1251 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
1252
1253 if (zRC == Z_STREAM_END) {
1254 /*
1255 * We have inflated all data. Now try to capture the
1256 * validation bytes. We may not have them all available
1257 * right now, but capture what is there.
1258 */
1259 ctx->validation_buffer = apr_pcalloc(f->r->pool,
1260 VALIDATION_SIZE);
1261 if (ctx->stream.avail_in > VALIDATION_SIZE) {
1262 ctx->validation_buffer_length = VALIDATION_SIZE;
1263 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1264 "Zlib: %d bytes of garbage at the end of "
1265 "compressed stream.",
1266 ctx->stream.avail_in - VALIDATION_SIZE);
1267 } else if (ctx->stream.avail_in > 0) {
1268 ctx->validation_buffer_length = ctx->stream.avail_in;
1269 }
1270 if (ctx->validation_buffer_length)
1271 memcpy(ctx->validation_buffer, ctx->stream.next_in,
1272 ctx->validation_buffer_length);
1273 break;
1274 }
1275
1276 if (zRC != Z_OK) {
1277 return APR_EGENERAL;
1278 }
1279 }
1280
1281 apr_bucket_delete(e);
1282 }
1283
1284 apr_brigade_cleanup(bb);
1285 return APR_SUCCESS;
1286 }
1287
1288 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
1289 static void register_hooks(apr_pool_t *p)
1290 {
1291 ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
1292 AP_FTYPE_CONTENT_SET);
1293 ap_register_output_filter("INFLATE", inflate_out_filter, NULL,
1294 AP_FTYPE_RESOURCE-1);
1295 ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
1296 AP_FTYPE_CONTENT_SET);
1297 }
1298
1299 static const command_rec deflate_filter_cmds[] = {
1300 AP_INIT_TAKE12("DeflateFilterNote", deflate_set_note, NULL, RSRC_CONF,
1301 "Set a note to report on compression ratio"),
1302 AP_INIT_TAKE1("DeflateWindowSize", deflate_set_window_size, NULL,
1303 RSRC_CONF, "Set the Deflate window size (1-15)"),
1304 AP_INIT_TAKE1("DeflateBufferSize", deflate_set_buffer_size, NULL, RSRC_CONF,
1305 "Set the Deflate Buffer Size"),
1306 AP_INIT_TAKE1("DeflateMemLevel", deflate_set_memlevel, NULL, RSRC_CONF,
1307 "Set the Deflate Memory Level (1-9)"),
1308 AP_INIT_TAKE1("DeflateCompressionLevel", deflate_set_compressionlevel, NULL, RSRC_CONF,
1309 "Set the Deflate Compression Level (1-9)"),
1310 {NULL}
1311 };
1312
1313 module AP_MODULE_DECLARE_DATA deflate_module = {
1314 STANDARD20_MODULE_STUFF,
1315 NULL, /* dir config creater */
1316 NULL, /* dir merger --- default is to override */
1317 create_deflate_server_config, /* server config */
1318 NULL, /* merge server config */
1319 deflate_filter_cmds, /* command table */
1320 register_hooks /* register hooks */
1321 };

Properties

Name Value
svn:eol-style native

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2