[PATCH] deflate support for gunzip module

Maxim Dounin mdounin at mdounin.ru
Fri Feb 25 15:20:55 MSK 2011


Hello!

On Fri, Feb 25, 2011 at 12:11:26PM +0600, Serge Sitnikov wrote:

> Adds deflate decompression support for gunzip module:

Short answer:

No.

Long answer:

There are multiple issues with the patch itself, but more 
importantly - there is a big issue with deflate itself.  See below 
for more details.

> 
> --- ngx_http_gunzip_filter_module.c
> +++ ngx_http_gunzip_filter_module.c	2011-02-24 09:07:08.000000000 +0100
> @@ -30,6 +30,8 @@
>      ngx_buf_t           *out_buf;
>      ngx_int_t            bufs;
> 
> +    unsigned             deflate:1;
> +
>      unsigned             started:1;
>      unsigned             flush:4;
>      unsigned             redo:1;
> @@ -122,42 +124,45 @@
>  {
>      ngx_http_gunzip_ctx_t   *ctx;
>      ngx_http_gunzip_conf_t  *conf;
> +    int deflate = 0, client_deflate = 0;
> 
>      conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
> 
> -    /* TODO support multiple content-codings */
> +    /* TODO support even more encodings */

This comment was about correctly handling multiple content-codings 
specified in Content-Encoding header, not about supporting more 
encodings.

>      /* TODO always gunzip - due to configuration or module request */
>      /* TODO ignore content encoding? */
> 
> -    if (!conf->enable
> -        || r->headers_out.content_encoding == NULL
> -        || r->headers_out.content_encoding->value.len != 4
> -        || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
> -                           (u_char *) "gzip", 4) != 0)
> -    {
> +    if (!conf->enable || r->headers_out.content_encoding == NULL) {
>          return ngx_http_next_header_filter(r);
> +    } else if (r->headers_out.content_encoding->value.len != 4 ||
> +        ngx_strncasecmp(r->headers_out.content_encoding->value.data,
> (u_char *) "gzip", 4) != 0)
> +    {
> +        deflate = r->headers_out.content_encoding->value.len == 7 &&
> +            ngx_strncasecmp(r->headers_out.content_encoding->value.data,
> (u_char *) "deflate", 7) == 0;
> +
> +        if (!deflate) return ngx_http_next_header_filter(r);
> +        else client_deflate = r->headers_in.accept_encoding != NULL ?
> +            ngx_strcasestrn(r->headers_in.accept_encoding->value.data,
> "deflate", 7 - 1) != NULL : 0;

Checking for accept encoding to see if encoding may be actually 
sent to client isn't really enough.  Gzip has lots of options for 
reason.

>      }
> 
>  #if (nginx_version >= 8025 || (nginx_version >= 7065 && nginx_version < 8000))
> 
>      r->gzip_vary = 1;
> +    int ok = r->gzip_tested ? r->gzip_ok : ngx_http_gzip_ok(r) == NGX_OK;
> 
> -    if (!r->gzip_tested) {
> -        if (ngx_http_gzip_ok(r) == NGX_OK) {
> -            return ngx_http_next_header_filter(r);
> -        }
> +#else
> 
> -    } else if (!r->gzip_ok) {
> -        return ngx_http_next_header_filter(r);
> -    }
> +    int ok = ngx_http_gzip_ok(r) == NGX_OK;
> 
> -#else
> +#endif
> 
> -    if (ngx_http_gzip_ok(r) == NGX_OK) {
> -        return ngx_http_next_header_filter(r);
> +    if (deflate) {
> +        if (ok && client_deflate) return ngx_http_next_header_filter(r);
> +    } else {
> +        if (ok) return ngx_http_next_header_filter(r);
>      }
> 
> -#endif
> +    // --

Don't even talking about style.

> 
>      ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
>      if (ctx == NULL) {
> @@ -167,6 +172,7 @@
>      ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
> 
>      ctx->request = r;
> +    ctx->deflate = deflate;
> 
>      r->filter_need_in_memory = 1;
> 
> @@ -314,8 +320,8 @@
>      ctx->zstream.zfree = ngx_http_gunzip_filter_free;
>      ctx->zstream.opaque = ctx;
> 
> -    /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
> -    rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
> +    /* windowBits +32 to auto decode gzip and deflate */
> +    rc = inflateInit2(&ctx->zstream, MAX_WBITS + 32);

1. Zlib version was specified on purpose.

2. This will allow gzip to be decoded with Content-Encoding set to 
deflate and vice versa, which is not a good thing.

> 
>      if (rc != Z_OK) {
>          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
> @@ -328,6 +334,33 @@
>      ctx->last_out = &ctx->out;
>      ctx->flush = Z_NO_FLUSH;
> 
> +    // --
> +
> +    if (!ctx->deflate) return NGX_OK;
> +
> +    /* Header inflation should not produce any output, so all
> +       these hacks below are valid. */
> +
> +    static unsigned char deflate_header[] = { 0x78, 0x9C };
> +    unsigned char temp;
> +
> +    ctx->zstream.next_in = deflate_header;
> +    ctx->zstream.avail_in = 2;
> +    ctx->zstream.next_out = &temp;
> +    ctx->zstream.avail_out = 1;

And, finally, here is key point why deflate isn't supported in 
nginx at all and I don't plan to implement it in gunzip as well:

Correct response with Content-Encoding deflate should have zlib 
stream in it, and an attempt to to add header indicate that you 
are trying to parse incorrect responses.  This in turn will
render correct responses unparseable.

Moreover, it's impossible to actually return correct deflate 
responses in real world - as many browsers (notably IE) implement 
deflate incorrectly.  Parsing should be possible, but I believe 
better aproach is to don't touch this piece at all as nginx did 
for years.

More details may be found here:

http://trac.tools.ietf.org/wg/httpbis/trac/ticket/73
http://lists.w3.org/Archives/Public/ietf-http-wg/2010JanMar/thread.html#msg324

> +
> +    rc = inflate(&ctx->zstream, Z_NO_FLUSH);
> +    if (rc != Z_OK) {
> +        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
> +            "deflate: inflate() failed: %d, %d", ctx->flush, rc);
> +        return NGX_ERROR;
> +    }
> +
> +    ctx->zstream.next_in = Z_NULL;
> +    ctx->zstream.avail_in = 0;
> +    ctx->zstream.next_out = Z_NULL;
> +    ctx->zstream.avail_out = 0;
> +
>      return NGX_OK;
>  }

Maxim Dounin



More information about the nginx-devel mailing list