Back to home page

Nginx displayed by LXR

Source navigation ]
Diff markup ]
Identifier search ]
general search ]
 
 
Version: nginx-1.13.12 ]​[ nginx-1.12.2 ]​

0001 
0002 /*
0003  * Copyright (C) Igor Sysoev
0004  * Copyright (C) Maxim Dounin
0005  * Copyright (C) Nginx, Inc.
0006  */
0007 
0008 
0009 #include <ngx_config.h>
0010 #include <ngx_core.h>
0011 #include <ngx_http.h>
0012 
0013 #include <zlib.h>
0014 
0015 
0016 typedef struct {
0017     ngx_flag_t           enable;
0018     ngx_bufs_t           bufs;
0019 } ngx_http_gunzip_conf_t;
0020 
0021 
0022 typedef struct {
0023     ngx_chain_t         *in;
0024     ngx_chain_t         *free;
0025     ngx_chain_t         *busy;
0026     ngx_chain_t         *out;
0027     ngx_chain_t        **last_out;
0028 
0029     ngx_buf_t           *in_buf;
0030     ngx_buf_t           *out_buf;
0031     ngx_int_t            bufs;
0032 
0033     unsigned             started:1;
0034     unsigned             flush:4;
0035     unsigned             redo:1;
0036     unsigned             done:1;
0037     unsigned             nomem:1;
0038 
0039     z_stream             zstream;
0040     ngx_http_request_t  *request;
0041 } ngx_http_gunzip_ctx_t;
0042 
0043 
0044 static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
0045     ngx_http_gunzip_ctx_t *ctx);
0046 static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
0047     ngx_http_gunzip_ctx_t *ctx);
0048 static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
0049     ngx_http_gunzip_ctx_t *ctx);
0050 static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
0051     ngx_http_gunzip_ctx_t *ctx);
0052 static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
0053     ngx_http_gunzip_ctx_t *ctx);
0054 
0055 static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
0056     u_int size);
0057 static void ngx_http_gunzip_filter_free(void *opaque, void *address);
0058 
0059 static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
0060 static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
0061 static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
0062     void *parent, void *child);
0063 
0064 
0065 static ngx_command_t  ngx_http_gunzip_filter_commands[] = {
0066 
0067     { ngx_string("gunzip"),
0068       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
0069       ngx_conf_set_flag_slot,
0070       NGX_HTTP_LOC_CONF_OFFSET,
0071       offsetof(ngx_http_gunzip_conf_t, enable),
0072       NULL },
0073 
0074     { ngx_string("gunzip_buffers"),
0075       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
0076       ngx_conf_set_bufs_slot,
0077       NGX_HTTP_LOC_CONF_OFFSET,
0078       offsetof(ngx_http_gunzip_conf_t, bufs),
0079       NULL },
0080 
0081       ngx_null_command
0082 };
0083 
0084 
0085 static ngx_http_module_t  ngx_http_gunzip_filter_module_ctx = {
0086     NULL,                                  /* preconfiguration */
0087     ngx_http_gunzip_filter_init,           /* postconfiguration */
0088 
0089     NULL,                                  /* create main configuration */
0090     NULL,                                  /* init main configuration */
0091 
0092     NULL,                                  /* create server configuration */
0093     NULL,                                  /* merge server configuration */
0094 
0095     ngx_http_gunzip_create_conf,           /* create location configuration */
0096     ngx_http_gunzip_merge_conf             /* merge location configuration */
0097 };
0098 
0099 
0100 ngx_module_t  ngx_http_gunzip_filter_module = {
0101     NGX_MODULE_V1,
0102     &ngx_http_gunzip_filter_module_ctx,    /* module context */
0103     ngx_http_gunzip_filter_commands,       /* module directives */
0104     NGX_HTTP_MODULE,                       /* module type */
0105     NULL,                                  /* init master */
0106     NULL,                                  /* init module */
0107     NULL,                                  /* init process */
0108     NULL,                                  /* init thread */
0109     NULL,                                  /* exit thread */
0110     NULL,                                  /* exit process */
0111     NULL,                                  /* exit master */
0112     NGX_MODULE_V1_PADDING
0113 };
0114 
0115 
0116 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
0117 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
0118 
0119 
0120 static ngx_int_t
0121 ngx_http_gunzip_header_filter(ngx_http_request_t *r)
0122 {
0123     ngx_http_gunzip_ctx_t   *ctx;
0124     ngx_http_gunzip_conf_t  *conf;
0125 
0126     conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
0127 
0128     /* TODO support multiple content-codings */
0129     /* TODO always gunzip - due to configuration or module request */
0130     /* TODO ignore content encoding? */
0131 
0132     if (!conf->enable
0133         || r->headers_out.content_encoding == NULL
0134         || r->headers_out.content_encoding->value.len != 4
0135         || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
0136                            (u_char *) "gzip", 4) != 0)
0137     {
0138         return ngx_http_next_header_filter(r);
0139     }
0140 
0141     r->gzip_vary = 1;
0142 
0143     if (!r->gzip_tested) {
0144         if (ngx_http_gzip_ok(r) == NGX_OK) {
0145             return ngx_http_next_header_filter(r);
0146         }
0147 
0148     } else if (r->gzip_ok) {
0149         return ngx_http_next_header_filter(r);
0150     }
0151 
0152     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
0153     if (ctx == NULL) {
0154         return NGX_ERROR;
0155     }
0156 
0157     ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
0158 
0159     ctx->request = r;
0160 
0161     r->filter_need_in_memory = 1;
0162 
0163     r->headers_out.content_encoding->hash = 0;
0164     r->headers_out.content_encoding = NULL;
0165 
0166     ngx_http_clear_content_length(r);
0167     ngx_http_clear_accept_ranges(r);
0168     ngx_http_weak_etag(r);
0169 
0170     return ngx_http_next_header_filter(r);
0171 }
0172 
0173 
0174 static ngx_int_t
0175 ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
0176 {
0177     int                     rc;
0178     ngx_uint_t              flush;
0179     ngx_chain_t            *cl;
0180     ngx_http_gunzip_ctx_t  *ctx;
0181 
0182     ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
0183 
0184     if (ctx == NULL || ctx->done) {
0185         return ngx_http_next_body_filter(r, in);
0186     }
0187 
0188     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0189                    "http gunzip filter");
0190 
0191     if (!ctx->started) {
0192         if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
0193             goto failed;
0194         }
0195     }
0196 
0197     if (in) {
0198         if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
0199             goto failed;
0200         }
0201     }
0202 
0203     if (ctx->nomem) {
0204 
0205         /* flush busy buffers */
0206 
0207         if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
0208             goto failed;
0209         }
0210 
0211         cl = NULL;
0212 
0213         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
0214                                 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
0215         ctx->nomem = 0;
0216         flush = 0;
0217 
0218     } else {
0219         flush = ctx->busy ? 1 : 0;
0220     }
0221 
0222     for ( ;; ) {
0223 
0224         /* cycle while we can write to a client */
0225 
0226         for ( ;; ) {
0227 
0228             /* cycle while there is data to feed zlib and ... */
0229 
0230             rc = ngx_http_gunzip_filter_add_data(r, ctx);
0231 
0232             if (rc == NGX_DECLINED) {
0233                 break;
0234             }
0235 
0236             if (rc == NGX_AGAIN) {
0237                 continue;
0238             }
0239 
0240 
0241             /* ... there are buffers to write zlib output */
0242 
0243             rc = ngx_http_gunzip_filter_get_buf(r, ctx);
0244 
0245             if (rc == NGX_DECLINED) {
0246                 break;
0247             }
0248 
0249             if (rc == NGX_ERROR) {
0250                 goto failed;
0251             }
0252 
0253             rc = ngx_http_gunzip_filter_inflate(r, ctx);
0254 
0255             if (rc == NGX_OK) {
0256                 break;
0257             }
0258 
0259             if (rc == NGX_ERROR) {
0260                 goto failed;
0261             }
0262 
0263             /* rc == NGX_AGAIN */
0264         }
0265 
0266         if (ctx->out == NULL && !flush) {
0267             return ctx->busy ? NGX_AGAIN : NGX_OK;
0268         }
0269 
0270         rc = ngx_http_next_body_filter(r, ctx->out);
0271 
0272         if (rc == NGX_ERROR) {
0273             goto failed;
0274         }
0275 
0276         ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
0277                                 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
0278         ctx->last_out = &ctx->out;
0279 
0280         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0281                        "gunzip out: %p", ctx->out);
0282 
0283         ctx->nomem = 0;
0284         flush = 0;
0285 
0286         if (ctx->done) {
0287             return rc;
0288         }
0289     }
0290 
0291     /* unreachable */
0292 
0293 failed:
0294 
0295     ctx->done = 1;
0296 
0297     return NGX_ERROR;
0298 }
0299 
0300 
0301 static ngx_int_t
0302 ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
0303     ngx_http_gunzip_ctx_t *ctx)
0304 {
0305     int  rc;
0306 
0307     ctx->zstream.next_in = Z_NULL;
0308     ctx->zstream.avail_in = 0;
0309 
0310     ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
0311     ctx->zstream.zfree = ngx_http_gunzip_filter_free;
0312     ctx->zstream.opaque = ctx;
0313 
0314     /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
0315     rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
0316 
0317     if (rc != Z_OK) {
0318         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
0319                       "inflateInit2() failed: %d", rc);
0320         return NGX_ERROR;
0321     }
0322 
0323     ctx->started = 1;
0324 
0325     ctx->last_out = &ctx->out;
0326     ctx->flush = Z_NO_FLUSH;
0327 
0328     return NGX_OK;
0329 }
0330 
0331 
0332 static ngx_int_t
0333 ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
0334     ngx_http_gunzip_ctx_t *ctx)
0335 {
0336     if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
0337         return NGX_OK;
0338     }
0339 
0340     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0341                    "gunzip in: %p", ctx->in);
0342 
0343     if (ctx->in == NULL) {
0344         return NGX_DECLINED;
0345     }
0346 
0347     ctx->in_buf = ctx->in->buf;
0348     ctx->in = ctx->in->next;
0349 
0350     ctx->zstream.next_in = ctx->in_buf->pos;
0351     ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
0352 
0353     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0354                    "gunzip in_buf:%p ni:%p ai:%ud",
0355                    ctx->in_buf,
0356                    ctx->zstream.next_in, ctx->zstream.avail_in);
0357 
0358     if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
0359         ctx->flush = Z_FINISH;
0360 
0361     } else if (ctx->in_buf->flush) {
0362         ctx->flush = Z_SYNC_FLUSH;
0363 
0364     } else if (ctx->zstream.avail_in == 0) {
0365         /* ctx->flush == Z_NO_FLUSH */
0366         return NGX_AGAIN;
0367     }
0368 
0369     return NGX_OK;
0370 }
0371 
0372 
0373 static ngx_int_t
0374 ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
0375     ngx_http_gunzip_ctx_t *ctx)
0376 {
0377     ngx_http_gunzip_conf_t  *conf;
0378 
0379     if (ctx->zstream.avail_out) {
0380         return NGX_OK;
0381     }
0382 
0383     conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
0384 
0385     if (ctx->free) {
0386         ctx->out_buf = ctx->free->buf;
0387         ctx->free = ctx->free->next;
0388 
0389         ctx->out_buf->flush = 0;
0390 
0391     } else if (ctx->bufs < conf->bufs.num) {
0392 
0393         ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
0394         if (ctx->out_buf == NULL) {
0395             return NGX_ERROR;
0396         }
0397 
0398         ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
0399         ctx->out_buf->recycled = 1;
0400         ctx->bufs++;
0401 
0402     } else {
0403         ctx->nomem = 1;
0404         return NGX_DECLINED;
0405     }
0406 
0407     ctx->zstream.next_out = ctx->out_buf->pos;
0408     ctx->zstream.avail_out = conf->bufs.size;
0409 
0410     return NGX_OK;
0411 }
0412 
0413 
0414 static ngx_int_t
0415 ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
0416     ngx_http_gunzip_ctx_t *ctx)
0417 {
0418     int           rc;
0419     ngx_buf_t    *b;
0420     ngx_chain_t  *cl;
0421 
0422     ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0423                    "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
0424                    ctx->zstream.next_in, ctx->zstream.next_out,
0425                    ctx->zstream.avail_in, ctx->zstream.avail_out,
0426                    ctx->flush, ctx->redo);
0427 
0428     rc = inflate(&ctx->zstream, ctx->flush);
0429 
0430     if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
0431         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
0432                       "inflate() failed: %d, %d", ctx->flush, rc);
0433         return NGX_ERROR;
0434     }
0435 
0436     ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0437                    "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
0438                    ctx->zstream.next_in, ctx->zstream.next_out,
0439                    ctx->zstream.avail_in, ctx->zstream.avail_out,
0440                    rc);
0441 
0442     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0443                    "gunzip in_buf:%p pos:%p",
0444                    ctx->in_buf, ctx->in_buf->pos);
0445 
0446     if (ctx->zstream.next_in) {
0447         ctx->in_buf->pos = ctx->zstream.next_in;
0448 
0449         if (ctx->zstream.avail_in == 0) {
0450             ctx->zstream.next_in = NULL;
0451         }
0452     }
0453 
0454     ctx->out_buf->last = ctx->zstream.next_out;
0455 
0456     if (ctx->zstream.avail_out == 0) {
0457 
0458         /* zlib wants to output some more data */
0459 
0460         cl = ngx_alloc_chain_link(r->pool);
0461         if (cl == NULL) {
0462             return NGX_ERROR;
0463         }
0464 
0465         cl->buf = ctx->out_buf;
0466         cl->next = NULL;
0467         *ctx->last_out = cl;
0468         ctx->last_out = &cl->next;
0469 
0470         ctx->redo = 1;
0471 
0472         return NGX_AGAIN;
0473     }
0474 
0475     ctx->redo = 0;
0476 
0477     if (ctx->flush == Z_SYNC_FLUSH) {
0478 
0479         ctx->flush = Z_NO_FLUSH;
0480 
0481         cl = ngx_alloc_chain_link(r->pool);
0482         if (cl == NULL) {
0483             return NGX_ERROR;
0484         }
0485 
0486         b = ctx->out_buf;
0487 
0488         if (ngx_buf_size(b) == 0) {
0489 
0490             b = ngx_calloc_buf(ctx->request->pool);
0491             if (b == NULL) {
0492                 return NGX_ERROR;
0493             }
0494 
0495         } else {
0496             ctx->zstream.avail_out = 0;
0497         }
0498 
0499         b->flush = 1;
0500 
0501         cl->buf = b;
0502         cl->next = NULL;
0503         *ctx->last_out = cl;
0504         ctx->last_out = &cl->next;
0505 
0506         return NGX_OK;
0507     }
0508 
0509     if (ctx->flush == Z_FINISH && ctx->zstream.avail_in == 0) {
0510 
0511         if (rc != Z_STREAM_END) {
0512             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
0513                           "inflate() returned %d on response end", rc);
0514             return NGX_ERROR;
0515         }
0516 
0517         if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
0518             return NGX_ERROR;
0519         }
0520 
0521         return NGX_OK;
0522     }
0523 
0524     if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {
0525 
0526         rc = inflateReset(&ctx->zstream);
0527 
0528         if (rc != Z_OK) {
0529             ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
0530                           "inflateReset() failed: %d", rc);
0531             return NGX_ERROR;
0532         }
0533 
0534         ctx->redo = 1;
0535 
0536         return NGX_AGAIN;
0537     }
0538 
0539     if (ctx->in == NULL) {
0540 
0541         b = ctx->out_buf;
0542 
0543         if (ngx_buf_size(b) == 0) {
0544             return NGX_OK;
0545         }
0546 
0547         cl = ngx_alloc_chain_link(r->pool);
0548         if (cl == NULL) {
0549             return NGX_ERROR;
0550         }
0551 
0552         ctx->zstream.avail_out = 0;
0553 
0554         cl->buf = b;
0555         cl->next = NULL;
0556         *ctx->last_out = cl;
0557         ctx->last_out = &cl->next;
0558 
0559         return NGX_OK;
0560     }
0561 
0562     return NGX_AGAIN;
0563 }
0564 
0565 
0566 static ngx_int_t
0567 ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
0568     ngx_http_gunzip_ctx_t *ctx)
0569 {
0570     int           rc;
0571     ngx_buf_t    *b;
0572     ngx_chain_t  *cl;
0573 
0574     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0575                    "gunzip inflate end");
0576 
0577     rc = inflateEnd(&ctx->zstream);
0578 
0579     if (rc != Z_OK) {
0580         ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
0581                       "inflateEnd() failed: %d", rc);
0582         return NGX_ERROR;
0583     }
0584 
0585     b = ctx->out_buf;
0586 
0587     if (ngx_buf_size(b) == 0) {
0588 
0589         b = ngx_calloc_buf(ctx->request->pool);
0590         if (b == NULL) {
0591             return NGX_ERROR;
0592         }
0593     }
0594 
0595     cl = ngx_alloc_chain_link(r->pool);
0596     if (cl == NULL) {
0597         return NGX_ERROR;
0598     }
0599 
0600     cl->buf = b;
0601     cl->next = NULL;
0602     *ctx->last_out = cl;
0603     ctx->last_out = &cl->next;
0604 
0605     b->last_buf = (r == r->main) ? 1 : 0;
0606     b->last_in_chain = 1;
0607     b->sync = 1;
0608 
0609     ctx->done = 1;
0610 
0611     return NGX_OK;
0612 }
0613 
0614 
0615 static void *
0616 ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
0617 {
0618     ngx_http_gunzip_ctx_t *ctx = opaque;
0619 
0620     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
0621                    "gunzip alloc: n:%ud s:%ud",
0622                    items, size);
0623 
0624     return ngx_palloc(ctx->request->pool, items * size);
0625 }
0626 
0627 
0628 static void
0629 ngx_http_gunzip_filter_free(void *opaque, void *address)
0630 {
0631 #if 0
0632     ngx_http_gunzip_ctx_t *ctx = opaque;
0633 
0634     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
0635                    "gunzip free: %p", address);
0636 #endif
0637 }
0638 
0639 
0640 static void *
0641 ngx_http_gunzip_create_conf(ngx_conf_t *cf)
0642 {
0643     ngx_http_gunzip_conf_t  *conf;
0644 
0645     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
0646     if (conf == NULL) {
0647         return NULL;
0648     }
0649 
0650     /*
0651      * set by ngx_pcalloc():
0652      *
0653      *     conf->bufs.num = 0;
0654      */
0655 
0656     conf->enable = NGX_CONF_UNSET;
0657 
0658     return conf;
0659 }
0660 
0661 
0662 static char *
0663 ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
0664 {
0665     ngx_http_gunzip_conf_t *prev = parent;
0666     ngx_http_gunzip_conf_t *conf = child;
0667 
0668     ngx_conf_merge_value(conf->enable, prev->enable, 0);
0669 
0670     ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
0671                               (128 * 1024) / ngx_pagesize, ngx_pagesize);
0672 
0673     return NGX_CONF_OK;
0674 }
0675 
0676 
0677 static ngx_int_t
0678 ngx_http_gunzip_filter_init(ngx_conf_t *cf)
0679 {
0680     ngx_http_next_header_filter = ngx_http_top_header_filter;
0681     ngx_http_top_header_filter = ngx_http_gunzip_header_filter;
0682 
0683     ngx_http_next_body_filter = ngx_http_top_body_filter;
0684     ngx_http_top_body_filter = ngx_http_gunzip_body_filter;
0685 
0686     return NGX_OK;
0687 }