/*-
 * Copyright (c) 2013-2015 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Interaction with the linvgz (zlib) library.
 *
 * The zlib library pollutes namespace a LOT when you include the "vgz.h"
 * (aka (zlib.h") file so we contain the damage by vectoring all access
 * to libz through this source file.
 *
 * The API defined by this file, will also insulate the rest of the code,
 * should we find a better gzip library at a later date.
 *
 */

#include "config.h"

#include <stdlib.h>

#include "cache.h"
#include "cache_filter.h"
#include "vend.h"

#include "vgz.h"

struct vgz {
	unsigned		magic;
#define VGZ_MAGIC		0x162df0cb
	enum {VGZ_GZ,VGZ_UN}	dir;
	struct vsl_log		*vsl;
	const char		*id;
	int			last_i;
	enum vgz_flag		flag;

	char			*m_buf;
	ssize_t			m_sz;
	ssize_t			m_len;

	intmax_t		bits;

	z_stream		vz;
};

static const char *
vgz_msg(const struct vgz *vg)
{
	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	return vg->vz.msg ? vg->vz.msg : "(null)";
}

/*--------------------------------------------------------------------
 * Set up a gunzip instance
 */

static struct vgz *
vgz_alloc_vgz(struct vsl_log *vsl, const char *id)
{
	struct vgz *vg;

	ALLOC_OBJ(vg, VGZ_MAGIC);
	AN(vg);
	vg->vsl = vsl;
	vg->id = id;
	return (vg);
}

static struct vgz *
VGZ_NewUngzip(struct vsl_log *vsl, const char *id)
{
	struct vgz *vg;

	vg = vgz_alloc_vgz(vsl, id);
	vg->dir = VGZ_UN;
	VSC_C_main->n_gunzip++;

	/*
	 * Max memory usage according to zonf.h:
	 *	mem_needed = "a few kb" + (1 << (windowBits))
	 * Since we don't control windowBits, we have to assume
	 * it is 15, so 34-35KB or so.
	 */
	assert(Z_OK == inflateInit2(&vg->vz, 31));
	return (vg);
}

struct vgz *
VGZ_NewGzip(struct vsl_log *vsl, const char *id)
{
	struct vgz *vg;
	int i;

	vg = vgz_alloc_vgz(vsl, id);
	vg->dir = VGZ_GZ;
	VSC_C_main->n_gzip++;

	/*
	 * From zconf.h:
	 *
	 *	mem_needed = "a few kb"
	 *		+ (1 << (windowBits+2))
	 *		+  (1 << (memLevel+9))
	 *
	 * windowBits [8..15] (-> 1K..128K)
	 * memLevel [1..9] (-> 1K->256K)
	 */
	i = deflateInit2(&vg->vz,
	    cache_param->gzip_level,		/* Level */
	    Z_DEFLATED,				/* Method */
	    16 + 15,				/* Window bits (16=gzip) */
	    cache_param->gzip_memlevel,		/* memLevel */
	    Z_DEFAULT_STRATEGY);
	assert(Z_OK == i);
	return (vg);
}

/*--------------------------------------------------------------------
 */

static int
vgz_getmbuf(struct vgz *vg)
{

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	AZ(vg->m_sz);
	AZ(vg->m_len);
	AZ(vg->m_buf);

	vg->m_sz = cache_param->gzip_buffer;
	vg->m_buf = malloc(vg->m_sz);
	if (vg->m_buf == NULL) {
		vg->m_sz = 0;
		return (-1);
	}
	return (0);
}

/*--------------------------------------------------------------------*/

void
VGZ_Ibuf(struct vgz *vg, const void *ptr, ssize_t len)
{

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);

	AZ(vg->vz.avail_in);
	vg->vz.next_in = TRUST_ME(ptr);
	vg->vz.avail_in = len;
}

int
VGZ_IbufEmpty(const struct vgz *vg)
{

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	return (vg->vz.avail_in == 0);
}

/*--------------------------------------------------------------------*/

void
VGZ_Obuf(struct vgz *vg, void *ptr, ssize_t len)
{

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);

	vg->vz.next_out = TRUST_ME(ptr);
	vg->vz.avail_out = len;
}

