comparison ngx_http_gunzip_filter_module.c @ 0:a75d4ad9c5d2

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