/[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 563229 - (show annotations)
Mon Aug 6 19:22:23 2007 UTC (2 years, 3 months ago) by rpluem
File MIME type: text/plain
File size: 47312 byte(s)
* We already unset Content-Length in the inflate_out_filter. So move the
  comment and unsetting of Content-MD5 in the right place.
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
546 /* initialize deflate output buffer */
547 ctx->stream.next_out = ctx->buffer;
548 ctx->stream.avail_out = c->bufferSize;
549 }
550
551 while (!APR_BRIGADE_EMPTY(bb))
552 {
553 const char *data;
554 apr_bucket *b;
555 apr_size_t len;
556
557 e = APR_BRIGADE_FIRST(bb);
558
559 if (APR_BUCKET_IS_EOS(e)) {
560 char *buf;
561
562 ctx->stream.avail_in = 0; /* should be zero already anyway */
563 /* flush the remaining data from the zlib buffers */
564 flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate, Z_FINISH,
565 NO_UPDATE_CRC);
566
567 buf = apr_palloc(r->pool, VALIDATION_SIZE);
568 putLong((unsigned char *)&buf[0], ctx->crc);
569 putLong((unsigned char *)&buf[4], ctx->stream.total_in);
570
571 b = apr_bucket_pool_create(buf, VALIDATION_SIZE, r->pool,
572 f->c->bucket_alloc);
573 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
574 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
575 "Zlib: Compressed %ld to %ld : URL %s",
576 ctx->stream.total_in, ctx->stream.total_out, r->uri);
577
578 /* leave notes for logging */
579 if (c->note_input_name) {
580 apr_table_setn(r->notes, c->note_input_name,
581 (ctx->stream.total_in > 0)
582 ? apr_off_t_toa(r->pool,
583 ctx->stream.total_in)
584 : "-");
585 }
586
587 if (c->note_output_name) {
588 apr_table_setn(r->notes, c->note_output_name,
589 (ctx->stream.total_in > 0)
590 ? apr_off_t_toa(r->pool,
591 ctx->stream.total_out)
592 : "-");
593 }
594
595 if (c->note_ratio_name) {
596 apr_table_setn(r->notes, c->note_ratio_name,
597 (ctx->stream.total_in > 0)
598 ? apr_itoa(r->pool,
599 (int)(ctx->stream.total_out
600 * 100
601 / ctx->stream.total_in))
602 : "-");
603 }
604
605 deflateEnd(&ctx->stream);
606 /* No need for cleanup any longer */
607 apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
608
609 /* Remove EOS from the old list, and insert into the new. */
610 APR_BUCKET_REMOVE(e);
611 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
612
613 /* Okay, we've seen the EOS.
614 * Time to pass it along down the chain.
615 */
616 return ap_pass_brigade(f->next, ctx->bb);
617 }
618
619 if (APR_BUCKET_IS_FLUSH(e)) {
620 apr_status_t rv;
621
622 /* flush the remaining data from the zlib buffers */
623 zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
624 Z_SYNC_FLUSH, NO_UPDATE_CRC);
625 if (zRC != Z_OK) {
626 return APR_EGENERAL;
627 }
628
629 /* Remove flush bucket from old brigade anf insert into the new. */
630 APR_BUCKET_REMOVE(e);
631 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
632 rv = ap_pass_brigade(f->next, ctx->bb);
633 if (rv != APR_SUCCESS) {
634 return rv;
635 }
636 continue;
637 }
638
639 if (APR_BUCKET_IS_METADATA(e)) {
640 /*
641 * Remove meta data bucket from old brigade and insert into the
642 * new.
643 */
644 APR_BUCKET_REMOVE(e);
645 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
646 continue;
647 }
648
649 /* read */
650 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
651
652 /* This crc32 function is from zlib. */
653 ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
654
655 /* write */
656 ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
657 * but we'll just have to
658 * trust zlib */
659 ctx->stream.avail_in = len;
660
661 while (ctx->stream.avail_in != 0) {
662 if (ctx->stream.avail_out == 0) {
663 apr_status_t rv;
664
665 ctx->stream.next_out = ctx->buffer;
666 len = c->bufferSize - ctx->stream.avail_out;
667
668 b = apr_bucket_heap_create((char *)ctx->buffer, len,
669 NULL, f->c->bucket_alloc);
670 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
671 ctx->stream.avail_out = c->bufferSize;
672 /* Send what we have right now to the next filter. */
673 rv = ap_pass_brigade(f->next, ctx->bb);
674 if (rv != APR_SUCCESS) {
675 return rv;
676 }
677 }
678
679 zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
680
681 if (zRC != Z_OK) {
682 return APR_EGENERAL;
683 }
684 }
685
686 apr_bucket_delete(e);
687 }
688
689 apr_brigade_cleanup(bb);
690 return APR_SUCCESS;
691 }
692
693 /* This is the deflate input filter (inflates). */
694 static apr_status_t deflate_in_filter(ap_filter_t *f,
695 apr_bucket_brigade *bb,
696 ap_input_mode_t mode,
697 apr_read_type_e block,
698 apr_off_t readbytes)
699 {
700 apr_bucket *bkt;
701 request_rec *r = f->r;
702 deflate_ctx *ctx = f->ctx;
703 int zRC;
704 apr_status_t rv;
705 deflate_filter_config *c;
706
707 /* just get out of the way of things we don't want. */
708 if (mode != AP_MODE_READBYTES) {
709 return ap_get_brigade(f->next, bb, mode, block, readbytes);
710 }
711
712 c = ap_get_module_config(r->server->module_config, &deflate_module);
713
714 if (!ctx) {
715 char deflate_hdr[10];
716 apr_size_t len;
717
718 /* only work on main request/no subrequests */
719 if (!ap_is_initial_req(r)) {
720 ap_remove_input_filter(f);
721 return ap_get_brigade(f->next, bb, mode, block, readbytes);
722 }
723
724 /* We can't operate on Content-Ranges */
725 if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
726 ap_remove_input_filter(f);
727 return ap_get_brigade(f->next, bb, mode, block, readbytes);
728 }
729
730 /* Check whether request body is gzipped.
731 *
732 * If it is, we're transforming the contents, invalidating
733 * some request headers including Content-Encoding.
734 *
735 * If not, we just remove ourself.
736 */
737 if (check_gzip(r->pool, r->headers_in) == 0) {
738 ap_remove_input_filter(f);
739 return ap_get_brigade(f->next, bb, mode, block, readbytes);
740 }
741
742 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
743 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
744 ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
745 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
746
747 rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
748 if (rv != APR_SUCCESS) {
749 return rv;
750 }
751
752 apr_table_unset(r->headers_in, "Content-Length");
753 apr_table_unset(r->headers_in, "Content-MD5");
754
755 len = 10;
756 rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len);
757 if (rv != APR_SUCCESS) {
758 return rv;
759 }
760
761 /* We didn't get the magic bytes. */
762 if (len != 10 ||
763 deflate_hdr[0] != deflate_magic[0] ||
764 deflate_hdr[1] != deflate_magic[1]) {
765 return APR_EGENERAL;
766 }
767
768 /* We can't handle flags for now. */
769 if (deflate_hdr[3] != 0) {
770 return APR_EGENERAL;
771 }
772
773 zRC = inflateInit2(&ctx->stream, c->windowSize);
774
775 if (zRC != Z_OK) {
776 f->ctx = NULL;
777 inflateEnd(&ctx->stream);
778 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
779 "unable to init Zlib: "
780 "inflateInit2 returned %d: URL %s",
781 zRC, r->uri);
782 ap_remove_input_filter(f);
783 return ap_get_brigade(f->next, bb, mode, block, readbytes);
784 }
785
786 /* initialize deflate output buffer */
787 ctx->stream.next_out = ctx->buffer;
788 ctx->stream.avail_out = c->bufferSize;
789
790 apr_brigade_cleanup(ctx->bb);
791 }
792
793 if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
794 rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
795
796 if (rv != APR_SUCCESS) {
797 /* What about APR_EAGAIN errors? */
798 inflateEnd(&ctx->stream);
799 return rv;
800 }
801
802 for (bkt = APR_BRIGADE_FIRST(ctx->bb);
803 bkt != APR_BRIGADE_SENTINEL(ctx->bb);
804 bkt = APR_BUCKET_NEXT(bkt))
805 {
806 const char *data;
807 apr_size_t len;
808
809 /* If we actually see the EOS, that means we screwed up! */
810 if (APR_BUCKET_IS_EOS(bkt)) {
811 inflateEnd(&ctx->stream);
812 return APR_EGENERAL;
813 }
814
815 if (APR_BUCKET_IS_FLUSH(bkt)) {
816 apr_bucket *tmp_heap;
817 zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
818 if (zRC != Z_OK) {
819 inflateEnd(&ctx->stream);
820 return APR_EGENERAL;
821 }
822
823 ctx->stream.next_out = ctx->buffer;
824 len = c->bufferSize - ctx->stream.avail_out;
825
826 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
827 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
828 NULL, f->c->bucket_alloc);
829 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
830 ctx->stream.avail_out = c->bufferSize;
831
832 /* Move everything to the returning brigade. */
833 APR_BUCKET_REMOVE(bkt);
834 APR_BRIGADE_CONCAT(bb, ctx->bb);
835 break;
836 }
837
838 /* read */
839 apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
840
841 /* pass through zlib inflate. */
842 ctx->stream.next_in = (unsigned char *)data;
843 ctx->stream.avail_in = len;
844
845 zRC = Z_OK;
846
847 while (ctx->stream.avail_in != 0) {
848 if (ctx->stream.avail_out == 0) {
849 apr_bucket *tmp_heap;
850 ctx->stream.next_out = ctx->buffer;
851 len = c->bufferSize - ctx->stream.avail_out;
852
853 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
854 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
855 NULL, f->c->bucket_alloc);
856 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
857 ctx->stream.avail_out = c->bufferSize;
858 }
859
860 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
861
862 if (zRC == Z_STREAM_END) {
863 break;
864 }
865
866 if (zRC != Z_OK) {
867 inflateEnd(&ctx->stream);
868 return APR_EGENERAL;
869 }
870 }
871 if (zRC == Z_STREAM_END) {
872 apr_bucket *tmp_heap, *eos;
873
874 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
875 "Zlib: Inflated %ld to %ld : URL %s",
876 ctx->stream.total_in, ctx->stream.total_out,
877 r->uri);
878
879 len = c->bufferSize - ctx->stream.avail_out;
880
881 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
882 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
883 NULL, f->c->bucket_alloc);
884 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
885 ctx->stream.avail_out = c->bufferSize;
886
887 /* Is the remaining 8 bytes already in the avail stream? */
888 if (ctx->stream.avail_in >= 8) {
889 unsigned long compCRC, compLen;
890 compCRC = getLong(ctx->stream.next_in);
891 if (ctx->crc != compCRC) {
892 inflateEnd(&ctx->stream);
893 return APR_EGENERAL;
894 }
895 ctx->stream.next_in += 4;
896 compLen = getLong(ctx->stream.next_in);
897 if (ctx->stream.total_out != compLen) {
898 inflateEnd(&ctx->stream);
899 return APR_EGENERAL;
900 }
901 }
902 else {
903 /* FIXME: We need to grab the 8 verification bytes
904 * from the wire! */
905 inflateEnd(&ctx->stream);
906 return APR_EGENERAL;
907 }
908
909 inflateEnd(&ctx->stream);
910
911 eos = apr_bucket_eos_create(f->c->bucket_alloc);
912 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos);
913 break;
914 }
915
916 }
917 apr_brigade_cleanup(ctx->bb);
918 }
919
920 /* If we are about to return nothing for a 'blocking' read and we have
921 * some data in our zlib buffer, flush it out so we can return something.
922 */
923 if (block == APR_BLOCK_READ &&
924 APR_BRIGADE_EMPTY(ctx->proc_bb) &&
925 ctx->stream.avail_out < c->bufferSize) {
926 apr_bucket *tmp_heap;
927 apr_size_t len;
928 ctx->stream.next_out = ctx->buffer;
929 len = c->bufferSize - ctx->stream.avail_out;
930
931 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
932 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
933 NULL, f->c->bucket_alloc);
934 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
935 ctx->stream.avail_out = c->bufferSize;
936 }
937
938 if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
939 apr_bucket_brigade *newbb;
940
941 /* May return APR_INCOMPLETE which is fine by us. */
942 apr_brigade_partition(ctx->proc_bb, readbytes, &bkt);
943
944 newbb = apr_brigade_split(ctx->proc_bb, bkt);
945 APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
946 APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
947 }
948
949 return APR_SUCCESS;
950 }
951
952
953 /* Filter to inflate for a content-transforming proxy. */
954 static apr_status_t inflate_out_filter(ap_filter_t *f,
955 apr_bucket_brigade *bb)
956 {
957 int zlib_method;
958 int zlib_flags;
959 int inflate_init = 1;
960 apr_bucket *e;
961 request_rec *r = f->r;
962 deflate_ctx *ctx = f->ctx;
963 int zRC;
964 apr_status_t rv;
965 deflate_filter_config *c;
966
967 /* Do nothing if asked to filter nothing. */
968 if (APR_BRIGADE_EMPTY(bb)) {
969 return ap_pass_brigade(f->next, bb);
970 }
971
972 c = ap_get_module_config(r->server->module_config, &deflate_module);
973
974 if (!ctx) {
975
976 /* only work on main request/no subrequests */
977 if (!ap_is_initial_req(r)) {
978 ap_remove_output_filter(f);
979 return ap_pass_brigade(f->next, bb);
980 }
981
982 /* We can't operate on Content-Ranges */
983 if (apr_table_get(r->headers_out, "Content-Range") != NULL) {
984 ap_remove_output_filter(f);
985 return ap_pass_brigade(f->next, bb);
986 }
987
988 /*
989 * Let's see what our current Content-Encoding is.
990 * Only inflate if gzipped.
991 */
992 if (check_gzip(r->pool, r->headers_out) == 0) {
993 ap_remove_output_filter(f);
994 return ap_pass_brigade(f->next, bb);
995 }
996
997 /* No need to inflate HEAD or 204/304 */
998 if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
999 ap_remove_output_filter(f);
1000 return ap_pass_brigade(f->next, bb);
1001 }
1002
1003 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
1004 ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
1005 ctx->buffer = apr_palloc(r->pool, c->bufferSize);
1006 ctx->libz_end_func = inflateEnd;
1007 ctx->validation_buffer = NULL;
1008 ctx->validation_buffer_length = 0;
1009
1010 zRC = inflateInit2(&ctx->stream, c->windowSize);
1011
1012 if (zRC != Z_OK) {
1013 f->ctx = NULL;
1014 inflateEnd(&ctx->stream);
1015 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1016 "unable to init Zlib: "
1017 "inflateInit2 returned %d: URL %s",
1018 zRC, r->uri);
1019 /*
1020 * Remove ourselves as it does not make sense to return:
1021 * We are not able to init libz and pass data down the chain
1022 * compressed.
1023 */
1024 ap_remove_output_filter(f);
1025 return ap_pass_brigade(f->next, bb);
1026 }
1027
1028 /*
1029 * Register a cleanup function to ensure that we cleanup the internal
1030 * libz resources.
1031 */
1032 apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
1033 apr_pool_cleanup_null);
1034
1035 /* these are unlikely to be set anyway, but ... */
1036 apr_table_unset(r->headers_out, "Content-Length");
1037 apr_table_unset(r->headers_out, "Content-MD5");
1038
1039 /* initialize inflate output buffer */
1040 ctx->stream.next_out = ctx->buffer;
1041 ctx->stream.avail_out = c->bufferSize;
1042
1043 inflate_init = 0;
1044 }
1045
1046 while (!APR_BRIGADE_EMPTY(bb))
1047 {
1048 const char *data;
1049 apr_bucket *b;
1050 apr_size_t len;
1051
1052 e = APR_BRIGADE_FIRST(bb);
1053
1054 if (APR_BUCKET_IS_EOS(e)) {
1055 /*
1056 * We are really done now. Ensure that we never return here, even
1057 * if a second EOS bucket falls down the chain. Thus remove
1058 * ourselves.
1059 */
1060 ap_remove_output_filter(f);
1061 /* should be zero already anyway */
1062 ctx->stream.avail_in = 0;
1063 /*
1064 * Flush the remaining data from the zlib buffers. It is correct
1065 * to use Z_SYNC_FLUSH in this case and not Z_FINISH as in the
1066 * deflate case. In the inflate case Z_FINISH requires to have a
1067 * large enough output buffer to put ALL data in otherwise it
1068 * fails, whereas in the deflate case you can empty a filled output
1069 * buffer and call it again until no more output can be created.
1070 */
1071 flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate, Z_SYNC_FLUSH,
1072 UPDATE_CRC);
1073 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1074 "Zlib: Inflated %ld to %ld : URL %s",
1075 ctx->stream.total_in, ctx->stream.total_out, r->uri);
1076
1077 if (ctx->validation_buffer_length == VALIDATION_SIZE) {
1078 unsigned long compCRC, compLen;
1079 compCRC = getLong(ctx->validation_buffer);
1080 if (ctx->crc != compCRC) {
1081 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1082 "Zlib: Checksum of inflated stream invalid");
1083 return APR_EGENERAL;
1084 }
1085 ctx->validation_buffer += VALIDATION_SIZE / 2;
1086 compLen = getLong(ctx->validation_buffer);
1087 if (ctx->stream.total_out != compLen) {
1088 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1089 "Zlib: Length of inflated stream invalid");
1090 return APR_EGENERAL;
1091 }
1092 }
1093 else {
1094 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1095 "Zlib: Validation bytes not present");
1096 return APR_EGENERAL;
1097 }
1098
1099 inflateEnd(&ctx->stream);
1100 /* No need for cleanup any longer */
1101 apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
1102
1103 /* Remove EOS from the old list, and insert into the new. */
1104 APR_BUCKET_REMOVE(e);
1105 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1106
1107 /*
1108 * Okay, we've seen the EOS.
1109 * Time to pass it along down the chain.
1110 */
1111 return ap_pass_brigade(f->next, ctx->bb);
1112 }
1113
1114 if (APR_BUCKET_IS_FLUSH(e)) {
1115 apr_status_t rv;
1116
1117 /* flush the remaining data from the zlib buffers */
1118 zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate,
1119 Z_SYNC_FLUSH, UPDATE_CRC);
1120 if (zRC != Z_OK) {
1121 return APR_EGENERAL;
1122 }
1123
1124 /* Remove flush bucket from old brigade anf insert into the new. */
1125 APR_BUCKET_REMOVE(e);
1126 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1127 rv = ap_pass_brigade(f->next, ctx->bb);
1128 if (rv != APR_SUCCESS) {
1129 return rv;
1130 }
1131 continue;
1132 }
1133
1134 if (APR_BUCKET_IS_METADATA(e)) {
1135 /*
1136 * Remove meta data bucket from old brigade and insert into the
1137 * new.
1138 */
1139 APR_BUCKET_REMOVE(e);
1140 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1141 continue;
1142 }
1143
1144 /* read */
1145 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
1146
1147 /* first bucket contains zlib header */
1148 if (!inflate_init++) {
1149 if (len < 10) {
1150 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1151 "Insufficient data for inflate");
1152 return APR_EGENERAL;
1153 }
1154 else {
1155 zlib_method = data[2];
1156 zlib_flags = data[3];
1157 if (zlib_method != Z_DEFLATED) {
1158 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1159 "inflate: data not deflated!");
1160 ap_remove_output_filter(f);
1161 return ap_pass_brigade(f->next, bb);
1162 }
1163 if (data[0] != deflate_magic[0] ||
1164 data[1] != deflate_magic[1] ||
1165 (zlib_flags & RESERVED) != 0) {
1166 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1167 "inflate: bad header");
1168 return APR_EGENERAL ;
1169 }
1170 data += 10 ;
1171 len -= 10 ;
1172 }
1173 if (zlib_flags & EXTRA_FIELD) {
1174 unsigned int bytes = (unsigned int)(data[0]);
1175 bytes += ((unsigned int)(data[1])) << 8;
1176 bytes += 2;
1177 if (len < bytes) {
1178 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1179 "inflate: extra field too big (not "
1180 "supported)");
1181 return APR_EGENERAL;
1182 }
1183 data += bytes;
1184 len -= bytes;
1185 }
1186 if (zlib_flags & ORIG_NAME) {
1187 while (len-- && *data++);
1188 }
1189 if (zlib_flags & COMMENT) {
1190 while (len-- && *data++);
1191 }
1192 if (zlib_flags & HEAD_CRC) {
1193 len -= 2;
1194 data += 2;
1195 }
1196 }
1197
1198 /* pass through zlib inflate. */
1199 ctx->stream.next_in = (unsigned char *)data;
1200 ctx->stream.avail_in = len;
1201
1202 if (ctx->validation_buffer) {
1203 if (ctx->validation_buffer_length < VALIDATION_SIZE) {
1204 apr_size_t copy_size;
1205
1206 copy_size = VALIDATION_SIZE - ctx->validation_buffer_length;
1207 if (copy_size > ctx->stream.avail_in)
1208 copy_size = ctx->stream.avail_in;
1209 memcpy(ctx->validation_buffer + ctx->validation_buffer_length,
1210 ctx->stream.next_in, copy_size);
1211 /* Saved copy_size bytes */
1212 ctx->stream.avail_in -= copy_size;
1213 ctx->validation_buffer_length += copy_size;
1214 }
1215 if (ctx->stream.avail_in) {
1216 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1217 "Zlib: %d bytes of garbage at the end of "
1218 "compressed stream.", ctx->stream.avail_in);
1219 /*
1220 * There is nothing worth consuming for zlib left, because it is
1221 * either garbage data or the data has been copied to the
1222 * validation buffer (processing validation data is no business
1223 * for zlib). So set ctx->stream.avail_in to zero to indicate
1224 * this to the following while loop.
1225 */
1226 ctx->stream.avail_in = 0;
1227 }
1228 }
1229
1230 zRC = Z_OK;
1231
1232 while (ctx->stream.avail_in != 0) {
1233 if (ctx->stream.avail_out == 0) {
1234
1235 ctx->stream.next_out = ctx->buffer;
1236 len = c->bufferSize - ctx->stream.avail_out;
1237
1238 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
1239 b = apr_bucket_heap_create((char *)ctx->buffer, len,
1240 NULL, f->c->bucket_alloc);
1241 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1242 ctx->stream.avail_out = c->bufferSize;
1243 /* Send what we have right now to the next filter. */
1244 rv = ap_pass_brigade(f->next, ctx->bb);
1245 if (rv != APR_SUCCESS) {
1246 return rv;
1247 }
1248 }
1249
1250 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
1251
1252 if (zRC == Z_STREAM_END) {
1253 /*
1254 * We have inflated all data. Now try to capture the
1255 * validation bytes. We may not have them all available
1256 * right now, but capture what is there.
1257 */
1258 ctx->validation_buffer = apr_pcalloc(f->r->pool,
1259 VALIDATION_SIZE);
1260 if (ctx->stream.avail_in > VALIDATION_SIZE) {
1261 ctx->validation_buffer_length = VALIDATION_SIZE;
1262 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1263 "Zlib: %d bytes of garbage at the end of "
1264 "compressed stream.",
1265 ctx->stream.avail_in - VALIDATION_SIZE);
1266 } else if (ctx->stream.avail_in > 0) {
1267 ctx->validation_buffer_length = ctx->stream.avail_in;
1268 }
1269 if (ctx->validation_buffer_length)
1270 memcpy(ctx->validation_buffer, ctx->stream.next_in,
1271 ctx->validation_buffer_length);
1272 break;
1273 }
1274
1275 if (zRC != Z_OK) {
1276 return APR_EGENERAL;
1277 }
1278 }
1279
1280 apr_bucket_delete(e);
1281 }
1282
1283 apr_brigade_cleanup(bb);
1284 return APR_SUCCESS;
1285 }
1286
1287 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
1288 static void register_hooks(apr_pool_t *p)
1289 {
1290 ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
1291 AP_FTYPE_CONTENT_SET);
1292 ap_register_output_filter("INFLATE", inflate_out_filter, NULL,
1293 AP_FTYPE_RESOURCE-1);
1294 ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
1295 AP_FTYPE_CONTENT_SET);
1296 }
1297
1298 static const command_rec deflate_filter_cmds[] = {
1299 AP_INIT_TAKE12("DeflateFilterNote", deflate_set_note, NULL, RSRC_CONF,
1300 "Set a note to report on compression ratio"),
1301 AP_INIT_TAKE1("DeflateWindowSize", deflate_set_window_size, NULL,
1302 RSRC_CONF, "Set the Deflate window size (1-15)"),
1303 AP_INIT_TAKE1("DeflateBufferSize", deflate_set_buffer_size, NULL, RSRC_CONF,
1304 "Set the Deflate Buffer Size"),
1305 AP_INIT_TAKE1("DeflateMemLevel", deflate_set_memlevel, NULL, RSRC_CONF,
1306 "Set the Deflate Memory Level (1-9)"),
1307 AP_INIT_TAKE1("DeflateCompressionLevel", deflate_set_compressionlevel, NULL, RSRC_CONF,
1308 "Set the Deflate Compression Level (1-9)"),
1309 {NULL}
1310 };
1311
1312 module AP_MODULE_DECLARE_DATA deflate_module = {
1313 STANDARD20_MODULE_STUFF,
1314 NULL, /* dir config creater */
1315 NULL, /* dir merger --- default is to override */
1316 create_deflate_server_config, /* server config */
1317 NULL, /* merge server config */
1318 deflate_filter_cmds, /* command table */
1319 register_hooks /* register hooks */
1320 };

Properties

Name Value
svn:eol-style native

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2