]> granicus.if.org Git - apache/blob - modules/filters/mod_deflate.c
Use the ap_is_initial_req() function to test for a subrequest instead of using r...
[apache] / modules / filters / mod_deflate.c
1 /* Copyright 2002-2004 The Apache Software Foundation
2  *
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 /*
17  * mod_deflate.c: Perform deflate transfer-encoding on the fly
18  *
19  * Written by Ian Holsman, Justin Erenkrantz, and Nick Kew
20  */
21
22 /*
23  * Portions of this software are based upon zlib code by Jean-loup Gailly
24  * (zlib functions gz_open and gzwrite, check_header)
25  */
26
27 /* zlib flags */
28 #define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
29 #define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
30 #define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
31 #define ORIG_NAME    0x08 /* bit 3 set: original file name present */
32 #define COMMENT      0x10 /* bit 4 set: file comment present */
33 #define RESERVED     0xE0 /* bits 5..7: reserved */
34
35
36 #include "httpd.h"
37 #include "http_config.h"
38 #include "http_log.h"
39 #include "apr_strings.h"
40 #include "apr_general.h"
41 #include "util_filter.h"
42 #include "apr_buckets.h"
43 #include "http_request.h"
44 #define APR_WANT_STRFUNC
45 #include "apr_want.h"
46
47 #include "zlib.h"
48
49 #ifdef HAVE_ZUTIL_H
50 #include "zutil.h"
51 #else
52 /* As part of the encoding process, we must send what our OS_CODE is
53  * (or so it seems based on what I can tell of how gzip encoding works).
54  *
55  * zutil.h is not always included with zlib distributions (it is a private
56  * header), so this is straight from zlib 1.1.3's zutil.h.
57  */
58 #ifdef OS2
59 #define OS_CODE  0x06
60 #endif
61
62 #ifdef WIN32 /* Window 95 & Windows NT */
63 #define OS_CODE  0x0b
64 #endif
65
66 #if defined(VAXC) || defined(VMS)
67 #define OS_CODE  0x02
68 #endif
69
70 #ifdef AMIGA
71 #define OS_CODE  0x01
72 #endif
73
74 #if defined(ATARI) || defined(atarist)
75 #define OS_CODE  0x05
76 #endif
77
78 #if defined(MACOS) || defined(TARGET_OS_MAC)
79 #define OS_CODE  0x07
80 #endif
81
82 #ifdef __50SERIES /* Prime/PRIMOS */
83 #define OS_CODE  0x0F
84 #endif
85
86 #ifdef TOPS20
87 #define OS_CODE  0x0a
88 #endif
89
90 #ifndef OS_CODE
91 #define OS_CODE  0x03  /* assume Unix */
92 #endif
93 #endif
94
95 static const char deflateFilterName[] = "DEFLATE";
96 module AP_MODULE_DECLARE_DATA deflate_module;
97
98 typedef struct deflate_filter_config_t
99 {
100     int windowSize;
101     int memlevel;
102     int compressionlevel;
103     apr_size_t bufferSize;
104     char *note_ratio_name;
105     char *note_input_name;
106     char *note_output_name;
107 } deflate_filter_config;
108
109 /* windowsize is negative to suppress Zlib header */
110 #define DEFAULT_COMPRESSION Z_DEFAULT_COMPRESSION
111 #define DEFAULT_WINDOWSIZE -15
112 #define DEFAULT_MEMLEVEL 9
113 #define DEFAULT_BUFFERSIZE 8096
114
115 /* Outputs a long in LSB order to the given file
116  * only the bottom 4 bits are required for the deflate file format.
117  */
118 static void putLong(unsigned char *string, unsigned long x)
119 {
120     string[0] = (unsigned char)(x & 0xff);
121     string[1] = (unsigned char)((x & 0xff00) >> 8);
122     string[2] = (unsigned char)((x & 0xff0000) >> 16);
123     string[3] = (unsigned char)((x & 0xff000000) >> 24);
124 }
125
126 /* Inputs a string and returns a long.
127  */
128 static unsigned long getLong(unsigned char *string)
129 {
130     return ((unsigned long)string[0])
131           | (((unsigned long)string[1]) << 8)
132           | (((unsigned long)string[2]) << 16)
133           | (((unsigned long)string[3]) << 24);
134 }
135
136 static void *create_deflate_server_config(apr_pool_t *p, server_rec *s)
137 {
138     deflate_filter_config *c = apr_pcalloc(p, sizeof *c);
139
140     c->memlevel   = DEFAULT_MEMLEVEL;
141     c->windowSize = DEFAULT_WINDOWSIZE;
142     c->bufferSize = DEFAULT_BUFFERSIZE;
143     c->compressionlevel = DEFAULT_COMPRESSION;
144
145     return c;
146 }
147
148 static const char *deflate_set_window_size(cmd_parms *cmd, void *dummy,
149                                            const char *arg)
150 {
151     deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
152                                                     &deflate_module);
153     int i;
154
155     i = atoi(arg);
156
157     if (i < 1 || i > 15)
158         return "DeflateWindowSize must be between 1 and 15";
159
160     c->windowSize = i * -1;
161
162     return NULL;
163 }
164
165 static const char *deflate_set_buffer_size(cmd_parms *cmd, void *dummy,
166                                            const char *arg)
167 {
168     deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
169                                                     &deflate_module);
170     int n = atoi(arg);
171
172     if (n <= 0) {
173         return "DeflateBufferSize should be positive";
174     }
175
176     c->bufferSize = (apr_size_t)n;
177
178     return NULL;
179 }
180 static const char *deflate_set_note(cmd_parms *cmd, void *dummy,
181                                     const char *arg1, const char *arg2)
182 {
183     deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
184                                                     &deflate_module);
185     
186     if (arg2 == NULL) {
187         c->note_ratio_name = apr_pstrdup(cmd->pool, arg1);
188     }
189     else if (!strcasecmp(arg1, "ratio")) {
190         c->note_ratio_name = apr_pstrdup(cmd->pool, arg2);
191     }
192     else if (!strcasecmp(arg1, "input")) {
193         c->note_input_name = apr_pstrdup(cmd->pool, arg2);
194     }
195     else if (!strcasecmp(arg1, "output")) {
196         c->note_output_name = apr_pstrdup(cmd->pool, arg2);
197     }
198     else {
199         return apr_psprintf(cmd->pool, "Unknown note type %s", arg1);
200     }
201
202     return NULL;
203 }
204
205 static const char *deflate_set_memlevel(cmd_parms *cmd, void *dummy,
206                                         const char *arg)
207 {
208     deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
209                                                     &deflate_module);
210     int i;
211
212     i = atoi(arg);
213
214     if (i < 1 || i > 9)
215         return "DeflateMemLevel must be between 1 and 9";
216
217     c->memlevel = i;
218
219     return NULL;
220 }
221
222 static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
223                                         const char *arg)
224 {
225     deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
226                                                     &deflate_module);
227     int i;
228
229     i = atoi(arg);
230
231     if (i < 1 || i > 9)
232         return "Compression Level must be between 1 and 9";
233
234     c->compressionlevel = i;
235
236     return NULL;
237 }
238
239 /* magic header */
240 static char deflate_magic[2] = { '\037', '\213' };
241
242 typedef struct deflate_ctx_t
243 {
244     z_stream stream;
245     unsigned char *buffer;
246     unsigned long crc;
247     apr_bucket_brigade *bb, *proc_bb;
248 } deflate_ctx;
249
250 static apr_status_t deflate_out_filter(ap_filter_t *f,
251                                        apr_bucket_brigade *bb)
252 {
253     apr_bucket *e;
254     request_rec *r = f->r;
255     deflate_ctx *ctx = f->ctx;
256     int zRC;
257     deflate_filter_config *c = ap_get_module_config(r->server->module_config,
258                                                     &deflate_module);
259
260     /* Do nothing if asked to filter nothing. */
261     if (APR_BRIGADE_EMPTY(bb)) {
262         return APR_SUCCESS;
263     }
264
265     /* If we don't have a context, we need to ensure that it is okay to send
266      * the deflated content.  If we have a context, that means we've done
267      * this before and we liked it.
268      * This could be not so nice if we always fail.  But, if we succeed,
269      * we're in better shape.
270      */
271     if (!ctx) {
272         char *buf, *token;
273         const char *encoding;
274
275         /* only work on main request/no subrequests */
276         if (!ap_is_initial_req(r)) {
277             ap_remove_output_filter(f);
278             return ap_pass_brigade(f->next, bb);
279         }
280
281         /* some browsers might have problems, so set no-gzip
282          * (with browsermatch) for them
283          */
284         if (apr_table_get(r->subprocess_env, "no-gzip")) {
285             ap_remove_output_filter(f);
286             return ap_pass_brigade(f->next, bb);
287         }
288
289         /* Some browsers might have problems with content types
290          * other than text/html, so set gzip-only-text/html
291          * (with browsermatch) for them
292          */
293         if (r->content_type == NULL
294              || strncmp(r->content_type, "text/html", 9)) {
295             const char *env_value = apr_table_get(r->subprocess_env,
296                                                   "gzip-only-text/html");
297             if ( env_value && (strcmp(env_value,"1") == 0) ) {
298                 ap_remove_output_filter(f);
299                 return ap_pass_brigade(f->next, bb);
300             }            
301         }
302
303         /* Let's see what our current Content-Encoding is.
304          * If it's already encoded, don't compress again.
305          * (We could, but let's not.)
306          */
307         encoding = apr_table_get(r->headers_out, "Content-Encoding");
308         if (encoding) {
309             const char *err_enc;
310
311             err_enc = apr_table_get(r->err_headers_out, "Content-Encoding");
312             if (err_enc) {
313                 encoding = apr_pstrcat(r->pool, encoding, ",", err_enc, NULL);
314             }
315         }
316         else {
317             encoding = apr_table_get(r->err_headers_out, "Content-Encoding");
318         }
319
320         if (r->content_encoding) {
321             encoding = encoding ? apr_pstrcat(r->pool, encoding, ",",
322                                               r->content_encoding, NULL)
323                                 : r->content_encoding;
324         }
325
326         if (encoding) {
327             const char *tmp = encoding;
328
329             token = ap_get_token(r->pool, &tmp, 0);
330             while (token && *token) {
331                 /* stolen from mod_negotiation: */
332                 if (strcmp(token, "identity") && strcmp(token, "7bit") &&
333                     strcmp(token, "8bit") && strcmp(token, "binary")) {
334
335                     ap_remove_output_filter(f);
336                     return ap_pass_brigade(f->next, bb);
337                 }
338
339                 /* Otherwise, skip token */
340                 if (*tmp) {
341                     ++tmp;
342                 }
343                 token = (*tmp) ? ap_get_token(r->pool, &tmp, 0) : NULL;
344             }
345         }
346
347         /* Even if we don't accept this request based on it not having
348          * the Accept-Encoding, we need to note that we were looking
349          * for this header and downstream proxies should be aware of that.
350          */
351         apr_table_setn(r->headers_out, "Vary", "Accept-Encoding");
352
353         /* force-gzip will just force it out regardless if the browser
354          * can actually do anything with it.
355          */
356         if (!apr_table_get(r->subprocess_env, "force-gzip")) {
357             const char *accepts;
358             /* if they don't have the line, then they can't play */
359             accepts = apr_table_get(r->headers_in, "Accept-Encoding");
360             if (accepts == NULL) {
361                 ap_remove_output_filter(f);
362                 return ap_pass_brigade(f->next, bb);
363             }
364
365             token = ap_get_token(r->pool, &accepts, 0);
366             while (token && token[0] && strcasecmp(token, "gzip")) {
367                 /* skip parameters, XXX: ;q=foo evaluation? */
368                 while (*accepts == ';') { 
369                     ++accepts;
370                     token = ap_get_token(r->pool, &accepts, 1);
371                 }
372
373                 /* retrieve next token */
374                 if (*accepts == ',') {
375                     ++accepts;
376                 }
377                 token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
378             }
379
380             /* No acceptable token found. */
381             if (token == NULL || token[0] == '\0') {
382                 ap_remove_output_filter(f);
383                 return ap_pass_brigade(f->next, bb);
384             }
385         }
386
387         /* Deflating a zero-length response would make it longer; the
388          * proxy may pass through an empty response for a 304 too.
389          * So we just need to fix up the headers as if we had a body.
390          */
391         if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
392             if (!encoding || !strcasecmp(encoding, "identity")) {
393                 apr_table_set(r->headers_out, "Content-Encoding", "gzip");
394             }
395             else {
396                 apr_table_merge(r->headers_out, "Content-Encoding", "gzip");
397             }
398             apr_table_unset(r->headers_out, "Content-Length");
399
400             ap_remove_output_filter(f);
401             return ap_pass_brigade(f->next, bb);
402         }
403
404         /* We're cool with filtering this. */
405         ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
406         ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
407         ctx->buffer = apr_palloc(r->pool, c->bufferSize);
408
409         zRC = deflateInit2(&ctx->stream, c->compressionlevel, Z_DEFLATED,
410                            c->windowSize, c->memlevel,
411                            Z_DEFAULT_STRATEGY);
412
413         if (zRC != Z_OK) {
414             f->ctx = NULL;
415             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
416                           "unable to init Zlib: "
417                           "deflateInit2 returned %d: URL %s",
418                           zRC, r->uri);
419             return ap_pass_brigade(f->next, bb);
420         }
421
422         /* RFC 1952 Section 2.3 dictates the gzip header:
423          *
424          * +---+---+---+---+---+---+---+---+---+---+
425          * |ID1|ID2|CM |FLG|     MTIME     |XFL|OS |
426          * +---+---+---+---+---+---+---+---+---+---+
427          *
428          * If we wish to populate in MTIME (as hinted in RFC 1952), do:
429          * putLong(date_array, apr_time_now() / APR_USEC_PER_SEC);
430          * where date_array is a char[4] and then print date_array in the
431          * MTIME position.  WARNING: ENDIANNESS ISSUE HERE.
432          */
433         buf = apr_psprintf(r->pool, "%c%c%c%c%c%c%c%c%c%c", deflate_magic[0],
434                            deflate_magic[1], Z_DEFLATED, 0 /* flags */,
435                            0, 0, 0, 0 /* 4 chars for mtime */,
436                            0 /* xflags */, OS_CODE);
437         e = apr_bucket_pool_create(buf, 10, r->pool, f->c->bucket_alloc);
438         APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
439
440         /* If the entire Content-Encoding is "identity", we can replace it. */
441         if (!encoding || !strcasecmp(encoding, "identity")) {
442             apr_table_setn(r->headers_out, "Content-Encoding", "gzip");
443         }
444         else {
445             apr_table_mergen(r->headers_out, "Content-Encoding", "gzip");
446         }
447         apr_table_unset(r->headers_out, "Content-Length");
448
449         /* initialize deflate output buffer */
450         ctx->stream.next_out = ctx->buffer;
451         ctx->stream.avail_out = c->bufferSize;
452     }
453     
454     while (!APR_BRIGADE_EMPTY(bb))
455     {
456         const char *data;
457         apr_bucket *b;
458         apr_size_t len;
459         int done = 0;
460
461         e = APR_BRIGADE_FIRST(bb);
462
463         if (APR_BUCKET_IS_EOS(e)) {
464             char *buf;
465             unsigned int deflate_len;
466
467             ctx->stream.avail_in = 0; /* should be zero already anyway */
468             for (;;) {
469                 deflate_len = c->bufferSize - ctx->stream.avail_out;
470
471                 if (deflate_len != 0) {
472                     b = apr_bucket_heap_create((char *)ctx->buffer,
473                                                deflate_len, NULL,
474                                                f->c->bucket_alloc);
475                     APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
476                     ctx->stream.next_out = ctx->buffer;
477                     ctx->stream.avail_out = c->bufferSize;
478                 }
479
480                 if (done) {
481                     break;
482                 }
483
484                 zRC = deflate(&ctx->stream, Z_FINISH);
485
486                 if (deflate_len == 0 && zRC == Z_BUF_ERROR) {
487                     zRC = Z_OK;
488                 }
489
490                 done = (ctx->stream.avail_out != 0 || zRC == Z_STREAM_END);
491
492                 if (zRC != Z_OK && zRC != Z_STREAM_END) {
493                     break;
494                 }
495             }
496
497             buf = apr_palloc(r->pool, 8);
498             putLong((unsigned char *)&buf[0], ctx->crc);
499             putLong((unsigned char *)&buf[4], ctx->stream.total_in);
500
501             b = apr_bucket_pool_create(buf, 8, r->pool, f->c->bucket_alloc);
502             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
503             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
504                           "Zlib: Compressed %ld to %ld : URL %s",
505                           ctx->stream.total_in, ctx->stream.total_out, r->uri);
506
507             /* leave notes for logging */
508             if (c->note_input_name) {
509                 apr_table_setn(r->notes, c->note_input_name,
510                                (ctx->stream.total_in > 0)
511                                 ? apr_off_t_toa(r->pool,
512                                                 ctx->stream.total_in)
513                                 : "-");
514             }
515
516             if (c->note_output_name) {
517                 apr_table_setn(r->notes, c->note_output_name,
518                                (ctx->stream.total_in > 0)
519                                 ? apr_off_t_toa(r->pool,
520                                                 ctx->stream.total_out)
521                                 : "-");
522             }
523
524             if (c->note_ratio_name) {
525                 apr_table_setn(r->notes, c->note_ratio_name,
526                                (ctx->stream.total_in > 0)
527                                 ? apr_itoa(r->pool,
528                                            (int)(ctx->stream.total_out
529                                                  * 100
530                                                  / ctx->stream.total_in))
531                                 : "-");
532             }
533
534             deflateEnd(&ctx->stream);
535
536             /* Remove EOS from the old list, and insert into the new. */
537             APR_BUCKET_REMOVE(e);
538             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
539
540             /* Okay, we've seen the EOS.
541              * Time to pass it along down the chain.
542              */
543             return ap_pass_brigade(f->next, ctx->bb);
544         }
545
546         if (APR_BUCKET_IS_FLUSH(e)) {
547             apr_bucket *bkt;
548             apr_status_t rv;
549
550             apr_bucket_delete(e);
551
552             if (ctx->stream.avail_in > 0) {
553                 zRC = deflate(&(ctx->stream), Z_SYNC_FLUSH);
554                 if (zRC != Z_OK) {
555                     return APR_EGENERAL;
556                 }
557             }
558
559             ctx->stream.next_out = ctx->buffer;
560             len = c->bufferSize - ctx->stream.avail_out;
561
562             b = apr_bucket_heap_create((char *)ctx->buffer, len,
563                                        NULL, f->c->bucket_alloc);
564             APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
565             ctx->stream.avail_out = c->bufferSize;
566
567             bkt = apr_bucket_flush_create(f->c->bucket_alloc);
568             APR_BRIGADE_INSERT_TAIL(ctx->bb, bkt);
569             rv = ap_pass_brigade(f->next, ctx->bb);
570             if (rv != APR_SUCCESS) {
571                 return rv;
572             }
573             continue;
574         }
575
576         /* read */
577         apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
578
579         /* This crc32 function is from zlib. */
580         ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
581
582         /* write */
583         ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
584                                                       * but we'll just have to
585                                                       * trust zlib */
586         ctx->stream.avail_in = len;
587
588         while (ctx->stream.avail_in != 0) {
589             if (ctx->stream.avail_out == 0) {
590                 apr_status_t rv;
591
592                 ctx->stream.next_out = ctx->buffer;
593                 len = c->bufferSize - ctx->stream.avail_out;
594
595                 b = apr_bucket_heap_create((char *)ctx->buffer, len,
596                                            NULL, f->c->bucket_alloc);
597                 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
598                 ctx->stream.avail_out = c->bufferSize;
599                 /* Send what we have right now to the next filter. */
600                 rv = ap_pass_brigade(f->next, ctx->bb);
601                 if (rv != APR_SUCCESS) {
602                     return rv;
603                 }
604             }
605
606             zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
607
608             if (zRC != Z_OK)
609                 return APR_EGENERAL;
610         }
611
612         apr_bucket_delete(e);
613     }
614
615     apr_brigade_cleanup(bb);
616     return APR_SUCCESS;
617 }
618
619 /* This is the deflate input filter (inflates).  */
620 static apr_status_t deflate_in_filter(ap_filter_t *f,
621                                       apr_bucket_brigade *bb,
622                                       ap_input_mode_t mode,
623                                       apr_read_type_e block,
624                                       apr_off_t readbytes)
625 {
626     apr_bucket *bkt;
627     request_rec *r = f->r;
628     deflate_ctx *ctx = f->ctx;
629     int zRC;
630     apr_status_t rv;
631     deflate_filter_config *c;
632
633     /* just get out of the way of things we don't want. */
634     if (mode != AP_MODE_READBYTES) {
635         return ap_get_brigade(f->next, bb, mode, block, readbytes);
636     }
637
638     c = ap_get_module_config(r->server->module_config, &deflate_module);
639
640     if (!ctx) {
641         int found = 0;
642         char *token, deflate_hdr[10];
643         const char *encoding;
644         apr_size_t len;
645
646         /* only work on main request/no subrequests */
647         if (!ap_is_initial_req(r)) {
648             ap_remove_input_filter(f);
649             return ap_get_brigade(f->next, bb, mode, block, readbytes);
650         }
651
652         /* Let's see what our current Content-Encoding is.
653          * If gzip is present, don't gzip again.  (We could, but let's not.)
654          */
655         encoding = apr_table_get(r->headers_in, "Content-Encoding");
656         if (encoding) {
657             const char *tmp = encoding;
658
659             token = ap_get_token(r->pool, &tmp, 0);
660             while (token && token[0]) {
661                 if (!strcasecmp(token, "gzip")) {
662                     found = 1;
663                     break;
664                 }
665                 /* Otherwise, skip token */
666                 tmp++;
667                 token = ap_get_token(r->pool, &tmp, 0);
668             }
669         }
670
671         if (found == 0) {
672             ap_remove_input_filter(f);
673             return ap_get_brigade(f->next, bb, mode, block, readbytes);
674         }
675
676         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
677         ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
678         ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
679         ctx->buffer = apr_palloc(r->pool, c->bufferSize);
680
681         rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
682         if (rv != APR_SUCCESS) {
683             return rv;
684         }
685
686         len = 10; 
687         rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len); 
688         if (rv != APR_SUCCESS) {
689             return rv;
690         }
691
692         /* We didn't get the magic bytes. */
693         if (len != 10 ||
694             deflate_hdr[0] != deflate_magic[0] ||
695             deflate_hdr[1] != deflate_magic[1]) {
696             return APR_EGENERAL;
697         }
698
699         /* We can't handle flags for now. */
700         if (deflate_hdr[3] != 0) {
701             return APR_EGENERAL;
702         }
703
704         zRC = inflateInit2(&ctx->stream, c->windowSize);
705
706         if (zRC != Z_OK) {
707             f->ctx = NULL;
708             inflateEnd(&ctx->stream);
709             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
710                           "unable to init Zlib: "
711                           "inflateInit2 returned %d: URL %s",
712                           zRC, r->uri);
713             ap_remove_input_filter(f);
714             return ap_get_brigade(f->next, bb, mode, block, readbytes);
715         }
716
717         /* initialize deflate output buffer */
718         ctx->stream.next_out = ctx->buffer;
719         ctx->stream.avail_out = c->bufferSize;
720
721         apr_brigade_cleanup(ctx->bb);
722     }
723
724     if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
725         rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
726
727         if (rv != APR_SUCCESS) {
728             /* What about APR_EAGAIN errors? */
729             inflateEnd(&ctx->stream);
730             return rv;
731         }
732
733         for (bkt = APR_BRIGADE_FIRST(ctx->bb);
734              bkt != APR_BRIGADE_SENTINEL(ctx->bb);
735              bkt = APR_BUCKET_NEXT(bkt))
736         {
737             const char *data;
738             apr_size_t len;
739
740             /* If we actually see the EOS, that means we screwed up! */
741             if (APR_BUCKET_IS_EOS(bkt)) {
742                 inflateEnd(&ctx->stream);
743                 return APR_EGENERAL;
744             }
745
746             if (APR_BUCKET_IS_FLUSH(bkt)) {
747                 apr_bucket *tmp_heap;
748                 zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
749                 if (zRC != Z_OK) {
750                     inflateEnd(&ctx->stream);
751                     return APR_EGENERAL;
752                 }
753
754                 ctx->stream.next_out = ctx->buffer;
755                 len = c->bufferSize - ctx->stream.avail_out;
756
757                 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
758                 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
759                                                  NULL, f->c->bucket_alloc);
760                 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
761                 ctx->stream.avail_out = c->bufferSize;
762
763                 /* Move everything to the returning brigade. */
764                 APR_BUCKET_REMOVE(bkt);
765                 APR_BRIGADE_CONCAT(bb, ctx->bb);
766                 break;
767             }
768
769             /* read */
770             apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
771
772             /* pass through zlib inflate. */
773             ctx->stream.next_in = (unsigned char *)data;
774             ctx->stream.avail_in = len;
775
776             zRC = Z_OK;
777
778             while (ctx->stream.avail_in != 0) {
779                 if (ctx->stream.avail_out == 0) {
780                     apr_bucket *tmp_heap;
781                     ctx->stream.next_out = ctx->buffer;
782                     len = c->bufferSize - ctx->stream.avail_out;
783
784                     ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
785                     tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
786                                                       NULL, f->c->bucket_alloc);
787                     APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
788                     ctx->stream.avail_out = c->bufferSize;
789                 }
790
791                 zRC = inflate(&ctx->stream, Z_NO_FLUSH);
792
793                 if (zRC == Z_STREAM_END) {
794                     break;
795                 }
796
797                 if (zRC != Z_OK) {
798                     inflateEnd(&ctx->stream);
799                     return APR_EGENERAL;
800                 }
801             }
802             if (zRC == Z_STREAM_END) {
803                 apr_bucket *tmp_heap, *eos;
804
805                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
806                               "Zlib: Inflated %ld to %ld : URL %s",
807                               ctx->stream.total_in, ctx->stream.total_out,
808                               r->uri);
809
810                 len = c->bufferSize - ctx->stream.avail_out;
811
812                 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
813                 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
814                                                   NULL, f->c->bucket_alloc);
815                 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
816                 ctx->stream.avail_out = c->bufferSize;
817
818                 /* Is the remaining 8 bytes already in the avail stream? */
819                 if (ctx->stream.avail_in >= 8) {
820                     unsigned long compCRC, compLen;
821                     compCRC = getLong(ctx->stream.next_in);
822                     if (ctx->crc != compCRC) {
823                         inflateEnd(&ctx->stream);
824                         return APR_EGENERAL;
825                     }
826                     ctx->stream.next_in += 4;
827                     compLen = getLong(ctx->stream.next_in);
828                     if (ctx->stream.total_out != compLen) {
829                         inflateEnd(&ctx->stream);
830                         return APR_EGENERAL;
831                     }
832                 }
833                 else {
834                     /* FIXME: We need to grab the 8 verification bytes
835                      * from the wire! */
836                     inflateEnd(&ctx->stream);
837                     return APR_EGENERAL;
838                 }
839
840                 inflateEnd(&ctx->stream);
841
842                 eos = apr_bucket_eos_create(f->c->bucket_alloc);
843                 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos); 
844                 break;
845             }
846
847         }
848         apr_brigade_cleanup(ctx->bb);
849     }
850
851     /* If we are about to return nothing for a 'blocking' read and we have
852      * some data in our zlib buffer, flush it out so we can return something.
853      */
854     if (block == APR_BLOCK_READ &&
855         APR_BRIGADE_EMPTY(ctx->proc_bb) &&
856         ctx->stream.avail_out < c->bufferSize) {
857         apr_bucket *tmp_heap;
858         apr_size_t len;
859         ctx->stream.next_out = ctx->buffer;
860         len = c->bufferSize - ctx->stream.avail_out;
861
862         ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
863         tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
864                                           NULL, f->c->bucket_alloc);
865         APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
866         ctx->stream.avail_out = c->bufferSize;
867     }
868
869     if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
870         apr_bucket_brigade *newbb;
871
872         /* May return APR_INCOMPLETE which is fine by us. */
873         apr_brigade_partition(ctx->proc_bb, readbytes, &bkt);
874
875         newbb = apr_brigade_split(ctx->proc_bb, bkt);
876         APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
877         APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
878     }
879
880     return APR_SUCCESS;
881 }
882
883
884 /* Filter to inflate for a content-transforming proxy.  */
885 static apr_status_t inflate_out_filter(ap_filter_t *f,
886                                       apr_bucket_brigade *bb)
887 {
888     int zlib_method;
889     int zlib_flags;
890     int deflate_init = 1; 
891     apr_bucket *bkt;
892     request_rec *r = f->r;
893     deflate_ctx *ctx = f->ctx;
894     int zRC;
895     apr_status_t rv;
896     deflate_filter_config *c;
897
898     /* Do nothing if asked to filter nothing. */
899     if (APR_BRIGADE_EMPTY(bb)) {
900         return APR_SUCCESS;
901     }
902
903     c = ap_get_module_config(r->server->module_config, &deflate_module);
904
905     if (!ctx) {
906         int found = 0;
907         char *token;
908         const char *encoding;
909
910         /* only work on main request/no subrequests */
911         if (!ap_is_initial_req(r)) {
912             ap_remove_output_filter(f);
913             return ap_pass_brigade(f->next, bb);
914         }
915
916         /* Let's see what our current Content-Encoding is.
917          * If gzip is present, don't gzip again.  (We could, but let's not.)
918          */
919         encoding = apr_table_get(r->headers_out, "Content-Encoding");
920         if (encoding) {
921             const char *tmp = encoding;
922
923             token = ap_get_token(r->pool, &tmp, 0);
924             while (token && token[0]) {
925                 if (!strcasecmp(token, "gzip")) {
926                     found = 1;
927                     break;
928                 }
929                 /* Otherwise, skip token */
930                 tmp++;
931                 token = ap_get_token(r->pool, &tmp, 0);
932             }
933         }
934
935         if (found == 0) {
936             ap_remove_output_filter(f);
937             return ap_pass_brigade(f->next, bb);
938         }
939         apr_table_unset(r->headers_out, "Content-Encoding");
940
941         /* No need to inflate HEAD or 204/304 */
942         if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
943             ap_remove_output_filter(f);
944             return ap_pass_brigade(f->next, bb);
945         }
946
947
948         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
949         ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
950         ctx->buffer = apr_palloc(r->pool, c->bufferSize);
951
952
953         zRC = inflateInit2(&ctx->stream, c->windowSize);
954
955         if (zRC != Z_OK) {
956             f->ctx = NULL;
957             inflateEnd(&ctx->stream);
958             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
959                           "unable to init Zlib: "
960                           "inflateInit2 returned %d: URL %s",
961                           zRC, r->uri);
962             ap_remove_output_filter(f);
963             return ap_pass_brigade(f->next, bb);
964         }
965
966         /* initialize deflate output buffer */
967         ctx->stream.next_out = ctx->buffer;
968         ctx->stream.avail_out = c->bufferSize;
969
970         deflate_init = 0;
971     }
972
973     for (bkt = APR_BRIGADE_FIRST(bb);
974          bkt != APR_BRIGADE_SENTINEL(bb);
975          bkt = APR_BUCKET_NEXT(bkt))
976     {
977         const char *data;
978         apr_size_t len;
979
980         /* If we actually see the EOS, that means we screwed up! */
981         /* no it doesn't - not in a HEAD or 204/304 */
982         if (APR_BUCKET_IS_EOS(bkt)) {
983             inflateEnd(&ctx->stream);
984             return ap_pass_brigade(f->next, bb);
985         }
986
987         if (APR_BUCKET_IS_FLUSH(bkt)) {
988             apr_bucket *tmp_heap;
989             zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
990             if (zRC != Z_OK) {
991                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
992                               "Inflate error %d on flush", zRC);
993                 inflateEnd(&ctx->stream);
994                 return APR_EGENERAL;
995             }
996
997             ctx->stream.next_out = ctx->buffer;
998             len = c->bufferSize - ctx->stream.avail_out;
999
1000             ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
1001             tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
1002                                              NULL, f->c->bucket_alloc);
1003             APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
1004             ctx->stream.avail_out = c->bufferSize;
1005
1006             /* Move everything to the returning brigade. */
1007             APR_BUCKET_REMOVE(bkt);
1008             break;
1009         }
1010
1011         /* read */
1012         apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
1013
1014         /* first bucket contains zlib header */
1015         if (!deflate_init++) {
1016             if (len < 10) {
1017                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1018                               "Insufficient data for inflate");
1019                 return APR_EGENERAL;
1020             } 
1021             else  {
1022                 zlib_method = data[2];
1023                 zlib_flags = data[3];
1024                 if (zlib_method != Z_DEFLATED) {
1025                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1026                                   "inflate: data not deflated!");
1027                     ap_remove_output_filter(f);
1028                     return ap_pass_brigade(f->next, bb);
1029                 }
1030                 if (data[0] != deflate_magic[0] ||
1031                     data[1] != deflate_magic[1] ||
1032                     (zlib_flags & RESERVED) != 0) {
1033                         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1034                                       "inflate: bad header");
1035                     return APR_EGENERAL ;
1036                 }
1037                 data += 10 ;
1038                 len -= 10 ;
1039            }
1040            if (zlib_flags & EXTRA_FIELD) {
1041                unsigned int bytes = (unsigned int)(data[0]);
1042                bytes += ((unsigned int)(data[1])) << 8;
1043                bytes += 2;
1044                if (len < bytes) {
1045                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1046                                  "inflate: extra field too big (not "
1047                                  "supported)");
1048                    return APR_EGENERAL;
1049                }
1050                data += bytes;
1051                len -= bytes;
1052            }
1053            if (zlib_flags & ORIG_NAME) {
1054                while (len-- && *data++);
1055            }
1056            if (zlib_flags & COMMENT) {
1057                while (len-- && *data++);
1058            }
1059            if (zlib_flags & HEAD_CRC) {
1060                 len -= 2;
1061                 data += 2;
1062            }
1063         }
1064
1065         /* pass through zlib inflate. */
1066         ctx->stream.next_in = (unsigned char *)data;
1067         ctx->stream.avail_in = len;
1068
1069         zRC = Z_OK;
1070
1071         while (ctx->stream.avail_in != 0) {
1072             if (ctx->stream.avail_out == 0) {
1073                 apr_bucket *tmp_heap;
1074                 ctx->stream.next_out = ctx->buffer;
1075                 len = c->bufferSize - ctx->stream.avail_out;
1076
1077                 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
1078                 tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
1079                                                   NULL, f->c->bucket_alloc);
1080                 APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
1081                 ctx->stream.avail_out = c->bufferSize;
1082             }
1083
1084             zRC = inflate(&ctx->stream, Z_NO_FLUSH);
1085
1086             if (zRC == Z_STREAM_END) {
1087                 break;
1088             }
1089
1090             if (zRC != Z_OK) {
1091                     inflateEnd(&ctx->stream);
1092                     return APR_EGENERAL;
1093             }
1094         }
1095         if (zRC == Z_STREAM_END) {
1096             apr_bucket *tmp_heap, *eos;
1097
1098             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1099                           "Zlib: Inflated %ld to %ld : URL %s",
1100                           ctx->stream.total_in, ctx->stream.total_out,
1101                           r->uri);
1102
1103             len = c->bufferSize - ctx->stream.avail_out;
1104
1105             ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
1106             tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
1107                                               NULL, f->c->bucket_alloc);
1108             APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
1109             ctx->stream.avail_out = c->bufferSize;
1110
1111             /* Is the remaining 8 bytes already in the avail stream? */
1112             if (ctx->stream.avail_in >= 8) {
1113                 unsigned long compCRC, compLen;
1114                 compCRC = getLong(ctx->stream.next_in);
1115                 if (ctx->crc != compCRC) {
1116                     inflateEnd(&ctx->stream);
1117                     return APR_EGENERAL;
1118                 }
1119                 ctx->stream.next_in += 4;
1120                 compLen = getLong(ctx->stream.next_in);
1121                 if (ctx->stream.total_out != compLen) {
1122                     inflateEnd(&ctx->stream);
1123                     return APR_EGENERAL;
1124                 }
1125             }
1126             else {
1127                 /* FIXME: We need to grab the 8 verification bytes
1128                  * from the wire! */
1129                 inflateEnd(&ctx->stream);
1130                 return APR_EGENERAL;
1131             }
1132
1133             inflateEnd(&ctx->stream);
1134
1135             eos = apr_bucket_eos_create(f->c->bucket_alloc);
1136             APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos); 
1137             break;
1138         }
1139
1140     }
1141
1142     rv = ap_pass_brigade(f->next, ctx->proc_bb);
1143     apr_brigade_cleanup(ctx->proc_bb);
1144     return rv ;
1145 }
1146
1147 static void register_hooks(apr_pool_t *p)
1148 {
1149     ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
1150                               AP_FTYPE_CONTENT_SET);
1151     ap_register_output_filter("INFLATE", inflate_out_filter, NULL,
1152                               AP_FTYPE_RESOURCE-1);
1153     ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
1154                               AP_FTYPE_CONTENT_SET);
1155 }
1156
1157 static const command_rec deflate_filter_cmds[] = {
1158     AP_INIT_TAKE12("DeflateFilterNote", deflate_set_note, NULL, RSRC_CONF,
1159                   "Set a note to report on compression ratio"),
1160     AP_INIT_TAKE1("DeflateWindowSize", deflate_set_window_size, NULL,
1161                   RSRC_CONF, "Set the Deflate window size (1-15)"),
1162     AP_INIT_TAKE1("DeflateBufferSize", deflate_set_buffer_size, NULL, RSRC_CONF,
1163                   "Set the Deflate Buffer Size"),
1164     AP_INIT_TAKE1("DeflateMemLevel", deflate_set_memlevel, NULL, RSRC_CONF,
1165                   "Set the Deflate Memory Level (1-9)"),
1166     AP_INIT_TAKE1("DeflateCompressionLevel", deflate_set_compressionlevel, NULL, RSRC_CONF,
1167                   "Set the Deflate Compression Level (1-9)"),
1168     {NULL}
1169 };
1170
1171 module AP_MODULE_DECLARE_DATA deflate_module = {
1172     STANDARD20_MODULE_STUFF,
1173     NULL,                         /* dir config creater */
1174     NULL,                         /* dir merger --- default is to override */
1175     create_deflate_server_config, /* server config */
1176     NULL,                         /* merge server config */
1177     deflate_filter_cmds,          /* command table */
1178     register_hooks                /* register hooks */
1179 };