comparison src/http/modules/ngx_http_gunzip_filter_module.c @ 4837:f9ae534ebf4b

Gunzip filter import.
author Maxim Dounin <mdounin@mdounin.ru>
date Mon, 10 Sep 2012 16:52:47 +0000
parents
children 3e789581349d
comparison
equal deleted inserted replaced
4836:e368b822d034 4837:f9ae534ebf4b
1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Maxim Dounin
5 * Copyright (C) Nginx, Inc.
6 */
7
8
9 #include <ngx_config.h>
10 #include <ngx_core.h>
11 #include <ngx_http.h>
12 #include <nginx.h>
13
14 #include <zlib.h>
15
16
17 typedef struct {
18 ngx_flag_t enable;
19 ngx_bufs_t bufs;
20 } ngx_http_gunzip_conf_t;
21
22
23 typedef struct {
24 ngx_chain_t *in;
25 ngx_chain_t *free;
26 ngx_chain_t *busy;
27 ngx_chain_t *out;
28 ngx_chain_t **last_out;
29
30 ngx_buf_t *in_buf;
31 ngx_buf_t *out_buf;
32 ngx_int_t bufs;
33
34 unsigned started:1;
35 unsigned flush:4;
36 unsigned redo:1;
37 unsigned done:1;
38 unsigned nomem:1;
39
40 z_stream zstream;
41 ngx_http_request_t *request;
42 } ngx_http_gunzip_ctx_t;
43
44
45 static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
46 ngx_http_gunzip_ctx_t *ctx);
47 static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
48 ngx_http_gunzip_ctx_t *ctx);
49 static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
50 ngx_http_gunzip_ctx_t *ctx);
51 static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
52 ngx_http_gunzip_ctx_t *ctx);
53 static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
54 ngx_http_gunzip_ctx_t *ctx);
55
56 static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
57 u_int size);
58 static void ngx_http_gunzip_filter_free(void *opaque, void *address);
59
60 static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
61 static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
62 static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
63 void *parent, void *child);
64
65
66 static ngx_command_t ngx_http_gunzip_filter_commands[] = {
67
68 { ngx_string("gunzip"),
69 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
70 ngx_conf_set_flag_slot,
71 NGX_HTTP_LOC_CONF_OFFSET,
72 offsetof(ngx_http_gunzip_conf_t, enable),
73 NULL },
74
75 { ngx_string("gunzip_buffers"),
76 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
77 ngx_conf_set_bufs_slot,
78 NGX_HTTP_LOC_CONF_OFFSET,
79 offsetof(ngx_http_gunzip_conf_t, bufs),
80 NULL },
81
82 ngx_null_command
83 };
84
85
86 static ngx_http_module_t ngx_http_gunzip_filter_module_ctx = {
87 NULL, /* preconfiguration */
88 ngx_http_gunzip_filter_init, /* postconfiguration */
89
90 NULL, /* create main configuration */
91 NULL, /* init main configuration */
92
93 NULL, /* create server configuration */
94 NULL, /* merge server configuration */
95
96 ngx_http_gunzip_create_conf, /* create location configuration */
97 ngx_http_gunzip_merge_conf /* merge location configuration */
98 };
99
100
101 ngx_module_t ngx_http_gunzip_filter_module = {
102 NGX_MODULE_V1,
103 &ngx_http_gunzip_filter_module_ctx, /* module context */
104 ngx_http_gunzip_filter_commands, /* module directives */
105 NGX_HTTP_MODULE, /* module type */
106 NULL, /* init master */
107 NULL, /* init module */
108 NULL, /* init process */
109 NULL, /* init thread */
110 NULL, /* exit thread */
111 NULL, /* exit process */
112 NULL, /* exit master */
113 NGX_MODULE_V1_PADDING
114 };
115
116
117 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
118 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
119
120
121 static ngx_int_t
122 ngx_http_gunzip_header_filter(ngx_http_request_t *r)
123 {
124 ngx_http_gunzip_ctx_t *ctx;
125 ngx_http_gunzip_conf_t *conf;
126
127 conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
128
129 /* TODO support multiple content-codings */
130 /* TODO always gunzip - due to configuration or module request */
131 /* TODO ignore content encoding? */
132
133 if (!conf->enable
134 || r->headers_out.content_encoding == NULL
135 || r->headers_out.content_encoding->value.len != 4
136 || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
137 (u_char *) "gzip", 4) != 0)
138 {
139 return ngx_http_next_header_filter(r);
140 }
141
142 r->gzip_vary = 1;
143
144 if (!r->gzip_tested) {
145 if (ngx_http_gzip_ok(r) == NGX_OK) {
146 return ngx_http_next_header_filter(r);
147 }
148
149 } else if (!r->gzip_ok) {
150 return ngx_http_next_header_filter(r);
151 }
152
153 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
154 if (ctx == NULL) {
155 return NGX_ERROR;
156 }
157
158 ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
159
160 ctx->request = r;
161
162 r->filter_need_in_memory = 1;
163
164 r->headers_out.content_encoding->hash = 0;
165 r->headers_out.content_encoding = NULL;
166
167 ngx_http_clear_content_length(r);
168 ngx_http_clear_accept_ranges(r);
169
170 return ngx_http_next_header_filter(r);
171 }
172
173
174 static ngx_int_t
175 ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
176 {
177 int rc;
178 ngx_chain_t *cl;
179 ngx_http_gunzip_ctx_t *ctx;
180
181 ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
182
183 if (ctx == NULL || ctx->done) {
184 return ngx_http_next_body_filter(r, in);
185 }
186
187 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
188 "http gunzip filter");
189
190 if (!ctx->started) {
191 if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
192 goto failed;
193 }
194 }
195
196 if (in) {
197 if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
198 goto failed;
199 }
200 }
201
202 if (ctx->nomem) {
203
204 /* flush busy buffers */
205
206 if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
207 goto failed;
208 }
209
210 cl = NULL;
211
212 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
213 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
214 ctx->nomem = 0;
215 }
216
217 for ( ;; ) {
218
219 /* cycle while we can write to a client */
220
221 for ( ;; ) {
222
223 /* cycle while there is data to feed zlib and ... */
224
225 rc = ngx_http_gunzip_filter_add_data(r, ctx);
226
227 if (rc == NGX_DECLINED) {
228 break;
229 }
230
231 if (rc == NGX_AGAIN) {
232 continue;
233 }
234
235
236 /* ... there are buffers to write zlib output */
237
238 rc = ngx_http_gunzip_filter_get_buf(r, ctx);
239
240 if (rc == NGX_DECLINED) {
241 break;
242 }
243
244 if (rc == NGX_ERROR) {
245 goto failed;
246 }
247
248 rc = ngx_http_gunzip_filter_inflate(r, ctx);
249
250 if (rc == NGX_OK) {
251 break;
252 }
253
254 if (rc == NGX_ERROR) {
255 goto failed;
256 }
257
258 /* rc == NGX_AGAIN */
259 }
260
261 if (ctx->out == NULL) {
262 return ctx->busy ? NGX_AGAIN : NGX_OK;
263 }
264
265 rc = ngx_http_next_body_filter(r, ctx->out);
266
267 if (rc == NGX_ERROR) {
268 goto failed;
269 }
270
271 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
272 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
273 ctx->last_out = &ctx->out;
274
275 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
276 "gunzip out: %p", ctx->out);
277
278 ctx->nomem = 0;
279
280 if (ctx->done) {
281 return rc;
282 }
283 }
284
285 /* unreachable */
286
287 failed:
288
289 ctx->done = 1;
290
291 return NGX_ERROR;
292 }
293
294
295 static ngx_int_t
296 ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
297 ngx_http_gunzip_ctx_t *ctx)
298 {
299 int rc;
300
301 ctx->zstream.next_in = Z_NULL;
302 ctx->zstream.avail_in = 0;
303
304 ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
305 ctx->zstream.zfree = ngx_http_gunzip_filter_free;
306 ctx->zstream.opaque = ctx;
307
308 /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
309 rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
310
311 if (rc != Z_OK) {
312 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
313 "inflateInit2() failed: %d", rc);
314 return NGX_ERROR;
315 }
316
317 ctx->started = 1;
318
319 ctx->last_out = &ctx->out;
320 ctx->flush = Z_NO_FLUSH;
321
322 return NGX_OK;
323 }
324
325
326 static ngx_int_t
327 ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
328 ngx_http_gunzip_ctx_t *ctx)
329 {
330 if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
331 return NGX_OK;
332 }
333
334 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
335 "gunzip in: %p", ctx->in);
336
337 if (ctx->in == NULL) {
338 return NGX_DECLINED;
339 }
340
341 ctx->in_buf = ctx->in->buf;
342 ctx->in = ctx->in->next;
343
344 ctx->zstream.next_in = ctx->in_buf->pos;
345 ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
346
347 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
348 "gunzip in_buf:%p ni:%p ai:%ud",
349 ctx->in_buf,
350 ctx->zstream.next_in, ctx->zstream.avail_in);
351
352 if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
353 ctx->flush = Z_FINISH;
354
355 } else if (ctx->in_buf->flush) {
356 ctx->flush = Z_SYNC_FLUSH;
357
358 } else if (ctx->zstream.avail_in == 0) {
359 /* ctx->flush == Z_NO_FLUSH */
360 return NGX_AGAIN;
361 }
362
363 return NGX_OK;
364 }
365
366
367 static ngx_int_t
368 ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
369 ngx_http_gunzip_ctx_t *ctx)
370 {
371 ngx_http_gunzip_conf_t *conf;
372
373 if (ctx->zstream.avail_out) {
374 return NGX_OK;
375 }
376
377 conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
378
379 if (ctx->free) {
380 ctx->out_buf = ctx->free->buf;
381 ctx->free = ctx->free->next;
382
383 ctx->out_buf->flush = 0;
384
385 } else if (ctx->bufs < conf->bufs.num) {
386
387 ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
388 if (ctx->out_buf == NULL) {
389 return NGX_ERROR;
390 }
391
392 ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
393 ctx->out_buf->recycled = 1;
394 ctx->bufs++;
395
396 } else {
397 ctx->nomem = 1;
398 return NGX_DECLINED;
399 }
400
401 ctx->zstream.next_out = ctx->out_buf->pos;
402 ctx->zstream.avail_out = conf->bufs.size;
403
404 return NGX_OK;
405 }
406
407
408 static ngx_int_t
409 ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
410 ngx_http_gunzip_ctx_t *ctx)
411 {
412 int rc;
413 ngx_buf_t *b;
414 ngx_chain_t *cl;
415
416 ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
417 "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
418 ctx->zstream.next_in, ctx->zstream.next_out,
419 ctx->zstream.avail_in, ctx->zstream.avail_out,
420 ctx->flush, ctx->redo);
421
422 rc = inflate(&ctx->zstream, ctx->flush);
423
424 if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
425 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
426 "inflate() failed: %d, %d", ctx->flush, rc);
427 return NGX_ERROR;
428 }
429
430 ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
431 "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
432 ctx->zstream.next_in, ctx->zstream.next_out,
433 ctx->zstream.avail_in, ctx->zstream.avail_out,
434 rc);
435
436 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
437 "gunzip in_buf:%p pos:%p",
438 ctx->in_buf, ctx->in_buf->pos);
439
440 if (ctx->zstream.next_in) {
441 ctx->in_buf->pos = ctx->zstream.next_in;
442
443 if (ctx->zstream.avail_in == 0) {
444 ctx->zstream.next_in = NULL;
445 }
446 }
447
448 ctx->out_buf->last = ctx->zstream.next_out;
449
450 if (ctx->zstream.avail_out == 0) {
451
452 /* zlib wants to output some more data */
453
454 cl = ngx_alloc_chain_link(r->pool);
455 if (cl == NULL) {
456 return NGX_ERROR;
457 }
458
459 cl->buf = ctx->out_buf;
460 cl->next = NULL;
461 *ctx->last_out = cl;
462 ctx->last_out = &cl->next;
463
464 ctx->redo = 1;
465
466 return NGX_AGAIN;
467 }
468
469 ctx->redo = 0;
470
471 if (ctx->flush == Z_SYNC_FLUSH) {
472
473 ctx->flush = Z_NO_FLUSH;
474
475 cl = ngx_alloc_chain_link(r->pool);
476 if (cl == NULL) {
477 return NGX_ERROR;
478 }
479
480 b = ctx->out_buf;
481
482 if (ngx_buf_size(b) == 0) {
483
484 b = ngx_calloc_buf(ctx->request->pool);
485 if (b == NULL) {
486 return NGX_ERROR;
487 }
488
489 } else {
490 ctx->zstream.avail_out = 0;
491 }
492
493 b->flush = 1;
494
495 cl->buf = b;
496 cl->next = NULL;
497 *ctx->last_out = cl;
498 ctx->last_out = &cl->next;
499
500 return NGX_OK;
501 }
502
503 if (rc == Z_STREAM_END && ctx->flush == Z_FINISH
504 && ctx->zstream.avail_in == 0)
505 {
506
507 if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
508 return NGX_ERROR;
509 }
510
511 return NGX_OK;
512 }
513
514 if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {
515
516 rc = inflateReset(&ctx->zstream);
517
518 if (rc != Z_OK) {
519 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
520 "inflateReset() failed: %d", rc);
521 return NGX_ERROR;
522 }
523
524 ctx->redo = 1;
525
526 return NGX_AGAIN;
527 }
528
529 if (ctx->in == NULL) {
530
531 b = ctx->out_buf;
532
533 if (ngx_buf_size(b) == 0) {
534 return NGX_OK;
535 }
536
537 cl = ngx_alloc_chain_link(r->pool);
538 if (cl == NULL) {
539 return NGX_ERROR;
540 }
541
542 ctx->zstream.avail_out = 0;
543
544 cl->buf = b;
545 cl->next = NULL;
546 *ctx->last_out = cl;
547 ctx->last_out = &cl->next;
548
549 return NGX_OK;
550 }
551
552 return NGX_AGAIN;
553 }
554
555
556 static ngx_int_t
557 ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
558 ngx_http_gunzip_ctx_t *ctx)
559 {
560 int rc;
561 ngx_buf_t *b;
562 ngx_chain_t *cl;
563
564 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
565 "gunzip inflate end");
566
567 rc = inflateEnd(&ctx->zstream);
568
569 if (rc != Z_OK) {
570 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
571 "inflateEnd() failed: %d", rc);
572 return NGX_ERROR;
573 }
574
575 b = ctx->out_buf;
576
577 if (ngx_buf_size(b) == 0) {
578
579 b = ngx_calloc_buf(ctx->request->pool);
580 if (b == NULL) {
581 return NGX_ERROR;
582 }
583 }
584
585 cl = ngx_alloc_chain_link(r->pool);
586 if (cl == NULL) {
587 return NGX_ERROR;
588 }
589
590 cl->buf = b;
591 cl->next = NULL;
592 *ctx->last_out = cl;
593 ctx->last_out = &cl->next;
594
595 b->last_buf = (r == r->main) ? 1 : 0;
596 b->last_in_chain = 1;
597 b->sync = 1;
598
599 ctx->done = 1;
600
601 return NGX_OK;
602 }
603
604
605 static void *
606 ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
607 {
608 ngx_http_gunzip_ctx_t *ctx = opaque;
609
610 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
611 "gunzip alloc: n:%ud s:%ud",
612 items, size);
613
614 return ngx_palloc(ctx->request->pool, items * size);
615 }
616
617
618 static void
619 ngx_http_gunzip_filter_free(void *opaque, void *address)
620 {
621 #if 0
622 ngx_http_gunzip_ctx_t *ctx = opaque;
623
624 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
625 "gunzip free: %p", address);
626 #endif
627 }
628
629
630 static void *
631 ngx_http_gunzip_create_conf(ngx_conf_t *cf)
632 {
633 ngx_http_gunzip_conf_t *conf;
634
635 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
636 if (conf == NULL) {
637 return NULL;
638 }
639
640 /*
641 * set by ngx_pcalloc():
642 *
643 * conf->bufs.num = 0;
644 */
645
646 conf->enable = NGX_CONF_UNSET;
647
648 return conf;
649 }
650
651
652 static char *
653 ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
654 {
655 ngx_http_gunzip_conf_t *prev = parent;
656 ngx_http_gunzip_conf_t *conf = child;
657
658 ngx_conf_merge_value(conf->enable, prev->enable, 0);
659
660 ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
661 (128 * 1024) / ngx_pagesize, ngx_pagesize);
662
663 return NGX_CONF_OK;
664 }
665
666
667 static ngx_int_t
668 ngx_http_gunzip_filter_init(ngx_conf_t *cf)
669 {
670 ngx_http_next_header_filter = ngx_http_top_header_filter;
671 ngx_http_top_header_filter = ngx_http_gunzip_header_filter;
672
673 ngx_http_next_body_filter = ngx_http_top_body_filter;
674 ngx_http_top_body_filter = ngx_http_gunzip_body_filter;
675
676 return NGX_OK;
677 }