/*
* Copyright (C) Alejandro Colomar
* Copyright (C) NGINX, Inc.
*/
#include "nxt_http_compress_gzip.h"
#include <stddef.h>
#include <stdint.h>
#include <nxt_unit_bit.h>
#include <nxt_unit_cdefs.h>
#include "nxt_buf.h"
#include "nxt_clang.h"
#include "nxt_errno.h"
#include "nxt_http.h"
#include "nxt_http_compress.h"
#include "nxt_http_filter.h"
#include "nxt_main.h"
#include "nxt_mp.h"
#include "nxt_router.h"
#include "nxt_string.h"
#include "nxt_types.h"
#if (NXT_WITH_ZLIB || __has_include(<zlib.h>))
#include <zlib.h>
#endif
typedef struct nxt_http_compress_gzip_ctx_s nxt_http_compress_gzip_ctx_t;
struct nxt_http_compress_gzip_ctx_s {
nxt_http_request_t *r;
nxt_buf_t *b;
int8_t level;
#if (NXT_WITH_ZLIB)
z_stream z;
#endif
};
static nxt_http_compress_gzip_ctx_t *nxt_http_compress_gzip_ctx(
nxt_task_t *task, nxt_http_request_t *r, nxt_http_compress_conf_t *conf,
size_t content_length);
static int nxt_http_compress_gzip_memlevel(size_t content_length);
static int nxt_http_compress_gzip_wbits(size_t content_length);
static void nxt_http_compress_gzip_filter(nxt_task_t *task, void *obj,
void *data);
nxt_int_t
nxt_http_compress_gzip(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_compress_conf_t *conf)
{
size_t clen;
nxt_int_t ret;
nxt_http_compress_gzip_ctx_t *ctx;
static nxt_str_t ce = nxt_string("Content-Encoding");
static nxt_str_t gzip = nxt_string("gzip");
clen = nxt_http_compress_resp_content_length(&r->resp);
if (clen < nxt_max(1u, conf->min_len) || r->body_handler == NULL) {
return NXT_OK;
}
ret = nxt_http_compress_accept_encoding(task, r, conf->accept_encoding,
&gzip);
switch (ret) {
case NXT_ERROR:
return NXT_ERROR;
case 0:
return NXT_OK;
case 1:
break;
}
ret = nxt_http_compressible_mtype(task, r, conf->mtrule);
switch (ret) {
case NXT_ERROR:
return NXT_ERROR;
case 0:
return NXT_OK;
case 1:
break;
}
r->resp.content_length = NULL;
r->resp.content_length_n = -1;
ret = nxt_http_compress_append_field(task, r, &ce, &gzip);
if (nxt_slow_path(ret == NXT_ERROR)) {
return NXT_ERROR;
}
ctx = nxt_http_compress_gzip_ctx(task, r, conf, clen);
if (nxt_slow_path(ctx == NULL)) {
return NXT_ERROR;
}
ret = nxt_http_filter_handler_add(r, nxt_http_compress_gzip_filter, ctx);
if (nxt_slow_path(ret == NXT_ERROR)) {
return NXT_ERROR;
}
return NXT_OK;
}
static nxt_http_compress_gzip_ctx_t *
nxt_http_compress_gzip_ctx(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_compress_conf_t *conf, size_t content_length)
{
#if (!NXT_WITH_ZLIB)
return NULL;
#else
int ret;
z_stream *z;
nxt_http_compress_gzip_ctx_t *ctx;
ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_compress_gzip_ctx_t));
if (nxt_slow_path(ctx == NULL)) {
return NULL;
}
ctx->level = conf->level;
ctx->r = r;
z = &ctx->z;
z->zalloc = NULL;
z->zfree = NULL;
z->opaque = NULL;
ret = deflateInit2(z, ctx->level, Z_DEFLATED,
nxt_http_compress_gzip_wbits(content_length),
nxt_http_compress_gzip_memlevel(content_length),
Z_DEFAULT_STRATEGY);
if (nxt_slow_path(ret != 0)) {
return NULL;
}
return ctx;
#endif
}
static int
nxt_http_compress_gzip_memlevel(size_t content_length)
{
int w;
w = nxt_bit_width_ul(content_length);
return nxt_min(MAX_MEM_LEVEL, nxt_max(2, w - 9));
}
static int
nxt_http_compress_gzip_wbits(size_t content_length)
{
int w;
w = nxt_bit_width_ul(content_length);
return 16 + nxt_min(MAX_WBITS, nxt_max(9, w - 2));
}
static void
nxt_http_compress_gzip_filter(nxt_task_t *task, void *obj, void *data)
{
#if (NXT_WITH_ZLIB)
int ret;
ssize_t size;
z_stream *z;
nxt_buf_t **b, *tmp;
nxt_bool_t is_last;
nxt_http_request_t *r;
nxt_http_compress_gzip_ctx_t *ctx;
b = obj;
ctx = data;
z = &ctx->z;
r = ctx->r;
is_last = ((*b)->next != NULL
&& (*b)->next->completion_handler != (*b)->completion_handler);
z->next_in = (*b)->mem.pos;
z->avail_in = (*b)->mem.free - (*b)->mem.pos;
size = deflateBound(z, z->avail_in);
tmp = nxt_buf_mem_alloc(r->mem_pool, size, 0);
if (nxt_slow_path(tmp == NULL)) {
return;
}
nxt_memcpy(tmp, *b, offsetof(nxt_buf_t, mem));
tmp->data = r->mem_pool;
z->next_out = tmp->mem.start;
z->avail_out = tmp->mem.end - tmp->mem.start;
ret = deflate(z, is_last ? Z_FINISH : Z_SYNC_FLUSH);
if (nxt_slow_path(ret == Z_STREAM_ERROR || ret == Z_BUF_ERROR)) {
goto fail;
}
tmp->mem.free = tmp->mem.end - z->avail_out;
size = tmp->mem.free - tmp->mem.start;
if ((*b)->mem.end - (*b)->mem.pos >= size) {
(*b)->mem.free = nxt_cpymem((*b)->mem.pos, tmp->mem.start, size);
} else {
nxt_swap(b, &tmp);
}
fail:
nxt_mp_free(tmp->data, tmp);
if (is_last) {
ret = deflateEnd(z);
if (ret != Z_OK) {
return;
}
}
return;
#endif
}