src/http/modules/ngx_http_gunzip_filter_module.c - nginx-1.7.10

Global variables defined

Data types defined

Functions defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Maxim Dounin
  4. * Copyright (C) Nginx, Inc.
  5. */


  6. #include <ngx_config.h>
  7. #include <ngx_core.h>
  8. #include <ngx_http.h>

  9. #include <zlib.h>


  10. typedef struct {
  11.     ngx_flag_t           enable;
  12.     ngx_bufs_t           bufs;
  13. } ngx_http_gunzip_conf_t;


  14. typedef struct {
  15.     ngx_chain_t         *in;
  16.     ngx_chain_t         *free;
  17.     ngx_chain_t         *busy;
  18.     ngx_chain_t         *out;
  19.     ngx_chain_t        **last_out;

  20.     ngx_buf_t           *in_buf;
  21.     ngx_buf_t           *out_buf;
  22.     ngx_int_t            bufs;

  23.     unsigned             started:1;
  24.     unsigned             flush:4;
  25.     unsigned             redo:1;
  26.     unsigned             done:1;
  27.     unsigned             nomem:1;

  28.     z_stream             zstream;
  29.     ngx_http_request_t  *request;
  30. } ngx_http_gunzip_ctx_t;


  31. static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
  32.     ngx_http_gunzip_ctx_t *ctx);
  33. static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
  34.     ngx_http_gunzip_ctx_t *ctx);
  35. static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
  36.     ngx_http_gunzip_ctx_t *ctx);
  37. static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
  38.     ngx_http_gunzip_ctx_t *ctx);
  39. static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
  40.     ngx_http_gunzip_ctx_t *ctx);

  41. static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
  42.     u_int size);
  43. static void ngx_http_gunzip_filter_free(void *opaque, void *address);

  44. static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
  45. static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
  46. static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
  47.     void *parent, void *child);


  48. static ngx_command_t  ngx_http_gunzip_filter_commands[] = {

  49.     { ngx_string("gunzip"),
  50.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
  51.       ngx_conf_set_flag_slot,
  52.       NGX_HTTP_LOC_CONF_OFFSET,
  53.       offsetof(ngx_http_gunzip_conf_t, enable),
  54.       NULL },

  55.     { ngx_string("gunzip_buffers"),
  56.       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
  57.       ngx_conf_set_bufs_slot,
  58.       NGX_HTTP_LOC_CONF_OFFSET,
  59.       offsetof(ngx_http_gunzip_conf_t, bufs),
  60.       NULL },

  61.       ngx_null_command
  62. };


  63. static ngx_http_module_t  ngx_http_gunzip_filter_module_ctx = {
  64.     NULL,                                  /* preconfiguration */
  65.     ngx_http_gunzip_filter_init,           /* postconfiguration */

  66.     NULL,                                  /* create main configuration */
  67.     NULL,                                  /* init main configuration */

  68.     NULL,                                  /* create server configuration */
  69.     NULL,                                  /* merge server configuration */

  70.     ngx_http_gunzip_create_conf,           /* create location configuration */
  71.     ngx_http_gunzip_merge_conf             /* merge location configuration */
  72. };


  73. ngx_module_t  ngx_http_gunzip_filter_module = {
  74.     NGX_MODULE_V1,
  75.     &ngx_http_gunzip_filter_module_ctx,    /* module context */
  76.     ngx_http_gunzip_filter_commands,       /* module directives */
  77.     NGX_HTTP_MODULE,                       /* module type */
  78.     NULL,                                  /* init master */
  79.     NULL,                                  /* init module */
  80.     NULL,                                  /* init process */
  81.     NULL,                                  /* init thread */
  82.     NULL,                                  /* exit thread */
  83.     NULL,                                  /* exit process */
  84.     NULL,                                  /* exit master */
  85.     NGX_MODULE_V1_PADDING
  86. };


  87. static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  88. static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;


  89. static ngx_int_t
  90. ngx_http_gunzip_header_filter(ngx_http_request_t *r)
  91. {
  92.     ngx_http_gunzip_ctx_t   *ctx;
  93.     ngx_http_gunzip_conf_t  *conf;

  94.     conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);

  95.     /* TODO support multiple content-codings */
  96.     /* TODO always gunzip - due to configuration or module request */
  97.     /* TODO ignore content encoding? */

  98.     if (!conf->enable
  99.         || r->headers_out.content_encoding == NULL
  100.         || r->headers_out.content_encoding->value.len != 4
  101.         || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
  102.                            (u_char *) "gzip", 4) != 0)
  103.     {
  104.         return ngx_http_next_header_filter(r);
  105.     }

  106.     r->gzip_vary = 1;

  107.     if (!r->gzip_tested) {
  108.         if (ngx_http_gzip_ok(r) == NGX_OK) {
  109.             return ngx_http_next_header_filter(r);
  110.         }

  111.     } else if (r->gzip_ok) {
  112.         return ngx_http_next_header_filter(r);
  113.     }

  114.     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
  115.     if (ctx == NULL) {
  116.         return NGX_ERROR;
  117.     }

  118.     ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);

  119.     ctx->request = r;

  120.     r->filter_need_in_memory = 1;

  121.     r->headers_out.content_encoding->hash = 0;
  122.     r->headers_out.content_encoding = NULL;

  123.     ngx_http_clear_content_length(r);
  124.     ngx_http_clear_accept_ranges(r);
  125.     ngx_http_weak_etag(r);

  126.     return ngx_http_next_header_filter(r);
  127. }


  128. static ngx_int_t
  129. ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  130. {
  131.     int                     rc;
  132.     ngx_uint_t              flush;
  133.     ngx_chain_t            *cl;
  134.     ngx_http_gunzip_ctx_t  *ctx;

  135.     ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);

  136.     if (ctx == NULL || ctx->done) {
  137.         return ngx_http_next_body_filter(r, in);
  138.     }

  139.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  140.                    "http gunzip filter");

  141.     if (!ctx->started) {
  142.         if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
  143.             goto failed;
  144.         }
  145.     }

  146.     if (in) {
  147.         if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
  148.             goto failed;
  149.         }
  150.     }

  151.     if (ctx->nomem) {

  152.         /* flush busy buffers */

  153.         if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
  154.             goto failed;
  155.         }

  156.         cl = NULL;

  157.         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
  158.                                 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
  159.         ctx->nomem = 0;
  160.         flush = 0;

  161.     } else {
  162.         flush = ctx->busy ? 1 : 0;
  163.     }

  164.     for ( ;; ) {

  165.         /* cycle while we can write to a client */

  166.         for ( ;; ) {

  167.             /* cycle while there is data to feed zlib and ... */

  168.             rc = ngx_http_gunzip_filter_add_data(r, ctx);

  169.             if (rc == NGX_DECLINED) {
  170.                 break;
  171.             }

  172.             if (rc == NGX_AGAIN) {
  173.                 continue;
  174.             }


  175.             /* ... there are buffers to write zlib output */

  176.             rc = ngx_http_gunzip_filter_get_buf(r, ctx);

  177.             if (rc == NGX_DECLINED) {
  178.                 break;
  179.             }

  180.             if (rc == NGX_ERROR) {
  181.                 goto failed;
  182.             }

  183.             rc = ngx_http_gunzip_filter_inflate(r, ctx);

  184.             if (rc == NGX_OK) {
  185.                 break;
  186.             }

  187.             if (rc == NGX_ERROR) {
  188.                 goto failed;
  189.             }

  190.             /* rc == NGX_AGAIN */
  191.         }

  192.         if (ctx->out == NULL && !flush) {
  193.             return ctx->busy ? NGX_AGAIN : NGX_OK;
  194.         }

  195.         rc = ngx_http_next_body_filter(r, ctx->out);

  196.         if (rc == NGX_ERROR) {
  197.             goto failed;
  198.         }

  199.         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
  200.                                 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
  201.         ctx->last_out = &ctx->out;

  202.         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  203.                        "gunzip out: %p", ctx->out);

  204.         ctx->nomem = 0;
  205.         flush = 0;

  206.         if (ctx->done) {
  207.             return rc;
  208.         }
  209.     }

  210.     /* unreachable */

  211. failed:

  212.     ctx->done = 1;

  213.     return NGX_ERROR;
  214. }


  215. static ngx_int_t
  216. ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
  217.     ngx_http_gunzip_ctx_t *ctx)
  218. {
  219.     int  rc;

  220.     ctx->zstream.next_in = Z_NULL;
  221.     ctx->zstream.avail_in = 0;

  222.     ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
  223.     ctx->zstream.zfree = ngx_http_gunzip_filter_free;
  224.     ctx->zstream.opaque = ctx;

  225.     /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
  226.     rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);

  227.     if (rc != Z_OK) {
  228.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
  229.                       "inflateInit2() failed: %d", rc);
  230.         return NGX_ERROR;
  231.     }

  232.     ctx->started = 1;

  233.     ctx->last_out = &ctx->out;
  234.     ctx->flush = Z_NO_FLUSH;

  235.     return NGX_OK;
  236. }


  237. static ngx_int_t
  238. ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
  239.     ngx_http_gunzip_ctx_t *ctx)
  240. {
  241.     if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
  242.         return NGX_OK;
  243.     }

  244.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  245.                    "gunzip in: %p", ctx->in);

  246.     if (ctx->in == NULL) {
  247.         return NGX_DECLINED;
  248.     }

  249.     ctx->in_buf = ctx->in->buf;
  250.     ctx->in = ctx->in->next;

  251.     ctx->zstream.next_in = ctx->in_buf->pos;
  252.     ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;

  253.     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  254.                    "gunzip in_buf:%p ni:%p ai:%ud",
  255.                    ctx->in_buf,
  256.                    ctx->zstream.next_in, ctx->zstream.avail_in);

  257.     if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
  258.         ctx->flush = Z_FINISH;

  259.     } else if (ctx->in_buf->flush) {
  260.         ctx->flush = Z_SYNC_FLUSH;

  261.     } else if (ctx->zstream.avail_in == 0) {
  262.         /* ctx->flush == Z_NO_FLUSH */
  263.         return NGX_AGAIN;
  264.     }

  265.     return NGX_OK;
  266. }


  267. static ngx_int_t
  268. ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
  269.     ngx_http_gunzip_ctx_t *ctx)
  270. {
  271.     ngx_http_gunzip_conf_t  *conf;

  272.     if (ctx->zstream.avail_out) {
  273.         return NGX_OK;
  274.     }

  275.     conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);

  276.     if (ctx->free) {
  277.         ctx->out_buf = ctx->free->buf;
  278.         ctx->free = ctx->free->next;

  279.         ctx->out_buf->flush = 0;

  280.     } else if (ctx->bufs < conf->bufs.num) {

  281.         ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
  282.         if (ctx->out_buf == NULL) {
  283.             return NGX_ERROR;
  284.         }

  285.         ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
  286.         ctx->out_buf->recycled = 1;
  287.         ctx->bufs++;

  288.     } else {
  289.         ctx->nomem = 1;
  290.         return NGX_DECLINED;
  291.     }

  292.     ctx->zstream.next_out = ctx->out_buf->pos;
  293.     ctx->zstream.avail_out = conf->bufs.size;

  294.     return NGX_OK;
  295. }


  296. static ngx_int_t
  297. ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
  298.     ngx_http_gunzip_ctx_t *ctx)
  299. {
  300.     int           rc;
  301.     ngx_buf_t    *b;
  302.     ngx_chain_t  *cl;

  303.     ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  304.                    "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
  305.                    ctx->zstream.next_in, ctx->zstream.next_out,
  306.                    ctx->zstream.avail_in, ctx->zstream.avail_out,
  307.                    ctx->flush, ctx->redo);

  308.     rc = inflate(&ctx->zstream, ctx->flush);

  309.     if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
  310.         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  311.                       "inflate() failed: %d, %d", ctx->flush, rc);
  312.         return NGX_ERROR;
  313.     }

  314.     ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  315.                    "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
  316.                    ctx->zstream.next_in, ctx->zstream.next_out,
  317.                    ctx->zstream.avail_in, ctx->zstream.avail_out,
  318.                    rc);

  319.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  320.                    "gunzip in_buf:%p pos:%p",
  321.                    ctx->in_buf, ctx->in_buf->pos);

  322.     if (ctx->zstream.next_in) {
  323.         ctx->in_buf->pos = ctx->zstream.next_in;

  324.         if (ctx->zstream.avail_in == 0) {
  325.             ctx->zstream.next_in = NULL;
  326.         }
  327.     }

  328.     ctx->out_buf->last = ctx->zstream.next_out;

  329.     if (ctx->zstream.avail_out == 0) {

  330.         /* zlib wants to output some more data */

  331.         cl = ngx_alloc_chain_link(r->pool);
  332.         if (cl == NULL) {
  333.             return NGX_ERROR;
  334.         }

  335.         cl->buf = ctx->out_buf;
  336.         cl->next = NULL;
  337.         *ctx->last_out = cl;
  338.         ctx->last_out = &cl->next;

  339.         ctx->redo = 1;

  340.         return NGX_AGAIN;
  341.     }

  342.     ctx->redo = 0;

  343.     if (ctx->flush == Z_SYNC_FLUSH) {

  344.         ctx->flush = Z_NO_FLUSH;

  345.         cl = ngx_alloc_chain_link(r->pool);
  346.         if (cl == NULL) {
  347.             return NGX_ERROR;
  348.         }

  349.         b = ctx->out_buf;

  350.         if (ngx_buf_size(b) == 0) {

  351.             b = ngx_calloc_buf(ctx->request->pool);
  352.             if (b == NULL) {
  353.                 return NGX_ERROR;
  354.             }

  355.         } else {
  356.             ctx->zstream.avail_out = 0;
  357.         }

  358.         b->flush = 1;

  359.         cl->buf = b;
  360.         cl->next = NULL;
  361.         *ctx->last_out = cl;
  362.         ctx->last_out = &cl->next;

  363.         return NGX_OK;
  364.     }

  365.     if (ctx->flush == Z_FINISH && ctx->zstream.avail_in == 0) {

  366.         if (rc != Z_STREAM_END) {
  367.             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
  368.                           "inflate() returned %d on response end", rc);
  369.             return NGX_ERROR;
  370.         }

  371.         if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
  372.             return NGX_ERROR;
  373.         }

  374.         return NGX_OK;
  375.     }

  376.     if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {

  377.         rc = inflateReset(&ctx->zstream);

  378.         if (rc != Z_OK) {
  379.             ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
  380.                           "inflateReset() failed: %d", rc);
  381.             return NGX_ERROR;
  382.         }

  383.         ctx->redo = 1;

  384.         return NGX_AGAIN;
  385.     }

  386.     if (ctx->in == NULL) {

  387.         b = ctx->out_buf;

  388.         if (ngx_buf_size(b) == 0) {
  389.             return NGX_OK;
  390.         }

  391.         cl = ngx_alloc_chain_link(r->pool);
  392.         if (cl == NULL) {
  393.             return NGX_ERROR;
  394.         }

  395.         ctx->zstream.avail_out = 0;

  396.         cl->buf = b;
  397.         cl->next = NULL;
  398.         *ctx->last_out = cl;
  399.         ctx->last_out = &cl->next;

  400.         return NGX_OK;
  401.     }

  402.     return NGX_AGAIN;
  403. }


  404. static ngx_int_t
  405. ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
  406.     ngx_http_gunzip_ctx_t *ctx)
  407. {
  408.     int           rc;
  409.     ngx_buf_t    *b;
  410.     ngx_chain_t  *cl;

  411.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  412.                    "gunzip inflate end");

  413.     rc = inflateEnd(&ctx->zstream);

  414.     if (rc != Z_OK) {
  415.         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
  416.                       "inflateEnd() failed: %d", rc);
  417.         return NGX_ERROR;
  418.     }

  419.     b = ctx->out_buf;

  420.     if (ngx_buf_size(b) == 0) {

  421.         b = ngx_calloc_buf(ctx->request->pool);
  422.         if (b == NULL) {
  423.             return NGX_ERROR;
  424.         }
  425.     }

  426.     cl = ngx_alloc_chain_link(r->pool);
  427.     if (cl == NULL) {
  428.         return NGX_ERROR;
  429.     }

  430.     cl->buf = b;
  431.     cl->next = NULL;
  432.     *ctx->last_out = cl;
  433.     ctx->last_out = &cl->next;

  434.     b->last_buf = (r == r->main) ? 1 : 0;
  435.     b->last_in_chain = 1;
  436.     b->sync = 1;

  437.     ctx->done = 1;

  438.     return NGX_OK;
  439. }


  440. static void *
  441. ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
  442. {
  443.     ngx_http_gunzip_ctx_t *ctx = opaque;

  444.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
  445.                    "gunzip alloc: n:%ud s:%ud",
  446.                    items, size);

  447.     return ngx_palloc(ctx->request->pool, items * size);
  448. }


  449. static void
  450. ngx_http_gunzip_filter_free(void *opaque, void *address)
  451. {
  452. #if 0
  453.     ngx_http_gunzip_ctx_t *ctx = opaque;

  454.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
  455.                    "gunzip free: %p", address);
  456. #endif
  457. }


  458. static void *
  459. ngx_http_gunzip_create_conf(ngx_conf_t *cf)
  460. {
  461.     ngx_http_gunzip_conf_t  *conf;

  462.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
  463.     if (conf == NULL) {
  464.         return NULL;
  465.     }

  466.     /*
  467.      * set by ngx_pcalloc():
  468.      *
  469.      *     conf->bufs.num = 0;
  470.      */

  471.     conf->enable = NGX_CONF_UNSET;

  472.     return conf;
  473. }


  474. static char *
  475. ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
  476. {
  477.     ngx_http_gunzip_conf_t *prev = parent;
  478.     ngx_http_gunzip_conf_t *conf = child;

  479.     ngx_conf_merge_value(conf->enable, prev->enable, 0);

  480.     ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
  481.                               (128 * 1024) / ngx_pagesize, ngx_pagesize);

  482.     return NGX_CONF_OK;
  483. }


  484. static ngx_int_t
  485. ngx_http_gunzip_filter_init(ngx_conf_t *cf)
  486. {
  487.     ngx_http_next_header_filter = ngx_http_top_header_filter;
  488.     ngx_http_top_header_filter = ngx_http_gunzip_header_filter;

  489.     ngx_http_next_body_filter = ngx_http_top_body_filter;
  490.     ngx_http_top_body_filter = ngx_http_gunzip_body_filter;

  491.     return NGX_OK;
  492. }