int
VGZ_ObufFull(const struct vgz *vg)
{

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	return (vg->vz.avail_out == 0);
}

/*--------------------------------------------------------------------*/

static enum vgzret_e
VGZ_Gunzip(struct vgz *vg, const void **pptr, ssize_t *plen)
{
	int i;
	ssize_t l;
	const uint8_t *before;

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);

	*pptr = NULL;
	*plen = 0;
	AN(vg->vz.next_out);
	AN(vg->vz.avail_out);
	before = vg->vz.next_out;
	i = inflate(&vg->vz, 0);
	if (i == Z_OK || i == Z_STREAM_END) {
		*pptr = before;
		l = (const uint8_t *)vg->vz.next_out - before;
		*plen = l;
	}
	vg->last_i = i;
	if (i == Z_OK)
		return (VGZ_OK);
	if (i == Z_STREAM_END)
		return (VGZ_END);
	if (i == Z_BUF_ERROR)
		return (VGZ_STUCK);
	VSLb(vg->vsl, SLT_Gzip, "Gunzip error: %d (%s)", i, vgz_msg(vg));
	return (VGZ_ERROR);
}

/*--------------------------------------------------------------------*/

enum vgzret_e
VGZ_Gzip(struct vgz *vg, const void **pptr, ssize_t *plen, enum vgz_flag flags)
{
	int i;
	int zflg;
	ssize_t l;
	const uint8_t *before;

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);

	*pptr = NULL;
	*plen = 0;
	AN(vg->vz.next_out);
	AN(vg->vz.avail_out);
	before = vg->vz.next_out;
	switch(flags) {
	case VGZ_NORMAL:	zflg = Z_NO_FLUSH; break;
	case VGZ_ALIGN:		zflg = Z_SYNC_FLUSH; break;
	case VGZ_RESET:		zflg = Z_FULL_FLUSH; break;
	case VGZ_FINISH:	zflg = Z_FINISH; break;
	default:		INCOMPL();
	}
	i = deflate(&vg->vz, zflg);
	if (i == Z_OK || i == Z_STREAM_END) {
		*pptr = before;
		l = (const uint8_t *)vg->vz.next_out - before;
		*plen = l;
	}
	vg->last_i = i;
	if (i == Z_OK)
		return (VGZ_OK);
	if (i == Z_STREAM_END)
		return (VGZ_END);
	if (i == Z_BUF_ERROR)
		return (VGZ_STUCK);
	VSLb(vg->vsl, SLT_Gzip, "Gzip error: %d (%s)", i, vgz_msg(vg));
	return (VGZ_ERROR);
}

/*--------------------------------------------------------------------
 * VDP for gunzip'ing
 */

int __match_proto__(vdp_bytes)
VDP_gunzip(struct req *req, enum vdp_action act, void **priv,
    const void *ptr, ssize_t len)
{
	enum vgzret_e vr;
	ssize_t dl;
	const void *dp;
	struct worker *wrk;
	struct vgz *vg;
	const char *p;
	uint64_t u;

	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
	wrk = req->wrk;
	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

	if (act == VDP_INIT) {
		vg = VGZ_NewUngzip(req->vsl, "U D -");
		AN(vg);
		if (vgz_getmbuf(vg)) {
			(void)VGZ_Destroy(&vg);
			return (-1);
		}

		req->res_mode |= RES_GUNZIP;
		VGZ_Obuf(vg, vg->m_buf, vg->m_sz);
		*priv = vg;

		http_Unset(req->resp, H_Content_Encoding);

		req->resp_len = -1;
		if (req->objcore->boc != NULL)
			return (0);	/* No idea about length (yet) */

		p = ObjGetAttr(req->wrk, req->objcore, OA_GZIPBITS, &dl);
		if (p == NULL || dl != 32)
			return (0); /* No OA_GZIPBITS yet */

		u = vbe64dec(p + 24);
		/*
		 * If the size is non-zero AND we are the top
		 * VDP (ie: no ESI), we know what size the output will be.
		 */
		if (u != 0 && VTAILQ_FIRST(&req->vdp)->func == VDP_gunzip)
			req->resp_len = u;

		return (0);
	}

	CAST_OBJ_NOTNULL(vg, *priv, VGZ_MAGIC);
	AN(vg->m_buf);

	if (act == VDP_FINI) {
		/* NB: Gunzip'ing may or may not have completed successfully. */
		AZ(len);
		(void)VGZ_Destroy(&vg);
		*priv = NULL;
		return (0);
	}

	if (len == 0)
		return (0);

	VGZ_Ibuf(vg, ptr, len);
	do {
		vr = VGZ_Gunzip(vg, &dp, &dl);
		vg->m_len += dl;
		if (vr < VGZ_OK)
			return (-1);
		if (vg->m_len == vg->m_sz || vr != VGZ_OK) {
			if (VDP_bytes(req, VDP_FLUSH, vg->m_buf, vg->m_len))
				return (-1);
			vg->m_len = 0;
			VGZ_Obuf(vg, vg->m_buf, vg->m_sz);
		}
	} while (!VGZ_IbufEmpty(vg));
	assert(vr == VGZ_STUCK || vr == VGZ_OK || vr == VGZ_END);
	return (0);
}

