/*-
 * Copyright 2026 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved
 *
 * Author: Nils Goroll <nils.goroll@uplex.de>
 *
 * 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.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include "cache/cache.h"
#include "vas.h"
#include "miniobj.h"

#include "vcc_re2_if.h"
#include "set_hdr_iter.h"

struct VPFX(re2_hdr_iter) {
	unsigned		magic;
#define RE2HDRITER_MAGIC	0xdce33bb3

	const char		*name;
};

VCL_VOID
vmod_hdr_iter__init(VRT_CTX,
    struct VPFX(re2_hdr_iter) **objp, const char *name)
{
	struct VPFX(re2_hdr_iter) *obj;

	(void)ctx;

	AN(objp);
	AZ(*objp);
	ALLOC_OBJ(obj, RE2HDRITER_MAGIC);
	AN(obj);

	obj->name = name;

	*objp = obj;
}

VCL_VOID
vmod_hdr_iter__fini(struct VPFX(re2_hdr_iter) **objp)
{
	struct VPFX(re2_hdr_iter) *obj;

	AN(objp);
	obj = *objp;
	*objp = NULL;

	if (obj == NULL)
		return;

	CHECK_OBJ(obj, RE2HDRITER_MAGIC);
	FREE_OBJ(obj);
}

#define SFMT "%s.foreach(...): "

static struct VPFX(re2_set) *
get_set(VRT_CTX, const char *name, VCL_BLOB re2blob)
{

	if (re2blob == NULL) {
		VRT_fail(ctx, SFMT "re2 is NULL", name);
		return NULL;
	}
	const char *err = NULL;
	if (re2blob->magic != VRT_BLOB_MAGIC)
		err = "wrong magic";
	else if (re2blob->type != VMOD_RE2_SET_MAGIC)
		err = "type magic";
	else if (re2blob->blob == NULL)
		err = "null pointer";

	if (err == NULL)
		return (void *)re2blob->blob;

	VRT_fail(ctx, SFMT "bad re2 blob: %s", name, err);
	return (NULL);
}

struct re2_hdr_iter_task {
	uint16_t		magic;
#define RE2HITA_MAGIC		0x9f48
	unsigned		inuse:1;

	// these are per header and need to be cleared
	unsigned		delete:1;
	unsigned		last:1;
	/* XXX  5-bit hole       */
	/* XXX  1-byte hole      */
	enum VSL_tag_e		logtag;
	txt			*hdr;
};

static void
re2_hita_percall(struct re2_hdr_iter_task *hita, txt *hdr)
{
	AN(hita->inuse);
	hita->delete = 0;
	hita->last = 0;
	hita->hdr = hdr;
}

struct re2_hdr_iter_task *
re2_hita(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	struct re2_hdr_iter_task *hita;
	struct vmod_priv *priv;

	priv = VRT_priv_task(ctx, obj);
	if (priv == NULL)
		goto err;
	if (priv->priv != NULL) {
		CAST_OBJ_NOTNULL(hita, priv->priv, RE2HITA_MAGIC);
		return (hita);
	}
	WS_TASK_ALLOC_OBJ(ctx, hita, RE2HITA_MAGIC);
	if (hita == NULL)
		goto err;
	priv->priv = hita;
	return (hita);

    err:
	VRT_fail(ctx, SFMT "could not get priv_task", obj->name);
	return NULL;
}

struct re2_hdr_iter_task *
re2_hita_get(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	struct re2_hdr_iter_task *hita;
	struct vmod_priv *priv;

	priv = VRT_priv_task_get(ctx, obj);
	if (priv == NULL || priv->priv == NULL)
		return (NULL);

	CAST_OBJ_NOTNULL(hita, priv->priv, RE2HITA_MAGIC);
	if (! hita->inuse)
		return (NULL);
	return (hita);
}

