1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 #include <zlib.h>
13 
14 
15 typedef struct {
16     ngx_flag_t           enable;
17     ngx_flag_t           no_buffer;
18 
19     ngx_hash_t           types;
20 
21     ngx_bufs_t           bufs;
22 
23     size_t               postpone_gzipping;
24     ngx_int_t            level;
25     size_t               wbits;
26     size_t               memlevel;
27     ssize_t              min_length;
28 
29     ngx_array_t         *types_keys;
30 } ngx_http_gzip_conf_t;
31 
32 
33 typedef struct {
34     ngx_chain_t         *in;
35     ngx_chain_t         *free;
36     ngx_chain_t         *busy;
37     ngx_chain_t         *out;
38     ngx_chain_t        **last_out;
39 
40     ngx_chain_t         *copied;
41     ngx_chain_t         *copy_buf;
42 
43     ngx_buf_t           *in_buf;
44     ngx_buf_t           *out_buf;
45     ngx_int_t            bufs;
46 
47     void                *preallocated;
48     char                *free_mem;
49     ngx_uint_t           allocated;
50 
51     int                  wbits;
52     int                  memlevel;
53 
54     unsigned             flush:4;
55     unsigned             redo:1;
56     unsigned             done:1;
57     unsigned             nomem:1;
58     unsigned             gzheader:1;
59     unsigned             buffering:1;
60     unsigned             intel:1;
61 
62     size_t               zin;
63     size_t               zout;
64 
65     uint32_t             crc32;
66     z_stream             zstream;
67     ngx_http_request_t  *request;
68 } ngx_http_gzip_ctx_t;
69 
70 
71 #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
72 
73 struct gztrailer {
74     uint32_t  crc32;
75     uint32_t  zlen;
76 };
77 
78 #else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
79 
80 struct gztrailer {
81     u_char  crc32[4];
82     u_char  zlen[4];
83 };
84 
85 #endif
86 
87 
88 static void ngx_http_gzip_filter_memory(ngx_http_request_t *r,
89     ngx_http_gzip_ctx_t *ctx);
90 static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx,
91     ngx_chain_t *in);
92 static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
93     ngx_http_gzip_ctx_t *ctx);
94 static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r,
95     ngx_http_gzip_ctx_t *ctx);
96 static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r,
97     ngx_http_gzip_ctx_t *ctx);
98 static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r,
99     ngx_http_gzip_ctx_t *ctx);
100 static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r,
101     ngx_http_gzip_ctx_t *ctx);
102 static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
103     ngx_http_gzip_ctx_t *ctx);
104 
105 static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
106     u_int size);
107 static void ngx_http_gzip_filter_free(void *opaque, void *address);
108 static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
109     ngx_http_gzip_ctx_t *ctx);
110 
111 static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf);
112 static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
113     ngx_http_variable_value_t *v, uintptr_t data);
114 
115 static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf);
116 static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
117 static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
118     void *parent, void *child);
119 static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data);
120 static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data);
121 
122 
123 static ngx_conf_num_bounds_t  ngx_http_gzip_comp_level_bounds = {
124     ngx_conf_check_num_bounds, 1, 9
125 };
126 
127 static ngx_conf_post_handler_pt  ngx_http_gzip_window_p = ngx_http_gzip_window;
128 static ngx_conf_post_handler_pt  ngx_http_gzip_hash_p = ngx_http_gzip_hash;
129 
130 
131 static ngx_command_t  ngx_http_gzip_filter_commands[] = {
132 
133     { ngx_string("gzip"),
134       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
135                         |NGX_CONF_FLAG,
136       ngx_conf_set_flag_slot,
137       NGX_HTTP_LOC_CONF_OFFSET,
138       offsetof(ngx_http_gzip_conf_t, enable),
139       NULL },
140 
141     { ngx_string("gzip_buffers"),
142       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
143       ngx_conf_set_bufs_slot,
144       NGX_HTTP_LOC_CONF_OFFSET,
145       offsetof(ngx_http_gzip_conf_t, bufs),
146       NULL },
147 
148     { ngx_string("gzip_types"),
149       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
150       ngx_http_types_slot,
151       NGX_HTTP_LOC_CONF_OFFSET,
152       offsetof(ngx_http_gzip_conf_t, types_keys),
153       &ngx_http_html_default_types[0] },
154 
155     { ngx_string("gzip_comp_level"),
156       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
157       ngx_conf_set_num_slot,
158       NGX_HTTP_LOC_CONF_OFFSET,
159       offsetof(ngx_http_gzip_conf_t, level),
160       &ngx_http_gzip_comp_level_bounds },
161 
162     { ngx_string("gzip_window"),
163       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
164       ngx_conf_set_size_slot,
165       NGX_HTTP_LOC_CONF_OFFSET,
166       offsetof(ngx_http_gzip_conf_t, wbits),
167       &ngx_http_gzip_window_p },
168 
169     { ngx_string("gzip_hash"),
170       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
171       ngx_conf_set_size_slot,
172       NGX_HTTP_LOC_CONF_OFFSET,
173       offsetof(ngx_http_gzip_conf_t, memlevel),
174       &ngx_http_gzip_hash_p },
175 
176     { ngx_string("postpone_gzipping"),
177       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
178       ngx_conf_set_size_slot,
179       NGX_HTTP_LOC_CONF_OFFSET,
180       offsetof(ngx_http_gzip_conf_t, postpone_gzipping),
181       NULL },
182 
183     { ngx_string("gzip_no_buffer"),
184       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
185       ngx_conf_set_flag_slot,
186       NGX_HTTP_LOC_CONF_OFFSET,
187       offsetof(ngx_http_gzip_conf_t, no_buffer),
188       NULL },
189 
190     { ngx_string("gzip_min_length"),
191       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
192       ngx_conf_set_size_slot,
193       NGX_HTTP_LOC_CONF_OFFSET,
194       offsetof(ngx_http_gzip_conf_t, min_length),
195       NULL },
196 
197       ngx_null_command
198 };
199 
200 
201 static ngx_http_module_t  ngx_http_gzip_filter_module_ctx = {
202     ngx_http_gzip_add_variables,           /* preconfiguration */
203     ngx_http_gzip_filter_init,             /* postconfiguration */
204 
205     NULL,                                  /* create main configuration */
206     NULL,                                  /* init main configuration */
207 
208     NULL,                                  /* create server configuration */
209     NULL,                                  /* merge server configuration */
210 
211     ngx_http_gzip_create_conf,             /* create location configuration */
212     ngx_http_gzip_merge_conf               /* merge location configuration */
213 };
214 
215 
216 ngx_module_t  ngx_http_gzip_filter_module = {
217     NGX_MODULE_V1,
218     &ngx_http_gzip_filter_module_ctx,      /* module context */
219     ngx_http_gzip_filter_commands,         /* module directives */
220     NGX_HTTP_MODULE,                       /* module type */
221     NULL,                                  /* init master */
222     NULL,                                  /* init module */
223     NULL,                                  /* init process */
224     NULL,                                  /* init thread */
225     NULL,                                  /* exit thread */
226     NULL,                                  /* exit process */
227     NULL,                                  /* exit master */
228     NGX_MODULE_V1_PADDING
229 };
230 
231 
232 static ngx_str_t  ngx_http_gzip_ratio = ngx_string("gzip_ratio");
233 
234 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
235 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
236 
237 static ngx_uint_t  ngx_http_gzip_assume_intel;
238 
239 
240 static ngx_int_t
ngx_http_gzip_header_filter(ngx_http_request_t * r)241 ngx_http_gzip_header_filter(ngx_http_request_t *r)
242 {
243     ngx_table_elt_t       *h;
244     ngx_http_gzip_ctx_t   *ctx;
245     ngx_http_gzip_conf_t  *conf;
246 
247     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
248 
249     if (!conf->enable
250         || (r->headers_out.status != NGX_HTTP_OK
251             && r->headers_out.status != NGX_HTTP_FORBIDDEN
252             && r->headers_out.status != NGX_HTTP_NOT_FOUND)
253         || (r->headers_out.content_encoding
254             && r->headers_out.content_encoding->value.len)
255         || (r->headers_out.content_length_n != -1
256             && r->headers_out.content_length_n < conf->min_length)
257         || ngx_http_test_content_type(r, &conf->types) == NULL
258         || r->header_only)
259     {
260         return ngx_http_next_header_filter(r);
261     }
262 
263     r->gzip_vary = 1;
264 
265 #if (NGX_HTTP_DEGRADATION)
266     {
267     ngx_http_core_loc_conf_t  *clcf;
268 
269     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
270 
271     if (clcf->gzip_disable_degradation && ngx_http_degraded(r)) {
272         return ngx_http_next_header_filter(r);
273     }
274     }
275 #endif
276 
277     if (!r->gzip_tested) {
278         if (ngx_http_gzip_ok(r) != NGX_OK) {
279             return ngx_http_next_header_filter(r);
280         }
281 
282     } else if (!r->gzip_ok) {
283         return ngx_http_next_header_filter(r);
284     }
285 
286     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t));
287     if (ctx == NULL) {
288         return NGX_ERROR;
289     }
290 
291     ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module);
292 
293     ctx->request = r;
294     ctx->buffering = (conf->postpone_gzipping != 0);
295 
296     ngx_http_gzip_filter_memory(r, ctx);
297 
298     h = ngx_list_push(&r->headers_out.headers);
299     if (h == NULL) {
300         return NGX_ERROR;
301     }
302 
303     h->hash = 1;
304     ngx_str_set(&h->key, "Content-Encoding");
305     ngx_str_set(&h->value, "gzip");
306     r->headers_out.content_encoding = h;
307 
308     r->main_filter_need_in_memory = 1;
309 
310     ngx_http_clear_content_length(r);
311     ngx_http_clear_accept_ranges(r);
312     ngx_http_weak_etag(r);
313 
314     return ngx_http_next_header_filter(r);
315 }
316 
317 
318 static ngx_int_t
ngx_http_gzip_body_filter(ngx_http_request_t * r,ngx_chain_t * in)319 ngx_http_gzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
320 {
321     int                   rc;
322     ngx_uint_t            flush;
323     ngx_chain_t          *cl;
324     ngx_http_gzip_ctx_t  *ctx;
325 
326     ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
327 
328     if (ctx == NULL || ctx->done || r->header_only) {
329         return ngx_http_next_body_filter(r, in);
330     }
331 
332     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
333                    "http gzip filter");
334 
335     if (ctx->buffering) {
336 
337         /*
338          * With default memory settings zlib starts to output gzipped data
339          * only after it has got about 90K, so it makes sense to allocate
340          * zlib memory (200-400K) only after we have enough data to compress.
341          * Although we copy buffers, nevertheless for not big responses
342          * this allows to allocate zlib memory, to compress and to output
343          * the response in one step using hot CPU cache.
344          */
345 
346         if (in) {
347             switch (ngx_http_gzip_filter_buffer(ctx, in)) {
348 
349             case NGX_OK:
350                 return NGX_OK;
351 
352             case NGX_DONE:
353                 in = NULL;
354                 break;
355 
356             default:  /* NGX_ERROR */
357                 goto failed;
358             }
359 
360         } else {
361             ctx->buffering = 0;
362         }
363     }
364 
365     if (ctx->preallocated == NULL) {
366         if (ngx_http_gzip_filter_deflate_start(r, ctx) != NGX_OK) {
367             goto failed;
368         }
369     }
370 
371     if (in) {
372         if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
373             goto failed;
374         }
375 
376         r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
377     }
378 
379     if (ctx->nomem) {
380 
381         /* flush busy buffers */
382 
383         if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
384             goto failed;
385         }
386 
387         cl = NULL;
388 
389         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
390                                 (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
391         ctx->nomem = 0;
392         flush = 0;
393 
394     } else {
395         flush = ctx->busy ? 1 : 0;
396     }
397 
398     for ( ;; ) {
399 
400         /* cycle while we can write to a client */
401 
402         for ( ;; ) {
403 
404             /* cycle while there is data to feed zlib and ... */
405 
406             rc = ngx_http_gzip_filter_add_data(r, ctx);
407 
408             if (rc == NGX_DECLINED) {
409                 break;
410             }
411 
412             if (rc == NGX_AGAIN) {
413                 continue;
414             }
415 
416 
417             /* ... there are buffers to write zlib output */
418 
419             rc = ngx_http_gzip_filter_get_buf(r, ctx);
420 
421             if (rc == NGX_DECLINED) {
422                 break;
423             }
424 
425             if (rc == NGX_ERROR) {
426                 goto failed;
427             }
428 
429 
430             rc = ngx_http_gzip_filter_deflate(r, ctx);
431 
432             if (rc == NGX_OK) {
433                 break;
434             }
435 
436             if (rc == NGX_ERROR) {
437                 goto failed;
438             }
439 
440             /* rc == NGX_AGAIN */
441         }
442 
443         if (ctx->out == NULL && !flush) {
444             ngx_http_gzip_filter_free_copy_buf(r, ctx);
445 
446             return ctx->busy ? NGX_AGAIN : NGX_OK;
447         }
448 
449         if (!ctx->gzheader) {
450             if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) {
451                 goto failed;
452             }
453         }
454 
455         rc = ngx_http_next_body_filter(r, ctx->out);
456 
457         if (rc == NGX_ERROR) {
458             goto failed;
459         }
460 
461         ngx_http_gzip_filter_free_copy_buf(r, ctx);
462 
463         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
464                                 (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
465         ctx->last_out = &ctx->out;
466 
467         ctx->nomem = 0;
468         flush = 0;
469 
470         if (ctx->done) {
471             return rc;
472         }
473     }
474 
475     /* unreachable */
476 
477 failed:
478 
479     ctx->done = 1;
480 
481     if (ctx->preallocated) {
482         deflateEnd(&ctx->zstream);
483 
484         ngx_pfree(r->pool, ctx->preallocated);
485     }
486 
487     ngx_http_gzip_filter_free_copy_buf(r, ctx);
488 
489     return NGX_ERROR;
490 }
491 
492 
493 static void
ngx_http_gzip_filter_memory(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)494 ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
495 {
496     int                    wbits, memlevel;
497     ngx_http_gzip_conf_t  *conf;
498 
499     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
500 
501     wbits = conf->wbits;
502     memlevel = conf->memlevel;
503 
504     if (r->headers_out.content_length_n > 0) {
505 
506         /* the actual zlib window size is smaller by 262 bytes */
507 
508         while (r->headers_out.content_length_n < ((1 << (wbits - 1)) - 262)) {
509             wbits--;
510             memlevel--;
511         }
512 
513         if (memlevel < 1) {
514             memlevel = 1;
515         }
516     }
517 
518     ctx->wbits = wbits;
519     ctx->memlevel = memlevel;
520 
521     /*
522      * We preallocate a memory for zlib in one buffer (200K-400K), this
523      * decreases a number of malloc() and free() calls and also probably
524      * decreases a number of syscalls (sbrk()/mmap() and so on).
525      * Besides we free the memory as soon as a gzipping will complete
526      * and do not wait while a whole response will be sent to a client.
527      *
528      * 8K is for zlib deflate_state, it takes
529      *  *) 5816 bytes on i386 and sparc64 (32-bit mode)
530      *  *) 5920 bytes on amd64 and sparc64
531      */
532 
533     if (!ngx_http_gzip_assume_intel) {
534         ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
535 
536     } else {
537         /*
538          * A zlib variant from Intel, https://github.com/jtkukunas/zlib.
539          * It can force window bits to 13 for fast compression level,
540          * on processors with SSE 4.2 it uses 64K hash instead of scaling
541          * it from the specified memory level, and also introduces
542          * 16-byte padding in one out of the two window-sized buffers.
543          */
544 
545         if (conf->level == 1) {
546             wbits = ngx_max(wbits, 13);
547         }
548 
549         ctx->allocated = 8192 + 16 + (1 << (wbits + 2))
550                          + (1 << (ngx_max(memlevel, 8) + 8))
551                          + (1 << (memlevel + 8));
552         ctx->intel = 1;
553     }
554 }
555 
556 
557 static ngx_int_t
ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t * ctx,ngx_chain_t * in)558 ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in)
559 {
560     size_t                 size, buffered;
561     ngx_buf_t             *b, *buf;
562     ngx_chain_t           *cl, **ll;
563     ngx_http_request_t    *r;
564     ngx_http_gzip_conf_t  *conf;
565 
566     r = ctx->request;
567 
568     r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
569 
570     buffered = 0;
571     ll = &ctx->in;
572 
573     for (cl = ctx->in; cl; cl = cl->next) {
574         buffered += cl->buf->last - cl->buf->pos;
575         ll = &cl->next;
576     }
577 
578     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
579 
580     while (in) {
581         cl = ngx_alloc_chain_link(r->pool);
582         if (cl == NULL) {
583             return NGX_ERROR;
584         }
585 
586         b = in->buf;
587 
588         size = b->last - b->pos;
589         buffered += size;
590 
591         if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) {
592             ctx->buffering = 0;
593         }
594 
595         if (ctx->buffering && size) {
596 
597             buf = ngx_create_temp_buf(r->pool, size);
598             if (buf == NULL) {
599                 return NGX_ERROR;
600             }
601 
602             buf->last = ngx_cpymem(buf->pos, b->pos, size);
603             b->pos = b->last;
604 
605             buf->last_buf = b->last_buf;
606             buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
607 
608             cl->buf = buf;
609 
610         } else {
611             cl->buf = b;
612         }
613 
614         *ll = cl;
615         ll = &cl->next;
616         in = in->next;
617     }
618 
619     *ll = NULL;
620 
621     return ctx->buffering ? NGX_OK : NGX_DONE;
622 }
623 
624 
625 static ngx_int_t
ngx_http_gzip_filter_deflate_start(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)626 ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
627     ngx_http_gzip_ctx_t *ctx)
628 {
629     int                    rc;
630     ngx_http_gzip_conf_t  *conf;
631 
632     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
633 
634     ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
635     if (ctx->preallocated == NULL) {
636         return NGX_ERROR;
637     }
638 
639     ctx->free_mem = ctx->preallocated;
640 
641     ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
642     ctx->zstream.zfree = ngx_http_gzip_filter_free;
643     ctx->zstream.opaque = ctx;
644 
645     rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
646                       - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY);
647 
648     if (rc != Z_OK) {
649         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
650                       "deflateInit2() failed: %d", rc);
651         return NGX_ERROR;
652     }
653 
654     ctx->last_out = &ctx->out;
655     ctx->crc32 = crc32(0L, Z_NULL, 0);
656     ctx->flush = Z_NO_FLUSH;
657 
658     return NGX_OK;
659 }
660 
661 
662 static ngx_int_t
ngx_http_gzip_filter_gzheader(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)663 ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
664 {
665     ngx_buf_t      *b;
666     ngx_chain_t    *cl;
667     static u_char  gzheader[10] =
668                                { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
669 
670     b = ngx_calloc_buf(r->pool);
671     if (b == NULL) {
672         return NGX_ERROR;
673     }
674 
675     b->memory = 1;
676     b->pos = gzheader;
677     b->last = b->pos + 10;
678 
679     cl = ngx_alloc_chain_link(r->pool);
680     if (cl == NULL) {
681         return NGX_ERROR;
682     }
683 
684     cl->buf = b;
685     cl->next = ctx->out;
686     ctx->out = cl;
687 
688     ctx->gzheader = 1;
689 
690     return NGX_OK;
691 }
692 
693 
694 static ngx_int_t
ngx_http_gzip_filter_add_data(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)695 ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
696 {
697     ngx_chain_t  *cl;
698 
699     if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
700         return NGX_OK;
701     }
702 
703     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
704                    "gzip in: %p", ctx->in);
705 
706     if (ctx->in == NULL) {
707         return NGX_DECLINED;
708     }
709 
710     if (ctx->copy_buf) {
711 
712         /*
713          * to avoid CPU cache trashing we do not free() just quit buf,
714          * but postpone free()ing after zlib compressing and data output
715          */
716 
717         ctx->copy_buf->next = ctx->copied;
718         ctx->copied = ctx->copy_buf;
719         ctx->copy_buf = NULL;
720     }
721 
722     cl = ctx->in;
723     ctx->in_buf = cl->buf;
724     ctx->in = cl->next;
725 
726     if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) {
727         ctx->copy_buf = cl;
728 
729     } else {
730         ngx_free_chain(r->pool, cl);
731     }
732 
733     ctx->zstream.next_in = ctx->in_buf->pos;
734     ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
735 
736     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
737                    "gzip in_buf:%p ni:%p ai:%ud",
738                    ctx->in_buf,
739                    ctx->zstream.next_in, ctx->zstream.avail_in);
740 
741     if (ctx->in_buf->last_buf) {
742         ctx->flush = Z_FINISH;
743 
744     } else if (ctx->in_buf->flush) {
745         ctx->flush = Z_SYNC_FLUSH;
746     }
747 
748     if (ctx->zstream.avail_in) {
749 
750         ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
751                            ctx->zstream.avail_in);
752 
753     } else if (ctx->flush == Z_NO_FLUSH) {
754         return NGX_AGAIN;
755     }
756 
757     return NGX_OK;
758 }
759 
760 
761 static ngx_int_t
ngx_http_gzip_filter_get_buf(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)762 ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
763 {
764     ngx_chain_t           *cl;
765     ngx_http_gzip_conf_t  *conf;
766 
767     if (ctx->zstream.avail_out) {
768         return NGX_OK;
769     }
770 
771     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
772 
773     if (ctx->free) {
774 
775         cl = ctx->free;
776         ctx->out_buf = cl->buf;
777         ctx->free = cl->next;
778 
779         ngx_free_chain(r->pool, cl);
780 
781     } else if (ctx->bufs < conf->bufs.num) {
782 
783         ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
784         if (ctx->out_buf == NULL) {
785             return NGX_ERROR;
786         }
787 
788         ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
789         ctx->out_buf->recycled = 1;
790         ctx->bufs++;
791 
792     } else {
793         ctx->nomem = 1;
794         return NGX_DECLINED;
795     }
796 
797     ctx->zstream.next_out = ctx->out_buf->pos;
798     ctx->zstream.avail_out = conf->bufs.size;
799 
800     return NGX_OK;
801 }
802 
803 
804 static ngx_int_t
ngx_http_gzip_filter_deflate(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)805 ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
806 {
807     int                    rc;
808     ngx_buf_t             *b;
809     ngx_chain_t           *cl;
810     ngx_http_gzip_conf_t  *conf;
811 
812     ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
813                  "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
814                  ctx->zstream.next_in, ctx->zstream.next_out,
815                  ctx->zstream.avail_in, ctx->zstream.avail_out,
816                  ctx->flush, ctx->redo);
817 
818     rc = deflate(&ctx->zstream, ctx->flush);
819 
820     if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
821         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
822                       "deflate() failed: %d, %d", ctx->flush, rc);
823         return NGX_ERROR;
824     }
825 
826     ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
827                    "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
828                    ctx->zstream.next_in, ctx->zstream.next_out,
829                    ctx->zstream.avail_in, ctx->zstream.avail_out,
830                    rc);
831 
832     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
833                    "gzip in_buf:%p pos:%p",
834                    ctx->in_buf, ctx->in_buf->pos);
835 
836     if (ctx->zstream.next_in) {
837         ctx->in_buf->pos = ctx->zstream.next_in;
838 
839         if (ctx->zstream.avail_in == 0) {
840             ctx->zstream.next_in = NULL;
841         }
842     }
843 
844     ctx->out_buf->last = ctx->zstream.next_out;
845 
846     if (ctx->zstream.avail_out == 0) {
847 
848         /* zlib wants to output some more gzipped data */
849 
850         cl = ngx_alloc_chain_link(r->pool);
851         if (cl == NULL) {
852             return NGX_ERROR;
853         }
854 
855         cl->buf = ctx->out_buf;
856         cl->next = NULL;
857         *ctx->last_out = cl;
858         ctx->last_out = &cl->next;
859 
860         ctx->redo = 1;
861 
862         return NGX_AGAIN;
863     }
864 
865     ctx->redo = 0;
866 
867     if (ctx->flush == Z_SYNC_FLUSH) {
868 
869         ctx->flush = Z_NO_FLUSH;
870 
871         cl = ngx_alloc_chain_link(r->pool);
872         if (cl == NULL) {
873             return NGX_ERROR;
874         }
875 
876         b = ctx->out_buf;
877 
878         if (ngx_buf_size(b) == 0) {
879 
880             b = ngx_calloc_buf(ctx->request->pool);
881             if (b == NULL) {
882                 return NGX_ERROR;
883             }
884 
885         } else {
886             ctx->zstream.avail_out = 0;
887         }
888 
889         b->flush = 1;
890 
891         cl->buf = b;
892         cl->next = NULL;
893         *ctx->last_out = cl;
894         ctx->last_out = &cl->next;
895 
896         r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
897 
898         return NGX_OK;
899     }
900 
901     if (rc == Z_STREAM_END) {
902 
903         if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) {
904             return NGX_ERROR;
905         }
906 
907         return NGX_OK;
908     }
909 
910     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
911 
912     if (conf->no_buffer && ctx->in == NULL) {
913 
914         cl = ngx_alloc_chain_link(r->pool);
915         if (cl == NULL) {
916             return NGX_ERROR;
917         }
918 
919         cl->buf = ctx->out_buf;
920         cl->next = NULL;
921         *ctx->last_out = cl;
922         ctx->last_out = &cl->next;
923 
924         return NGX_OK;
925     }
926 
927     return NGX_AGAIN;
928 }
929 
930 
931 static ngx_int_t
ngx_http_gzip_filter_deflate_end(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)932 ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
933     ngx_http_gzip_ctx_t *ctx)
934 {
935     int                rc;
936     ngx_buf_t         *b;
937     ngx_chain_t       *cl;
938     struct gztrailer  *trailer;
939 
940     ctx->zin = ctx->zstream.total_in;
941     ctx->zout = 10 + ctx->zstream.total_out + 8;
942 
943     rc = deflateEnd(&ctx->zstream);
944 
945     if (rc != Z_OK) {
946         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
947                       "deflateEnd() failed: %d", rc);
948         return NGX_ERROR;
949     }
950 
951     ngx_pfree(r->pool, ctx->preallocated);
952 
953     cl = ngx_alloc_chain_link(r->pool);
954     if (cl == NULL) {
955         return NGX_ERROR;
956     }
957 
958     cl->buf = ctx->out_buf;
959     cl->next = NULL;
960     *ctx->last_out = cl;
961     ctx->last_out = &cl->next;
962 
963     if (ctx->zstream.avail_out >= 8) {
964         trailer = (struct gztrailer *) ctx->out_buf->last;
965         ctx->out_buf->last += 8;
966         ctx->out_buf->last_buf = 1;
967 
968     } else {
969         b = ngx_create_temp_buf(r->pool, 8);
970         if (b == NULL) {
971             return NGX_ERROR;
972         }
973 
974         b->last_buf = 1;
975 
976         cl = ngx_alloc_chain_link(r->pool);
977         if (cl == NULL) {
978             return NGX_ERROR;
979         }
980 
981         cl->buf = b;
982         cl->next = NULL;
983         *ctx->last_out = cl;
984         ctx->last_out = &cl->next;
985         trailer = (struct gztrailer *) b->pos;
986         b->last += 8;
987     }
988 
989 #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
990 
991     trailer->crc32 = ctx->crc32;
992     trailer->zlen = ctx->zin;
993 
994 #else
995 
996     trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
997     trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
998     trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
999     trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
1000 
1001     trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
1002     trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
1003     trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
1004     trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
1005 
1006 #endif
1007 
1008     ctx->zstream.avail_in = 0;
1009     ctx->zstream.avail_out = 0;
1010 
1011     ctx->done = 1;
1012 
1013     r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
1014 
1015     return NGX_OK;
1016 }
1017 
1018 
1019 static void *
ngx_http_gzip_filter_alloc(void * opaque,u_int items,u_int size)1020 ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size)
1021 {
1022     ngx_http_gzip_ctx_t *ctx = opaque;
1023 
1024     void        *p;
1025     ngx_uint_t   alloc;
1026 
1027     alloc = items * size;
1028 
1029     if (items == 1 && alloc % 512 != 0 && alloc < 8192) {
1030 
1031         /*
1032          * The zlib deflate_state allocation, it takes about 6K,
1033          * we allocate 8K.  Other allocations are divisible by 512.
1034          */
1035 
1036         alloc = 8192;
1037     }
1038 
1039     if (alloc <= ctx->allocated) {
1040         p = ctx->free_mem;
1041         ctx->free_mem += alloc;
1042         ctx->allocated -= alloc;
1043 
1044         ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
1045                        "gzip alloc: n:%ud s:%ud a:%ui p:%p",
1046                        items, size, alloc, p);
1047 
1048         return p;
1049     }
1050 
1051     if (ctx->intel) {
1052         ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0,
1053                       "gzip filter failed to use preallocated memory: "
1054                       "%ud of %ui", items * size, ctx->allocated);
1055 
1056     } else {
1057         ngx_http_gzip_assume_intel = 1;
1058     }
1059 
1060     p = ngx_palloc(ctx->request->pool, items * size);
1061 
1062     return p;
1063 }
1064 
1065 
1066 static void
ngx_http_gzip_filter_free(void * opaque,void * address)1067 ngx_http_gzip_filter_free(void *opaque, void *address)
1068 {
1069 #if 0
1070     ngx_http_gzip_ctx_t *ctx = opaque;
1071 
1072     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
1073                    "gzip free: %p", address);
1074 #endif
1075 }
1076 
1077 
1078 static void
ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t * r,ngx_http_gzip_ctx_t * ctx)1079 ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
1080     ngx_http_gzip_ctx_t *ctx)
1081 {
1082     ngx_chain_t  *cl;
1083 
1084     for (cl = ctx->copied; cl; cl = cl->next) {
1085         ngx_pfree(r->pool, cl->buf->start);
1086     }
1087 
1088     ctx->copied = NULL;
1089 }
1090 
1091 
1092 static ngx_int_t
ngx_http_gzip_add_variables(ngx_conf_t * cf)1093 ngx_http_gzip_add_variables(ngx_conf_t *cf)
1094 {
1095     ngx_http_variable_t  *var;
1096 
1097     var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH);
1098     if (var == NULL) {
1099         return NGX_ERROR;
1100     }
1101 
1102     var->get_handler = ngx_http_gzip_ratio_variable;
1103 
1104     return NGX_OK;
1105 }
1106 
1107 
1108 static ngx_int_t
ngx_http_gzip_ratio_variable(ngx_http_request_t * r,ngx_http_variable_value_t * v,uintptr_t data)1109 ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
1110     ngx_http_variable_value_t *v, uintptr_t data)
1111 {
1112     ngx_uint_t            zint, zfrac;
1113     ngx_http_gzip_ctx_t  *ctx;
1114 
1115     ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
1116 
1117     if (ctx == NULL || ctx->zout == 0) {
1118         v->not_found = 1;
1119         return NGX_OK;
1120     }
1121 
1122     v->valid = 1;
1123     v->no_cacheable = 0;
1124     v->not_found = 0;
1125 
1126     v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3);
1127     if (v->data == NULL) {
1128         return NGX_ERROR;
1129     }
1130 
1131     zint = (ngx_uint_t) (ctx->zin / ctx->zout);
1132     zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100);
1133 
1134     if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) {
1135 
1136         /* the rounding, e.g., 2.125 to 2.13 */
1137 
1138         zfrac++;
1139 
1140         if (zfrac > 99) {
1141             zint++;
1142             zfrac = 0;
1143         }
1144     }
1145 
1146     v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data;
1147 
1148     return NGX_OK;
1149 }
1150 
1151 
1152 static void *
ngx_http_gzip_create_conf(ngx_conf_t * cf)1153 ngx_http_gzip_create_conf(ngx_conf_t *cf)
1154 {
1155     ngx_http_gzip_conf_t  *conf;
1156 
1157     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t));
1158     if (conf == NULL) {
1159         return NULL;
1160     }
1161 
1162     /*
1163      * set by ngx_pcalloc():
1164      *
1165      *     conf->bufs.num = 0;
1166      *     conf->types = { NULL };
1167      *     conf->types_keys = NULL;
1168      */
1169 
1170     conf->enable = NGX_CONF_UNSET;
1171     conf->no_buffer = NGX_CONF_UNSET;
1172 
1173     conf->postpone_gzipping = NGX_CONF_UNSET_SIZE;
1174     conf->level = NGX_CONF_UNSET;
1175     conf->wbits = NGX_CONF_UNSET_SIZE;
1176     conf->memlevel = NGX_CONF_UNSET_SIZE;
1177     conf->min_length = NGX_CONF_UNSET;
1178 
1179     return conf;
1180 }
1181 
1182 
1183 static char *
ngx_http_gzip_merge_conf(ngx_conf_t * cf,void * parent,void * child)1184 ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1185 {
1186     ngx_http_gzip_conf_t *prev = parent;
1187     ngx_http_gzip_conf_t *conf = child;
1188 
1189     ngx_conf_merge_value(conf->enable, prev->enable, 0);
1190     ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
1191 
1192     ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
1193                               (128 * 1024) / ngx_pagesize, ngx_pagesize);
1194 
1195     ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping,
1196                               0);
1197     ngx_conf_merge_value(conf->level, prev->level, 1);
1198     ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS);
1199     ngx_conf_merge_size_value(conf->memlevel, prev->memlevel,
1200                               MAX_MEM_LEVEL - 1);
1201     ngx_conf_merge_value(conf->min_length, prev->min_length, 20);
1202 
1203     if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
1204                              &prev->types_keys, &prev->types,
1205                              ngx_http_html_default_types)
1206         != NGX_OK)
1207     {
1208         return NGX_CONF_ERROR;
1209     }
1210 
1211     return NGX_CONF_OK;
1212 }
1213 
1214 
1215 static ngx_int_t
ngx_http_gzip_filter_init(ngx_conf_t * cf)1216 ngx_http_gzip_filter_init(ngx_conf_t *cf)
1217 {
1218     ngx_http_next_header_filter = ngx_http_top_header_filter;
1219     ngx_http_top_header_filter = ngx_http_gzip_header_filter;
1220 
1221     ngx_http_next_body_filter = ngx_http_top_body_filter;
1222     ngx_http_top_body_filter = ngx_http_gzip_body_filter;
1223 
1224     return NGX_OK;
1225 }
1226 
1227 
1228 static char *
ngx_http_gzip_window(ngx_conf_t * cf,void * post,void * data)1229 ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
1230 {
1231     size_t *np = data;
1232 
1233     size_t  wbits, wsize;
1234 
1235     wbits = 15;
1236 
1237     for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) {
1238 
1239         if (wsize == *np) {
1240             *np = wbits;
1241 
1242             return NGX_CONF_OK;
1243         }
1244 
1245         wbits--;
1246     }
1247 
1248     return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k";
1249 }
1250 
1251 
1252 static char *
ngx_http_gzip_hash(ngx_conf_t * cf,void * post,void * data)1253 ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
1254 {
1255     size_t *np = data;
1256 
1257     size_t  memlevel, hsize;
1258 
1259     memlevel = 9;
1260 
1261     for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
1262 
1263         if (hsize == *np) {
1264             *np = memlevel;
1265 
1266             return NGX_CONF_OK;
1267         }
1268 
1269         memlevel--;
1270     }
1271 
1272     return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
1273 }
1274