/*--------------------------------------------------------------------*/

void
VGZ_UpdateObj(const struct vfp_ctx *vc, struct vgz *vg, enum vgz_ua_e e)
{
	char *p;
	intmax_t ii;

	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	ii = vg->vz.start_bit + vg->vz.last_bit + vg->vz.stop_bit;
	if (e == VUA_UPDATE && ii == vg->bits)
		return;
	vg->bits = ii;
	p = ObjSetAttr(vc->wrk, vc->oc, OA_GZIPBITS, 32, NULL);
	AN(p);
	vbe64enc(p, vg->vz.start_bit);
	vbe64enc(p + 8, vg->vz.last_bit);
	vbe64enc(p + 16, vg->vz.stop_bit);
	if (e == VUA_END_GZIP)
		vbe64enc(p + 24, vg->vz.total_in);
	if (e == VUA_END_GUNZIP)
		vbe64enc(p + 24, vg->vz.total_out);
}

/*--------------------------------------------------------------------
 */

enum vgzret_e
VGZ_Destroy(struct vgz **vgp)
{
	struct vgz *vg;
	enum vgzret_e vr;
	int i;

	vg = *vgp;
	CHECK_OBJ_NOTNULL(vg, VGZ_MAGIC);
	*vgp = NULL;

	AN(vg->id);
	VSLb(vg->vsl, SLT_Gzip, "%s %jd %jd %jd %jd %jd",
	    vg->id,
	    (intmax_t)vg->vz.total_in,
	    (intmax_t)vg->vz.total_out,
	    (intmax_t)vg->vz.start_bit,
	    (intmax_t)vg->vz.last_bit,
	    (intmax_t)vg->vz.stop_bit);
	if (vg->dir == VGZ_GZ)
		i = deflateEnd(&vg->vz);
	else
		i = inflateEnd(&vg->vz);
	if (vg->last_i == Z_STREAM_END && i == Z_OK)
		i = Z_STREAM_END;
	if (vg->m_buf)
		free(vg->m_buf);
	if (i == Z_OK)
		vr = VGZ_OK;
	else if (i == Z_STREAM_END)
		vr = VGZ_END;
	else if (i == Z_BUF_ERROR)
		vr = VGZ_STUCK;
	else {
		VSLb(vg->vsl, SLT_Gzip, "G(un)zip error: %d (%s)",
		    i, vgz_msg(vg));
		vr = VGZ_ERROR;
	}
	FREE_OBJ(vg);
	return (vr);
}

/*--------------------------------------------------------------------*/

#define VFP_GUNZIP	0
#define VFP_GZIP	1
#define VFP_TESTGUNZIP	2

