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