VCL_VOID
vmod_hdr_iter_foreach(VRT_CTX,
    struct VPFX(re2_hdr_iter) *obj, VCL_HTTP hp, VCL_BLOB re2blob, VCL_SUB sub,
    VCL_ENUM which_a)
{
	struct re2_hdr_iter_task *hita;
	struct VPFX(re2_set) *set;
	VCL_BOOL result, expect;

	CHECK_OBJ_NOTNULL(obj, RE2HDRITER_MAGIC);
	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);

	set = get_set(ctx, obj->name, re2blob);
	hita = re2_hita(ctx, obj);
	if (set == NULL || hita == NULL)
		return;

	if (hita->inuse) {
		VRT_fail(ctx, SFMT "already iterating", obj->name);
		return;
	}
	hita->inuse = 1;

	if (which_a == VENUM(match))
		expect = 1;
	else if (which_a == VENUM(nomatch))
		expect = 0;
	else
		WRONG("which_a");

	hita->last = 0;
	hita->logtag = hp->logtag;
	uint16_t u, v;
	for (v = u = HTTP_HDR_FIRST; u < hp->nhd; u++) {
		const char *hdr;
		unsigned len;

		// having the next checks at the beginning of the loop simplifies it
		if (hita->last || VRT_handled(ctx)) {
			uint16_t n = hp->nhd - u;
			memcpy(&hp->hd[v], &hp->hd[u], sizeof *hp->hd * n);
			memcpy(&hp->hdf[v], &hp->hdf[u], sizeof *hp->hdf * n);
			v += n;
			break;
		}

		Tcheck(hp->hd[u]);
		hdr = (void *)hp->hd[u].b;
		len = Tlen(hp->hd[u]);
		assert(hdr[len] == '\0');

		result = vmod_set_match(ctx, set, hdr);

		if (!result != !expect)
			goto keep;

		re2_hita_percall(hita, &hp->hd[u]);
		VRT_call(ctx, sub);

		if (hita->delete) {
			http_vslh_del(hp, u);
			continue;
		}

	    keep:
		if (v != u) {
			memcpy(&hp->hd[v], &hp->hd[u], sizeof *hp->hd);
			memcpy(&hp->hdf[v], &hp->hdf[u], sizeof *hp->hdf);
		}
		v++;
	}
	hp->nhd = v;
	hita->inuse = 0;
}

#undef SFMT

#define HITA_COMMON(retval)						\
	CHECK_OBJ_NOTNULL(obj, RE2HDRITER_MAGIC);			\
	struct re2_hdr_iter_task *hita = re2_hita_get(ctx, obj);	\
	if (hita == NULL) {						\
		VRT_fail(ctx, "%s.get(): Not iterating", obj->name);	\
		return retval;						\
	}

const char *
skipname(const char *p)
{
	AN(p);
	p = strchr(p, ':');
	AN(p);
	p++;
	// no vct.h installed :(
	while (*p == ' ' || *p == '\t')
		p++;
	return (p);
}

VCL_STRING
vmod_hdr_iter_get_full(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	HITA_COMMON(NULL)
	AN(hita->hdr);
	AN(hita->hdr->b);
	return (hita->hdr->b);
}

VCL_STRING
vmod_hdr_iter_get(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	const char *p = vmod_hdr_iter_get_full(ctx, obj);
	if (p == NULL)
		return (p);

	return (skipname(p));
}

static void
log_hdr(struct vsl_log *vsl, enum VSL_tag_e logtag,
    txt old, txt new)
{
	int i;

	i = (HTTP_HDR_UNSET - HTTP_HDR_METHOD) + logtag;
	VSLbt(vsl, (enum VSL_tag_e)i, old);
	i = HTTP_HDR_FIRST + logtag;
	VSLbt(vsl, (enum VSL_tag_e)i, new);
}

static void
replace(VRT_CTX, const char *pfx, VCL_STRANDS s, struct re2_hdr_iter_task *hita)
{
	const char *hdr = VRT_StrandsWS(ctx->ws, pfx, s);
	if (hdr == NULL)
		return;
	if (! strchr(hdr, ':')) {
		VRT_fail(ctx, ".replace(\"%s\"): No colon", hdr);
		return;
	}
	if (! VRT_ValidHdr(ctx, TOSTRAND(hdr))) {
		VRT_fail(ctx, ".replace(\"%s\"): Invalid Header", hdr);
		return;
	}

	txt new;
	new.b = TRUST_ME(hdr);
	new.e = strchr(hdr, '\0');

	log_hdr(ctx->vsl, hita->logtag, *hita->hdr, new);
	*hita->hdr = new;
}

VCL_VOID
vmod_hdr_iter_replace(VRT_CTX, struct VPFX(re2_hdr_iter) *obj, VCL_STRANDS s)
{
	HITA_COMMON()
	replace(ctx, NULL, s, hita);
}

VCL_VOID
vmod_hdr_iter_set(VRT_CTX, struct VPFX(re2_hdr_iter) *obj, VCL_STRANDS s)
{
	HITA_COMMON()
	
	char buf[Tlen(*hita->hdr)];
	const char *e = skipname(hita->hdr->b);
	AN(e);
	assert(e > hita->hdr->b);
	size_t l = e - hita->hdr->b;
	memcpy(buf, hita->hdr->b, l);
	buf[l]= '\0';

	replace(ctx, buf, s, hita);
}

VCL_VOID
vmod_hdr_iter_delete(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	HITA_COMMON()
	hita->delete = 1;
}

VCL_VOID
vmod_hdr_iter_last(VRT_CTX, struct VPFX(re2_hdr_iter) *obj)
{
	HITA_COMMON()
	hita->last = 1;
}