static enum vfp_status __match_proto__(vfp_init_f)
vfp_gzip_init(struct vfp_ctx *vc, struct vfp_entry *vfe)
{
	struct vgz *vg;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);

	if (http_HdrIs(vc->http, H_Content_Length, "0")) {
		http_Unset(vc->http, H_Content_Encoding);
		return (VFP_NULL);
	}

	if (vfe->vfp->priv2 == VFP_GZIP) {
		if (http_GetHdr(vc->http, H_Content_Encoding, NULL))
			return (VFP_NULL);
		vg = VGZ_NewGzip(vc->wrk->vsl, vfe->vfp->priv1);
	} else {
		if (!http_HdrIs(vc->http, H_Content_Encoding, "gzip"))
			return (VFP_NULL);
		vg = VGZ_NewUngzip(vc->wrk->vsl, vfe->vfp->priv1);
	}
	if (vg == NULL)
		return (VFP_ERROR);
	vfe->priv1 = vg;
	if (vgz_getmbuf(vg))
		return (VFP_ERROR);
	VGZ_Ibuf(vg, vg->m_buf, 0);
	AZ(vg->m_len);

	if (vfe->vfp->priv2 == VFP_GUNZIP || vfe->vfp->priv2 == VFP_GZIP) {
		http_Unset(vc->http, H_Content_Encoding);
		http_Unset(vc->http, H_Content_Length);
		RFC2616_Weaken_Etag(vc->http);
	}

	if (vfe->vfp->priv2 == VFP_GZIP)
		http_SetHeader(vc->http, "Content-Encoding: gzip");

	if (vfe->vfp->priv2 == VFP_GZIP || vfe->vfp->priv2 == VFP_TESTGUNZIP)
		RFC2616_Vary_AE(vc->http);

	return (VFP_OK);
}

/*--------------------------------------------------------------------
 * VFP_GUNZIP
 *
 * A VFP for gunzip'ing an object as we receive it from the backend
 */

static enum vfp_status __match_proto__(vfp_pull_f)
vfp_gunzip_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *p,
    ssize_t *lp)
{
	ssize_t l;
	struct vgz *vg;
	enum vgzret_e vr = VGZ_ERROR;
	const void *dp;
	ssize_t dl;
	enum vfp_status vp = VFP_OK;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);
	CAST_OBJ_NOTNULL(vg, vfe->priv1, VGZ_MAGIC);
	AN(p);
	AN(lp);
	l = *lp;
	*lp = 0;
	VGZ_Obuf(vg, p, l);
	do {
		if (VGZ_IbufEmpty(vg)) {
			l = vg->m_sz;
			vp = VFP_Suck(vc, vg->m_buf, &l);
			if (vp == VFP_ERROR)
				return (vp);
			VGZ_Ibuf(vg, vg->m_buf, l);
		}
		if (!VGZ_IbufEmpty(vg) || vp == VFP_END) {
			vr = VGZ_Gunzip(vg, &dp, &dl);
			if (vr == VGZ_END && !VGZ_IbufEmpty(vg))
				return(VFP_Error(vc, "Junk after gzip data"));
			if (vr < VGZ_OK)
				return (VFP_Error(vc,
				    "Invalid Gzip data: %s", vgz_msg(vg)));
			if (dl > 0) {
				*lp = dl;
				assert(dp == p);
				return (VFP_OK);
			}
		}
		AN(VGZ_IbufEmpty(vg));
	} while (vp == VFP_OK);
	if (vr != VGZ_END)
		return(VFP_Error(vc, "Gunzip error at the very end"));
	return (vp);
}


/*--------------------------------------------------------------------
 * VFP_GZIP
 *
 * A VFP for gzip'ing an object as we receive it from the backend
 */

static enum vfp_status __match_proto__(vfp_pull_f)
vfp_gzip_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *p,
    ssize_t *lp)
{
	ssize_t l;
	struct vgz *vg;
	enum vgzret_e vr = VGZ_ERROR;
	const void *dp;
	ssize_t dl;
	enum vfp_status vp = VFP_ERROR;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);
	CAST_OBJ_NOTNULL(vg, vfe->priv1, VGZ_MAGIC);
	AN(p);
	AN(lp);
	l = *lp;
	*lp = 0;
	VGZ_Obuf(vg, p, l);
	do {
		if (VGZ_IbufEmpty(vg)) {
			l = vg->m_sz;
			vp = VFP_Suck(vc, vg->m_buf, &l);
			if (vp == VFP_ERROR)
				break;
			if (vp == VFP_END)
				vg->flag = VGZ_FINISH;
			VGZ_Ibuf(vg, vg->m_buf, l);
		}
		if (!VGZ_IbufEmpty(vg) || vg->flag == VGZ_FINISH) {
			vr = VGZ_Gzip(vg, &dp, &dl, vg->flag);
			if (vr < VGZ_OK)
				return (VFP_Error(vc, "Gzip failed"));
			if (dl > 0) {
				VGZ_UpdateObj(vc, vg, VUA_UPDATE);
				*lp = dl;
				assert(dp == p);
				return (VFP_OK);
			}
		}
		AN(VGZ_IbufEmpty(vg));
	} while (vg->flag != VGZ_FINISH);

	if (vr != VGZ_END)
		return (VFP_Error(vc, "Gzip failed"));
	VGZ_UpdateObj(vc, vg, VUA_END_GZIP);
	return (VFP_END);
}

/*--------------------------------------------------------------------
 * VFP_TESTGZIP
 *
 * A VFP for testing that received gzip data is valid, and for
 * collecting the magic bits while we're at it.
 */

static enum vfp_status __match_proto__(vfp_pull_f)
vfp_testgunzip_pull(struct vfp_ctx *vc, struct vfp_entry *vfe, void *p,
    ssize_t *lp)
{
	struct vgz *vg;
	enum vgzret_e vr = VGZ_ERROR;
	const void *dp;
	ssize_t dl;
	enum vfp_status vp;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);
	CAST_OBJ_NOTNULL(vg, vfe->priv1, VGZ_MAGIC);
	AN(p);
	AN(lp);
	CAST_OBJ_NOTNULL(vg, vfe->priv1, VGZ_MAGIC);
	vp = VFP_Suck(vc, p, lp);
	if (vp == VFP_ERROR)
		return (vp);
	if (*lp > 0 || vp == VFP_END) {
		VGZ_Ibuf(vg, p, *lp);
		do {
			VGZ_Obuf(vg, vg->m_buf, vg->m_sz);
			vr = VGZ_Gunzip(vg, &dp, &dl);
			if (vr == VGZ_END && !VGZ_IbufEmpty(vg))
				return(VFP_Error(vc, "Junk after gzip data"));
			if (vr < VGZ_OK)
				return (VFP_Error(vc,
				    "Invalid Gzip data: %s", vgz_msg(vg)));
		} while (!VGZ_IbufEmpty(vg));
	}
	VGZ_UpdateObj(vc, vg, VUA_UPDATE);
	if (vp == VFP_END) {
		if (vr != VGZ_END)
			return (VFP_Error(vc, "tGunzip failed"));
		VGZ_UpdateObj(vc, vg, VUA_END_GUNZIP);
	}
	return (vp);
}

/*--------------------------------------------------------------------*/

static void __match_proto__(vfp_fini_f)
vfp_gzip_fini(struct vfp_ctx *vc, struct vfp_entry *vfe)
{
	struct vgz *vg;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);

	if (vfe->priv1 != NULL) {
		CAST_OBJ_NOTNULL(vg, vfe->priv1, VGZ_MAGIC);
		vfe->priv1 = NULL;
		(void)VGZ_Destroy(&vg);
	}
}

/*--------------------------------------------------------------------*/

const struct vfp vfp_gunzip = {
	.name = "GUNZIP",
	.init = vfp_gzip_init,
	.pull = vfp_gunzip_pull,
	.fini = vfp_gzip_fini,
	.priv1 = "U F -",
	.priv2 = VFP_GUNZIP,
};

const struct vfp vfp_gzip = {
	.name = "GZIP",
	.init = vfp_gzip_init,
	.pull = vfp_gzip_pull,
	.fini = vfp_gzip_fini,
	.priv1 = "G F -",
	.priv2 = VFP_GZIP,
};

const struct vfp vfp_testgunzip = {
	.name = "TESTGUNZIP",
	.init = vfp_gzip_init,
	.pull = vfp_testgunzip_pull,
	.fini = vfp_gzip_fini,
	.priv1 = "u F -",
	.priv2 = VFP_TESTGUNZIP,
};
