/*-
 * Copyright 2013 - 2023 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved
 *
 * Authors: Geoffrey Simmons <geoffrey.simmons@uplex.de>
 *	    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"

#define PCRE2_CODE_UNIT_WIDTH 8

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

#include "cache/cache.h"
#include "cache/cache_filter.h"
#include "vcl.h"
#include "vre.h"
#include "vre_pcre2.h"
#include "vsb.h"

#include "vcc_re_if.h"
#include "rvb.h"

#define MAX_MATCHES	11
#define MAX_OV		((MAX_MATCHES) * 2)

// lack of priv pointer
// https://github.com/varnishcache/varnish-cache/pull/3912
struct vdp_container {
	unsigned		magic;
#define VMOD_RE_VDP_MAGIC	0xa16a677f
	struct vdp		vdp;
	struct vmod_re_regex	*re;
	// lack of VRT_CTX argument to _fini
	struct vcl		*vcl;
};

struct vmod_re_regex {
	unsigned		magic;
#define VMOD_RE_REGEX_MAGIC 0x955706ee
	vre_t			*vre;
	struct vre_limits	vre_limits;
	struct vfp		*vfp;
	struct vdp_container	*vdpc;
};

typedef struct ov_s {
	unsigned		magic;
#define OV_MAGIC 0x844bfa39
	const char		*subject;
	int			ovector[MAX_OV];
} ov_t;

static vfp_init_f  re_vfp_init;
static vfp_pull_f  re_vfp_pull;
static vfp_fini_f  re_vfp_fini;

static vdp_init_f  re_vdp_init;
static vdp_bytes_f re_vdp_bytes;
static vdp_fini_f  re_vdp_fini;

static void
errmsg(VRT_CTX, const char *fmt, ...)
{
	va_list args;

	AZ(ctx->method & VCL_MET_TASK_H);
	va_start(args, fmt);
	if (ctx->vsl)
		VSLbv(ctx->vsl, SLT_VCL_Error, fmt, args);
	else
		VSLv(SLT_VCL_Error, NO_VXID, fmt, args);
	va_end(args);
}

static vre_t *
re_compile(const char *pattern, unsigned options, char *errbuf,
    ssize_t errbufsz, int *erroffset)
{
	static vre_t *vre;
	struct vsb vsb[1];
	int errcode;

	vre = VRE_compile(pattern, options, &errcode, erroffset, 1);
	if (vre != NULL)
		return (vre);

	AN(VSB_init(vsb, errbuf, errbufsz));
	AZ(VRE_error(vsb, errcode));
	AZ(VSB_finish(vsb));
	VSB_fini(vsb);
	return (NULL);
}

VCL_VOID
vmod_regex__init(VRT_CTX, struct vmod_re_regex **rep, const char *vcl_name,
		 VCL_STRING pattern, VCL_INT limit, VCL_INT limit_recursion,
		 VCL_BOOL forbody, VCL_BOOL asfilter)
{
	struct vmod_re_regex *re;
	vre_t *vre;
	char errbuf[VRE_ERROR_LEN];
	int erroffset;
	unsigned options = 0;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(rep);
	AZ(*rep);
	AN(vcl_name);
	AN(pattern);

	if (limit < 1) {
		VRT_fail(ctx, "vmod re: invalid limit %ld in %s constructor",
			 limit, vcl_name);
		return;
	}

	if (limit_recursion < 1) {
		VRT_fail(ctx, "vmod re: invalid limit_recursion %ld "
			 "in %s constructor", limit_recursion, vcl_name);
		return;
	}

	if (limit > UINT_MAX)
		limit = UINT_MAX;
	if (limit_recursion > UINT_MAX)
		limit_recursion = UINT_MAX;

	forbody |= asfilter;

	if (forbody)
		options |= PCRE2_PARTIAL_HARD;
	vre = re_compile(pattern, options, errbuf, sizeof errbuf, &erroffset);
	if (vre == NULL) {
		VRT_fail(ctx, "vmod re: error compiling regex \"%s\" in %s "
			 "constructor: %s (at offset %d)", pattern, vcl_name,
			 errbuf, erroffset);
		return;
	}

	/* duplication with varnish-cache */
	if (forbody)
		(void) pcre2_jit_compile(VRE_unpack(vre),
		    PCRE2_JIT_PARTIAL_HARD | PCRE2_JIT_COMPLETE);

	ALLOC_OBJ(re, VMOD_RE_REGEX_MAGIC);
	AN(re);
	re->vre = vre;
	re->vre_limits.match = (unsigned)limit;
	re->vre_limits.depth = (unsigned)limit_recursion;
	*rep = re;

	if (asfilter == 0)
		return;

	struct vfp *vfp = malloc(sizeof *vfp);
	AN(vfp);
	struct vdp_container *vdpc;
	ALLOC_OBJ(vdpc, VMOD_RE_VDP_MAGIC);
	AN(vdpc);

	vfp->name  = vcl_name;
	vfp->init  = re_vfp_init;
	vfp->pull  = re_vfp_pull;
	vfp->fini  = re_vfp_fini;
	vfp->priv1 = re;

	vdpc->vdp.name  = vcl_name;
	vdpc->vdp.init  = re_vdp_init;
	vdpc->vdp.bytes = re_vdp_bytes;
	vdpc->vdp.fini  = re_vdp_fini;
	vdpc->re = re;
	vdpc->vcl = ctx->vcl;

	re->vfp = vfp;
	re->vdpc = vdpc;

	if (! VRT_AddFilter(ctx, vfp, &vdpc->vdp))
		return;

	re->vfp = NULL;
	re->vdpc = NULL;

	free(vfp);
	free(vdpc);

	vmod_regex__fini(rep);
	// VRT_fail() from VRT_AddFilter()
}

VCL_VOID
vmod_regex__fini(struct vmod_re_regex **rep)
{
	struct vmod_re_regex *re;

	if (rep == NULL || *rep == NULL)
		return;
	TAKE_OBJ_NOTNULL(re, rep, VMOD_RE_REGEX_MAGIC);
	if (re->vre != NULL)
		VRE_free(&re->vre);

	if (re->vfp) {
		struct vrt_ctx ctx[1];

		AN(re->vdpc);
		INIT_OBJ(ctx, VRT_CTX_MAGIC);
		ctx->vcl = re->vdpc->vcl;

		VRT_RemoveFilter(ctx, re->vfp, &re->vdpc->vdp);
		free(re->vfp);
		FREE_OBJ(re->vdpc);
	}

	FREE_OBJ(re);
}

static void
init_task(VRT_CTX, struct vmod_priv *task)
{
	ov_t *ov;

	AN(task);
	if (task->priv == NULL)
		task->priv = WS_Alloc(ctx->ws, (unsigned)sizeof(*ov));
	if (task->priv == NULL) {
		VSLb(ctx->vsl, SLT_VCL_Error, "vmod re error: "
		     "insufficient workspace for backref data");
		return;
	}
	task->len = -1;
	AZ(task->methods);
	ov = (ov_t *) task->priv;
	INIT_OBJ(ov, OV_MAGIC);
}

static int
match(VRT_CTX, const vre_t *vre, VCL_STRING subject,
	PCRE2_SIZE length, PCRE2_SIZE startoffset, uint32_t options,
	struct vmod_priv *task, const struct vre_limits *vre_limits)
{
	ov_t *ov;
	int i, s = PCRE2_ERROR_NOMEMORY;
	pcre2_match_context *re_ctx;
	pcre2_match_data *data;
	pcre2_code *re;
	PCRE2_SIZE *ovector;

	//lint --e{801} goto
	AN(vre);
	if (subject == NULL)
		subject = "";

	AN(task);
	if (task->priv == NULL)
		init_task(ctx, task);
	if (task->priv == NULL)
		return (s);

	AN(WS_Allocated(ctx->ws, task->priv, sizeof(*ov)));
	CAST_OBJ_NOTNULL(ov, task->priv, OV_MAGIC);

	// BEGIN duplication with vre
	re = VRE_unpack(vre);
	AN(re);
	data = pcre2_match_data_create_from_pattern(re, NULL);
	if (data == NULL) {
		VRT_fail(ctx, "vmod_re: failed to create match data");
		return (s);
	}
	// END duplication with vre

	// BEGIN unneeded overhead (unless we get access to re_ctx also)
	re_ctx = pcre2_match_context_create(NULL);
	if (re_ctx == NULL) {
		VRT_fail(ctx, "vmod_re: failed to create context");
		goto out;
	}
	AZ(pcre2_set_depth_limit(re_ctx, vre_limits->depth));
	AZ(pcre2_set_match_limit(re_ctx, vre_limits->match));
	// END unneeded overhead

	s = pcre2_match(re, (PCRE2_SPTR)subject, length, startoffset, options,
			data, re_ctx);

	if (s <= PCRE2_ERROR_NOMATCH) {
		if (s < PCRE2_ERROR_PARTIAL)
			VSLb(ctx->vsl, SLT_VCL_Error,
			     "vmod re: regex match returned %d", s);
		goto out;
	}
	if (s > MAX_MATCHES) {
		VSLb(ctx->vsl, SLT_VCL_Error,
		     "vmod re: capturing substrings exceed max %d",
		     MAX_MATCHES - 1);
		s = MAX_MATCHES;
	}
	ovector = pcre2_get_ovector_pointer(data);
	assert (s <= (int)pcre2_get_ovector_count(data));

	task->len = sizeof(*ov);
	ov->subject = subject;

	for (i = 0; i < s * 2; i++)
		ov->ovector[i] = (int)ovector[i];
	for ( ; i < MAX_OV; i++)
		ov->ovector[i] = -1;

out: // XXX goto because this might be throw-away code
	AN(data);
	pcre2_match_data_free(data);
	if (re_ctx != NULL)
		pcre2_match_context_free(re_ctx);
	return (s);
}

static VCL_STRING
backref(VRT_CTX, VCL_INT refnum, VCL_STRING fallback,
    const struct vmod_priv *task)
{
	ov_t *ov;
	const char *substr, *start;
	int len;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(fallback);
	AN(task);

	if (refnum < 0 || refnum >= MAX_MATCHES) {
		VSLb(ctx->vsl, SLT_VCL_Error,
		     "vmod re: backref %ld out of range", refnum);
		return fallback;
	}

	if (task->priv == NULL) {
		VSLb(ctx->vsl, SLT_VCL_Error,
		     "vmod re: backref called without prior match");
		return fallback;
	}
	if (task->len <= 0)
		return fallback;

	AN(WS_Allocated(ctx->ws, task->priv, sizeof(*ov)));
	CAST_OBJ_NOTNULL(ov, task->priv, OV_MAGIC);

	refnum *= 2;
	assert(refnum + 1 < MAX_OV);
	if (ov->ovector[refnum] == -1)
		return fallback;

	start = ov->subject + ov->ovector[refnum];
	len = ov->ovector[refnum+1] - ov->ovector[refnum];
	assert(len <= ov->ovector[1] - ov->ovector[0]);
	if (start[len] == '\0')
		substr = start;
	else
		substr = WS_Printf(ctx->ws, "%.*s", len, start);
	if (substr == NULL) {
		VSLb(ctx->vsl, SLT_VCL_Error,
		     "vmod re: insufficient workspace");
		return fallback;
	}
	return substr;
}

static inline const struct vre_limits *
get_limits(const struct vmod_re_regex *re, struct vre_limits *limits,
	   VCL_INT limit, VCL_INT limit_recursion)
{
	if (limit <= 0 && limit_recursion <= 0)
		return (&re->vre_limits);

	if (limit > UINT_MAX)
		limit = UINT_MAX;
	if (limit_recursion > UINT_MAX)
		limit_recursion = UINT_MAX;

	if (limit > 0)
		limits->match = (unsigned)limit;
	else
		limits->match = re->vre_limits.match;

	if (limit_recursion > 0)
		limits->depth = (unsigned)limit_recursion;
	else
		limits->depth = re->vre_limits.depth;

	return (limits);
}

VCL_BOOL
vmod_regex_match(VRT_CTX, struct vmod_re_regex *re, VCL_STRING subject,
		 VCL_INT limit, VCL_INT limit_recursion)
{
	struct vmod_priv *task;
	struct vre_limits buf;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);
	AN(re->vre);

	task = VRT_priv_task(ctx, re);

	if (task == NULL) {
		errmsg(ctx, "vmod re: Could not get a PRIV_TASK - out of workspace?");
		return (0);
	}

	AN(task);
	task->len = 0;

	return (match(ctx, re->vre, subject, PCRE2_ZERO_TERMINATED, 0UL, 0,
		      task, get_limits(re, &buf, limit, limit_recursion))
		> PCRE2_ERROR_NOMATCH);
}

VCL_BOOL
vmod_regex_foreach(VRT_CTX, struct vmod_re_regex *re, VCL_STRING subject,
    VCL_SUB sub, VCL_INT limit, VCL_INT limit_recursion)
{
	struct vmod_priv *task;
	struct vre_limits buf;
	ov_t *ov;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);
	AN(re->vre);

	task = VRT_priv_task(ctx, re);

	if (task == NULL) {
		errmsg(ctx, "vmod re: Could not get a PRIV_TASK - out of workspace?");
		return (0);
	}

	AN(task);
	task->len = 0;

	init_task(ctx, task);
	if (task->priv == NULL)
		return (0);

	ov = (ov_t *) task->priv;
	assert(ov->ovector[1] >= 0);
	while (! VRT_handled(ctx) &&
	       match(ctx, re->vre, subject, PCRE2_ZERO_TERMINATED,
		     (size_t)(unsigned)ov->ovector[1], 0,
		     task, get_limits(re, &buf, limit, limit_recursion))
	       > PCRE2_ERROR_NOMATCH)
		VRT_call(ctx, sub);

	return (1);
}

struct re_iter_priv {
	unsigned		magic;
#define RE_ITER_PRIV_MAGIC	0x04383ab8
	uint32_t		options;
	int			s;
	int			ret;
	VRT_CTX;
	VCL_SUB			sub;
	const vre_t		*vre;
	PCRE2_SIZE		startoffset;
	struct vmod_priv	*task;
	const struct vre_limits	*vre_limits;

	char			*buf;
	size_t			len;
};

static int
reip_free(struct re_iter_priv *reip, int ret)
{
	CHECK_OBJ_NOTNULL(reip, RE_ITER_PRIV_MAGIC);
	free(reip->buf);
	reip->buf = NULL;
	reip->len = 0;
	return (ret);
}

static int
reip_iter(struct re_iter_priv *reip, unsigned flush,
    const void *ptr, ssize_t lena)
{
	VCL_STRING		subject;
	// where data from ptr starts in subject
	size_t			ptrsubjoff;
	ov_t			*ov;
	int			i;
	size_t len;

	CHECK_OBJ_NOTNULL(reip, RE_ITER_PRIV_MAGIC);

	assert(lena >= 0);
	len = (size_t)lena;

#ifdef ITERDBG
	VSLb(reip->ctx->vsl, SLT_Debug, "reip_iter reip { .s=%d, "
	     "buf/len=%p/%zu (%.*s), "
	     "start=%zu } "
	     "flush=%u, ptr/len=%p/%zd (%.*s)",
	     reip->s,
	     reip->buf, reip->len, (int)reip->len, reip->buf ? reip->buf : "",
	     reip->startoffset,
	     flush, ptr, len, (int)len, ptr ? (const char *)ptr : "");
#endif

	subject = ptr;
	ptrsubjoff = 0;

	if (reip->s > PCRE2_ERROR_NOMATCH) {
		AZ(reip->buf);
		AZ(reip->len);
	}
	else if (reip->s == PCRE2_ERROR_NOMATCH) {
		AZ(reip->startoffset);
		AZ(reip->buf);
		AZ(reip->len);
	}
	else if (reip->s == PCRE2_ERROR_PARTIAL) {
		ptrsubjoff = reip->len;
		AN(reip->buf);
		reip->buf = realloc(reip->buf, reip->len + len);
		if (reip->buf != NULL) {
			memcpy(reip->buf + ptrsubjoff, ptr, len);
			len += reip->len;
			reip->len = len;
			subject = reip->buf;
		}
		else {
			AZ(reip->len);
			AZ(len);
		}
	}
	else {
		WRONG("match error should have been latched "
		      "in previous iteration");
	}

	if (flush & OBJ_ITER_END)
		reip->options &= ~PCRE2_PARTIAL_HARD;

	reip->s = match(reip->ctx, reip->vre, subject, len,
			reip->startoffset, reip->options, reip->task,
			reip->vre_limits);
	assert(reip->s != PCRE2_ERROR_BADOFFSET);

	if ((reip->s < PCRE2_ERROR_NOMATCH && reip->s < reip->ret) ||
	    reip->s > reip->ret)
		reip->ret = reip->s;

#ifdef ITERDBG
	VSLb(reip->ctx->vsl, SLT_Debug, "match=%d, subject=%.*s, len=%zd, "
	    "options=0x%x", reip->s, (int)len, subject, len, reip->options);
#endif

	CAST_OBJ_NOTNULL(ov, reip->task->priv, OV_MAGIC);

	if (reip->s == PCRE2_ERROR_PARTIAL && reip->buf != NULL)
		return (0);

	if (reip->s == PCRE2_ERROR_PARTIAL) {
		AZ(reip->buf);
		reip->buf = malloc(len);
		AN(reip->buf);
		memcpy(reip->buf, ptr, len);
		reip->len = len;

		return (0);
	}

	if (len > 0)
		reip->options |= PCRE2_NOTBOL;

	if (reip->s < PCRE2_ERROR_PARTIAL) {
		return (reip_free(reip, 1));
	}

	if (reip->s == PCRE2_ERROR_NOMATCH) {
		reip->startoffset = 0;
		return (reip_free(reip, 0));
	}

	assert(reip->s > PCRE2_ERROR_NOMATCH);

	assert(ov->ovector[1] >= 0);

	// save start offset for next call, relative to ptr
	if (reip->buf == NULL)
		reip->startoffset = (unsigned)ov->ovector[1];
	else if ((unsigned)ov->ovector[1] >= ptrsubjoff)
		reip->startoffset = (unsigned)ov->ovector[1] - ptrsubjoff;
	else
		WRONG("impossible ovector[1]");

	/* no buffer && no flush -> no copy needed */
	if (reip->buf == NULL && (flush & OBJ_ITER_FLUSH) == 0)
		return (0);

	/*
	 * we have a match, and we either can not hold on to the memory (because
	 * OBJ_ITER_FLUSH), or it is in our temporary buffer, which we want to
	 * free. Copy the match out to workspace
	 */
	assert(ov->ovector[0] >= 0);
	assert(ov->ovector[1] >= ov->ovector[0]);
	lena = (typeof(lena))ov->ovector[1] - (typeof(lena))ov->ovector[0];
	assert(lena >= 0);
	assert(lena <= INT_MAX);
	int l = (int)lena;

	ov->subject = (l == 0) ? "" :
		WS_Copy(reip->ctx->ws, subject + ov->ovector[0], l);

	if (ov->subject == NULL) {
		errmsg(reip->ctx, "vmod re: insufficient workspace "
		   "for match copy");
		return (reip_free(reip, -1));
	}

	/* we have copied subject from start of match, fix all offsets */
	l = ov->ovector[0];
	for (i = 0; i < reip->s * 2; i++) {
		assert(ov->ovector[i] >= l);
		ov->ovector[i] -= l;
	}

	return (reip_free(reip, 0));
}

static int v_matchproto_(objiterate_f)
match_iter_f(void *priv, unsigned flush, const void *ptr, ssize_t len)
{
	struct re_iter_priv	*reip;

	CAST_OBJ_NOTNULL(reip, priv, RE_ITER_PRIV_MAGIC);

#ifdef ITERDBG
	VSLb(reip->ctx->vsl, SLT_Debug, "flush=%u, s=%d, ptr=%.*s, len=%zd",
	     flush, reip->s, (int)len, (const char *)ptr, len);
#endif

	/* already have a match ? */
	if (reip->s > PCRE2_ERROR_NOMATCH) {
		AZ(reip->buf);
		return (0);
	}

	return (reip_iter(reip, flush, ptr, len));
}

static VCL_BOOL
reip_match_body(struct re_iter_priv *reip, struct vmod_re_regex *re,
    objiterate_f iter, VCL_ENUM which)
{
	VRT_CTX;
	struct vmod_priv *task;
	uint32_t u;
	int r;

	CHECK_OBJ_NOTNULL(reip, RE_ITER_PRIV_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);
	AN(re->vre);
	ctx = reip->ctx;
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);

	AZ(pcre2_pattern_info(VRE_unpack(re->vre), PCRE2_INFO_ARGOPTIONS, &u));
	if ((u & PCRE2_PARTIAL_HARD) == 0) {
		VRT_fail(ctx, "vmod re: .*_body() requires "
		       "construction with forbody=true");
		return (0);
	}

	task = VRT_priv_task(ctx, re);

	if (task == NULL) {
		errmsg(ctx, "vmod re: Could not get a PRIV_TASK - "
		    "out of workspace?");
		return (0);
	}

	init_task(ctx, task);
	if (task->priv == NULL)
		return (0);

	reip->options = PCRE2_PARTIAL_HARD;
	reip->s = PCRE2_ERROR_NOMATCH;
	AZ(reip->vre);
	reip->vre = re->vre;
	AZ(reip->task);
	reip->task = task;
	AN(reip->vre_limits);

	if (which == VENUM(req_body)) {
		if (ctx->req == NULL)
			errmsg(ctx, "vmod re: .match_body(which = req_body) "
			       "called but no request body found");
		else
			(void) VRB_Iterate(ctx->req->wrk, ctx->vsl, ctx->req,
					   iter, reip);
	}
	else if (which == VENUM(bereq_body) &&
	     ctx->bo != NULL && ctx->bo->bereq_body != NULL) {
		(void) ObjIterate(ctx->bo->wrk, ctx->bo->bereq_body,
				  reip, iter, 0);
	}
	else if (which == VENUM(bereq_body)) {
		if (ctx->bo == NULL || ctx->bo->req == NULL)
			errmsg(ctx, "vmod re: .match_body(which = bereq_body) "
			       "called but no backend request body found");
		else
			(void) VRB_Iterate(ctx->bo->wrk, ctx->vsl, ctx->bo->req,
					   iter, reip);
	}
	else if (which == VENUM(resp_body)) {
		if (ctx->req == NULL || ctx->req->objcore == NULL)
			errmsg(ctx, "vmod re: .match_body(which = resp_body) "
			       "called but no response body found");
		else
			(void) ObjIterate(ctx->req->wrk, ctx->req->objcore,
					  reip, iter, 0);
	}

	// XXX core code does not send OBJ_ITER_END reliably
	if (reip->s == PCRE2_ERROR_PARTIAL)
		(void) iter(reip, OBJ_ITER_END, "", 0L);

	AZ(reip->buf);

	assert(reip->s != PCRE2_ERROR_PARTIAL);

	r = reip->ret;
	memset(reip, 0, sizeof *reip);
	return (r > 0);
}

VCL_BOOL
vmod_regex_match_body(VRT_CTX, struct vmod_re_regex *re, VCL_ENUM which,
    VCL_INT limit, VCL_INT limit_recursion)
{
	struct re_iter_priv reip[1];
	struct vre_limits buf;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	INIT_OBJ(reip, RE_ITER_PRIV_MAGIC);
	reip->ctx = ctx;
	AZ(reip->sub);
	reip->vre_limits = get_limits(re, &buf, limit, limit_recursion);

	return (reip_match_body(reip, re, match_iter_f, which));
}

static int v_matchproto_(objiterate_f)
foreach_iter_f(void *priv, unsigned flush, const void *ptr, ssize_t len)
{
	struct re_iter_priv *reip;
	int i;

	CAST_OBJ_NOTNULL(reip, priv, RE_ITER_PRIV_MAGIC);

#ifdef ITERDBG
	VSLb(reip->ctx->vsl, SLT_Debug, "foreach_iter_f "
	     "flush=%u, s=%d, ptr=%.*s, len=%zd",
	     flush, reip->s, (int)len, (const char *)ptr, len);
#endif

	i = reip_iter(reip, flush, ptr, len);
	while (i == 0 && reip->s > PCRE2_ERROR_NOMATCH &&
	       ! VRT_handled(reip->ctx)) {
		VRT_call(reip->ctx, reip->sub);
		i = reip_iter(reip, flush, ptr, len);
	}

	return (i);
}

VCL_BOOL
vmod_regex_foreach_body(VRT_CTX, struct vmod_re_regex *re, VCL_ENUM which,
    VCL_SUB sub, VCL_INT limit, VCL_INT limit_recursion)
{
	struct re_iter_priv reip[1];
	struct vre_limits buf;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(sub);

	INIT_OBJ(reip, RE_ITER_PRIV_MAGIC);
	reip->ctx = ctx;
	reip->sub = sub;
	reip->vre_limits = get_limits(re, &buf, limit, limit_recursion);

	return (reip_match_body(reip, re, foreach_iter_f, which));
}

VCL_STRING
vmod_regex_backref(VRT_CTX, struct vmod_re_regex *re, VCL_INT refnum,
		   VCL_STRING fallback)
{
	struct vmod_priv *task;

	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);

	task = VRT_priv_task(ctx, re);
	if (task == NULL) {
		errmsg(ctx, "vmod re: Could not get a PRIV_TASK - out of workspace?");
		return (0);
	}
	return backref(ctx, refnum, fallback, task);
}

VCL_BOOL
vmod_match_dyn(VRT_CTX, struct vmod_priv *task, VCL_STRING pattern,
	       VCL_STRING subject, VCL_INT limit, VCL_INT limit_recursion)
{
	vre_t *vre;
	char errbuf[VRE_ERROR_LEN];
	int erroffset;
	VCL_BOOL dyn_return;
	struct vre_limits limits;

	AN(pattern);
	AN(task);

	if (limit < 1) {
		errmsg(ctx, "vmod re: invalid limit %d for regex \"%s\"",
		       limit, pattern);
		return 0;
	}

	if (limit_recursion < 1) {
		errmsg(ctx, "vmod re: invalid limit_recursion %d "
		       "for regex \"%s\"", limit_recursion, pattern);
		return 0;
	}

	if (limit > UINT_MAX)
		limit = UINT_MAX;
	if (limit_recursion > UINT_MAX)
		limit_recursion = UINT_MAX;

	limits.match = (unsigned)limit;
	limits.depth = (unsigned)limit_recursion;

	task->len = 0;
	vre = re_compile(pattern, 0, errbuf, sizeof errbuf, &erroffset);
	if (vre == NULL) {
		VSLb(ctx->vsl, SLT_VCL_Error,
		     "vmod re: error compiling regex \"%s\": %s (position %d)",
		     pattern, errbuf, erroffset);
		return 0;
	}

	dyn_return = (match(ctx, vre, subject, PCRE2_ZERO_TERMINATED, 0L, 0,
			    task, &limits)
		      > PCRE2_ERROR_NOMATCH);

	VRE_free(&vre);
	return dyn_return;
}

VCL_STRING
vmod_backref_dyn(VRT_CTX, struct vmod_priv *task, VCL_INT refnum,
		 VCL_STRING fallback)
{
	return backref(ctx, refnum, fallback, task);
}

VCL_STRING
vmod_version(VRT_CTX __attribute__((unused)))
{

	(void) ctx;
	return VERSION;
}

/*
 * ============================================================
 * filter VCL interface
 *
 * our id for the priv_task is the vfp pointer
 */

struct re_filter_subst {
	uint16_t			magic;
#define RE_FILTER_SUBST_MAGIC		0x6559
	uint16_t			flags;
#define REFS_F_FIXED			1	// no \n in s
	unsigned			n;
	VSLIST_ENTRY(re_filter_subst)	list;
	VCL_STRING			s;
};

VSLIST_HEAD(re_filter_substhead, re_filter_subst);

static struct re_filter_subst *
re_filter_subst_insert(struct re_filter_substhead *head,
    struct re_filter_subst *sub)
{
	struct re_filter_subst *e, *prev;

	if (VSLIST_EMPTY(head)) {
		VSLIST_FIRST(head) = sub;
		return (NULL);
	}
	e = VSLIST_FIRST(head);
	AN(e);
	if (sub->n < e->n) {
		VSLIST_INSERT_HEAD(head, sub, list);
		return (NULL);
	}

	prev = NULL;
	VSLIST_FOREACH_FROM(e, head, list) {
		if (sub->n > e->n) {
			prev = e;
			continue;
		}
		if (sub->n == e->n)
			return (e);
		break;
	}
	AN(prev);
	VSLIST_INSERT_AFTER(prev, sub, list);
	return (NULL);
}

VCL_VOID
vmod_regex_substitute_match(VRT_CTX,
    struct VPFX(re_regex) *re, VCL_INT n, VCL_STRING s)
{
	struct vmod_priv *task;
	struct re_filter_substhead *head;
	struct re_filter_subst *sub;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);

	if (! re->vfp) {
		VRT_fail(ctx, "vmod re: .substitute*() methods require "
		       "construction with asfilter=true");
		return;
	}

	task = VRT_priv_task(ctx, re->vfp);
	WS_TASK_ALLOC_OBJ(ctx, sub, RE_FILTER_SUBST_MAGIC);
	if (task == NULL || sub == NULL) {
		VRT_fail(ctx, "vmod re: out of workspace?");
		return;
	}
	//lint -e{740} the head is just a single pointer, as is the task priv
	head = (struct re_filter_substhead *)&task->priv;

	if (n < 0) {
		VRT_fail(ctx, "vmod re: substitute number "
		    "must not be negative");
		return;
	}

	if (n > UINT_MAX) {
		VRT_fail(ctx, "vmod re: substitute number "
		    "too big");
		return;
	}
	sub->n = (unsigned)n;
	sub->s = s;

	if (strchr(sub->s, '\\') == NULL)
		sub->flags |= REFS_F_FIXED;

	if (re_filter_subst_insert(head, sub))
		VRT_fail(ctx, "vmod re: substitute n=%lu already defined. "
			 "use .clear_substitutions() ?", n);
}

VCL_VOID vmod_regex_substitute_all(VRT_CTX,
    struct VPFX(re_regex) *re, VCL_STRING s)
{
	vmod_regex_clear_substitutions(ctx, re);
	vmod_regex_substitute_match(ctx, re, 0L, s);
}

VCL_VOID vmod_regex_clear_substitutions(VRT_CTX,
    struct VPFX(re_regex) *re)
{
	struct vmod_priv *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);

	if (! re->vfp) {
		VRT_fail(ctx, "vmod re: .clear_substitutions() requires "
		       "construction with asfilter=true");
		return;
	}

	task = VRT_priv_task_get(ctx, re->vfp);
	if (task == NULL)
		return;
	task->priv = NULL;
}

/*
 * ============================================================
 * filter
 *
 */

struct re_flt_state {
	unsigned			magic;
#define RE_FLT_STATE_MAGIC		0x4624f390
	unsigned			seen;
	uint32_t			re_options;
	struct vsl_log			*vsl;
	struct re_filter_substhead	*head;
	pcre2_code			*re;
	pcre2_match_context		*re_ctx;
	pcre2_match_data		*re_data;
	PCRE2_SIZE			re_min_length;
	struct rvb			i[1], o[1];
};

// common for vfp and vdp
static struct re_flt_state *
re_flt_init(VRT_CTX, const struct vmod_re_regex *re,
    struct re_filter_substhead *head)
{
	struct re_flt_state *res;

	WS_TASK_ALLOC_OBJ(ctx, res, RE_FLT_STATE_MAGIC);
	if (res == NULL) {
		VRT_fail(ctx, "vmod re: out of workspace?");
		return (NULL);
	}

	res->re_options = PCRE2_PARTIAL_HARD;
	res->vsl = ctx->vsl;
	res->head = head;

	res->re = VRE_unpack(re->vre);
	AN(res->re);

	res->re_ctx = pcre2_match_context_create(NULL);
	if (res->re_ctx == NULL) {
		VRT_fail(ctx, "vmod re_ctx create failed");
		return (NULL);
	}

	res->re_data = pcre2_match_data_create_from_pattern(res->re, NULL);
	if (res->re_data == NULL) {
		pcre2_match_context_free(res->re_ctx);
		VRT_fail(ctx, "vmod re_data create failed");
		return (NULL);
	}

	AZ(pcre2_pattern_info(res->re, PCRE2_INFO_MINLENGTH,
	    &res->re_min_length));

	AZ(pcre2_set_depth_limit(res->re_ctx, re->vre_limits.depth));
	AZ(pcre2_set_match_limit(res->re_ctx, re->vre_limits.match));

	return (res);
}

static void
re_flt_fini(struct re_flt_state **resp)
{
	struct re_flt_state *res;

	TAKE_OBJ_NOTNULL(res, resp, RE_FLT_STATE_MAGIC);

	AN(res->re_ctx);
	AN(res->re_data);
	pcre2_match_context_free(res->re_ctx);
	pcre2_match_data_free(res->re_data);
	if (rvb_is(res->i))
		rvb_fini(res->i);
	if (rvb_is(res->o))
		rvb_fini(res->o);
}

static enum vfp_status v_matchproto_(vfp_init_f)
re_vfp_init(VRT_CTX, struct vfp_ctx *vc, struct vfp_entry *vfe)
{
	const struct vmod_re_regex *re;
	struct re_flt_state *res;
	struct vmod_priv *task;

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

	CAST_OBJ_NOTNULL(re, vfe->vfp->priv1, VMOD_RE_REGEX_MAGIC);
	assert(re->vfp == vfe->vfp);

	task = VRT_priv_task_get(ctx, re->vfp);
	if (task == NULL || task->priv == NULL)
		return (VFP_NULL);

	//lint -e{740} the head is just a single pointer, as is the task priv
	res = re_flt_init(ctx, re, (struct re_filter_substhead *)&task->priv);
	if (res == NULL)
		return (VFP_ERROR);

	vfe->priv1 = res;

	http_Unset(vc->resp, H_Content_Length);
	http_Unset(vc->resp, H_ETag);

	return (VFP_OK);
}

//#define RE_FLT_DEBUG 1

#ifdef RE_FLT_DEBUG
static inline void
rvb_dbg(struct vsl_log *vsl, const char *pfx, struct rvb *b)
{
	if (rvb_is(b))
		VSLb(vsl, SLT_Debug,
		     "%s: %.*s>%.*s< f=%zd fl=0x%02x", pfx,
		     (int)(b->u.ro.r - b->u.ro.p), b->u.ro.p,
		     (int)(b->u.ro.w - b->u.ro.r), b->u.ro.r,
		     b->u.ro.l - b->u.ro.w, b->flags);
	else
		VSLb(vsl, SLT_Debug, "%s: (nil)", pfx);
}
#define RVB_DBG(vsl, x)   rvb_dbg(vsl, #x, x)
#define VFP_DBG(vsl, ...) VSLb(vsl, __VA_ARGS__)
#else
#define RVB_DBG(vsl, x)   (void)0
#define VFP_DBG(vsl, ...) (void)0
#endif

// realing ovector to ovector[0] == 0
static void
realign_ovector(PCRE2_SIZE *ovector, int matches)
{
	PCRE2_SIZE off;
	int i;

	off = ovector[0];
	if (off == 0)
		return;

	for (i = 0; i < matches * 2; i++) {
		assert(ovector[i] >= off);
		ovector[i] -= off;
	}
}

#define xisdigit(x) ((x) >= '0' && (x) <= '9')

// rvb_puto() the replacement with \n substituted
// basically VRE_sub()
static void
re_vfp_subst(struct rvb *out, struct rvb *overflow,
    const char *replacement, PCRE2_SIZE *ovector,
    const char *subj, unsigned matches)
{
	const char *s, *e;
	unsigned x;

	//lint -e{850} loop variable e modified
	for (s = e = replacement; *e != '\0'; e++ ) {
		if (*e != '\\' || e[1] == '\0')
			continue;
		rvb_puto(out, overflow, s, pdiff(s, e));
		s = ++e;
		if (xisdigit(*e)) {
			s++;
			x = (unsigned char)*e;
			x -= '0';
			if (x >= matches)
				continue;
			x *= 2;
			rvb_puto(out, overflow, subj + ovector[x],
			    ovector[x | 1] - ovector[x]);
			continue;
		}
	}
	rvb_puto(out, overflow, s, pdiff(s, e));
}

/*
 * return the right sub for the next match, if any
 *
 * there must be subs left (see "no replacements left" code)
 *
 */
static const struct re_filter_subst *
re_flt_sub(struct re_flt_state *res)
{
	struct re_filter_subst *sub0, *sub;

	res->seen++;

	sub = VSLIST_FIRST(res->head);
	CHECK_OBJ_NOTNULL(sub, RE_FILTER_SUBST_MAGIC);

	if (sub->n == 0) {
		sub0 = sub;
		sub = VSLIST_NEXT(sub, list);
	}
	else
		sub0 = NULL;

	if (sub != NULL) {
		CHECK_OBJ(sub, RE_FILTER_SUBST_MAGIC);
		if (sub->n != res->seen)
			sub = NULL;
	}

	if (sub != NULL) {
		if (sub0)
			VSLIST_REMOVE_AFTER(sub0, list);
		else
			VSLIST_REMOVE_HEAD(res->head, list);
		return (sub);
	}
	if (sub0 != NULL)
		return (sub0);
	return (NULL);
}

/*
 * (set +xe ; for b in {1..40} ; do echo $b ; rm -f src/vmod_re.*o ; make CFLAGS='-Wall -Werror -g -DTEST_REDUCED_BUFFER='$b -j 20 check ; done; echo TEST_REDUCED_BUFFER done)
 */

//#define TEST_REDUCED_BUFFER 3

static enum vfp_status v_matchproto_(vfp_pull_f)
re_vfp_pull(struct vfp_ctx *vc, struct vfp_entry *vfe,
    void *p, ssize_t *lp)
{
	const struct re_filter_subst *sub;
	struct re_flt_state *res;
	struct rvb out[1] = {0}, *b;
	PCRE2_SIZE *ovector;
	enum vfp_status r;
	int i;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);
	CAST_OBJ_NOTNULL(res, vfe->priv1, RE_FLT_STATE_MAGIC);
	AN(p);
	AN(lp);
	assert(*lp >= 0);

#ifdef TEST_REDUCED_BUFFER
	*lp = TEST_REDUCED_BUFFER;
	VSLb(res->vsl, SLT_Debug, "TEST_REDUCED_BUFFER %u",
	     TEST_REDUCED_BUFFER);
#endif

	rvb_init_wr(out, p, (size_t)*lp);
	*lp = 0;

	if (rvb_is(res->i) && rvb_rl(res->i) > 0)
		b = res->i;
	else
		b = out;

	//lint --e{801} could replace goto with while/continue
suck:
	VFP_DBG(res->vsl, SLT_Debug, "---");

	RVB_DBG(res->vsl, res->i);
	RVB_DBG(res->vsl, res->o);

	// append any remaining output
	if (rvb_is(res->o)) {
		rvb_append(out, res->o);
		if (rvb_rl(res->o) == 0)
			rvb_fini(res->o);	// XXX reuse?
		// output data not to be matched again
		rvb_consume_all(out);
	}

	// return to caller if buffer full or end of data
	if (rvb_wl(out) == 0 || (out->flags & RVB_F_END)) {
		AZ(rvb_rl(out));
		RVB_DBG(res->vsl, out);
		VFP_DBG(res->vsl, SLT_Debug, "=== ret fl 0x%02x",
		     out->flags);
		return (rvb_ret(out, p, lp));
	}

	/* no replacements left == noop - copy from in buffer */
	if (VSLIST_EMPTY(res->head) && rvb_is(res->i)) {
		rvb_take(res->o, res->i);
		b = out;
		goto suck;
	}

	/* no replacements left == noop - suck remaining data */
	if (VSLIST_EMPTY(res->head)) {
		assert(b == out);
		r = rvb_vfp_status(out);
		while (r == VFP_OK && rvb_wl(out) > 0)
			r = rvb_suck(out, vc);

		VFP_DBG(res->vsl, SLT_Debug, "=== ret noop");
		return (rvb_ret(out, p, lp));
	}

	// if the input buffer is consumed, reset it and read to out
	if (b == res->i && rvb_rl(res->i) == 0) {
		out->flags |= (res->i->flags & RVB_F_END);
		rvb_reset(res->i);
		rvb_consume_all(out);
		b = out;
	}

	r = rvb_vfp_status(b);
	if (r != VFP_END && rvb_wl(b) > 0) {
		VFP_DBG(res->vsl, SLT_Debug, "suck");
		r = rvb_suck(b, vc);

		if (r == VFP_ERROR) {
			VFP_DBG(res->vsl, SLT_Debug, "ERROR");
			return (r);
		}
		if (rvb_rl(b) == 0) {
			VFP_DBG(res->vsl, SLT_Debug, "=== END");
			return (rvb_ret(out, p, lp));
		}
	}
	RVB_DBG(res->vsl, b);

	if (r == VFP_END)
		res->re_options &= ~PCRE2_PARTIAL_HARD;

	/* XXX lookbehind issue: we would need to keep some
	 *     context around to fully support lookbehind
	 *
	 *     make it work without first and maybe revisit later
	 */

	i = pcre2_match(res->re, (PCRE2_SPTR)rvb_r(b), rvb_rl(b), 0UL,
	    res->re_options, res->re_data, res->re_ctx);

	if (i < PCRE2_ERROR_PARTIAL) {
		VFP_DBG(res->vsl, SLT_VCL_Error,
		     "vmod re: regex match returned %d", i);
		return (VFP_ERROR);
	}

	if (i == PCRE2_ERROR_PARTIAL) {
		assert(r != VFP_END);

		if (rvb_wl(b) > 0)
			goto suck;

		if (b == res->i) {
			rvb_compact(b);
			rvb_grow(b, 0UL);
			goto suck;
		}

		assert(b == out);

		if (rvb_is(res->i))
			rvb_reset(res->i);

		rvb_transfer(res->i, b, res->re_min_length << 1);

		b = res->i;
		goto suck;
	}

	res->re_options |= PCRE2_NOTBOL;

	AZ(rvb_is(res->o));
	if (i == PCRE2_ERROR_NOMATCH) {
		if (b == out)
			rvb_consume_all(b);
		else if (b == res->i) {
			rvb_take(res->o, res->i);
			b = out;
		}
		else
			WRONG("b must point to out or i");

		goto suck;
	}

	assert(i >= 0);

	ovector = pcre2_get_ovector_pointer(res->re_data);
	assert ((unsigned)i <= pcre2_get_ovector_count(res->re_data));

	VFP_DBG(res->vsl, SLT_Debug, "match %s %zd %zd", rvb_r(b),
	     ovector[0], ovector[1]);

	AZ(rvb_is(res->o));

	sub = re_flt_sub(res);
	if (sub == NULL) {
		if (b == res->i)
			// leave copy to rvb_append() at suck:
			rvb_ref_prefix(res->o, b, ovector[1]);
		else
			assert(b == out);
		rvb_consume(b, ovector[1]);
		goto suck;
	}

	/* keep bit before match
	 * consume match
	 * output replacement
	 */

	AZ(rvb_is(res->o));

	if (b == out) {
		rvb_consume(b, ovector[0]);
		if (rvb_is(res->i))
			AZ(rvb_rl(res->i));

		rvb_transfer(res->i, b, res->re_min_length << 1);

		b = res->i;
	}
	else if (b == res->i) {
		rvb_puto(out, res->o, rvb_r(b), ovector[0]);
		rvb_consume(b, ovector[0]);
	}
	else
		WRONG("b");

	// caller buffer is in output mode
	assert(b == res->i);

	realign_ovector(ovector, i);

	VFP_DBG(res->vsl, SLT_Debug, "%d subj %.*s repl %s",
	    i, (int)ovector[1], rvb_r(b), sub->s);

	if (sub->flags & REFS_F_FIXED)
		rvb_puto(out, res->o, sub->s, strlen(sub->s));
	else
		re_vfp_subst(out, res->o, sub->s, ovector,
		    rvb_r(b), (unsigned)i);

	AZ(ovector[0]);
	rvb_consume(b, ovector[1]);

	if (rvb_rl(b) == 0 && b->flags & RVB_F_END) {
		if (rvb_is(res->o) && rvb_rl(res->o))
			res->o->flags |= RVB_F_END;
		else
			out->flags |= RVB_F_END;
	}
	rvb_consume_all(out);

	goto suck;
}
static void v_matchproto_(vfp_fini_f)
re_vfp_fini(struct vfp_ctx *vc, struct vfp_entry *vfe)
{
	struct re_flt_state *res;

	CHECK_OBJ_NOTNULL(vc, VFP_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vfe, VFP_ENTRY_MAGIC);
	if (vfe->priv1 == NULL)
		return;
	TAKE_OBJ_NOTNULL(res, &vfe->priv1, RE_FLT_STATE_MAGIC);

	re_flt_fini(&res);
}

// from linux
#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) *__mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)) ; })

static int v_matchproto_(vdp_init_f)
re_vdp_init(VRT_CTX, struct vdp_ctx *vdx, void **priv)
{
	const struct vmod_re_regex *re;
	struct vdp_container *vdpc;
	struct re_flt_state *res;
	struct vdp_entry *vde;
	struct vmod_priv *task;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(vdx, VDP_CTX_MAGIC);

	/*
	 * making a mountain out of a molehill because we do not
	 * have a private pointer in the vdp... And not even a
	 * pointer to the vdp
	 */
	vde = container_of(priv, struct vdp_entry, priv);
	CHECK_OBJ_NOTNULL(vde, VDP_ENTRY_MAGIC);

	vdpc = container_of(vde->vdp, struct vdp_container, vdp);
	CHECK_OBJ_NOTNULL(vdpc, VMOD_RE_VDP_MAGIC);

	re = vdpc->re;
	CHECK_OBJ_NOTNULL(re, VMOD_RE_REGEX_MAGIC);

	task = VRT_priv_task_get(ctx, re->vfp);
	if (task == NULL || task->priv == NULL)
		return (1);

	//lint -e{740} the head is just a single pointer, as is the task priv
	res = re_flt_init(ctx, re, (struct re_filter_substhead *)&task->priv);
	if (res == NULL)
		return (-1);

	AN(priv);
	AZ(*priv);
	*priv = res;

	AN(vdx->clen);
	*vdx->clen = -1;

	return (0);
}

// we do not want to push the individual micro-matches:
// use the vfp subst function to write to a buffer/overflow and
// push those

static int
re_vdp_subst(struct vdp_ctx *vdx, enum vdp_action act,
    const char *replacement, PCRE2_SIZE *ovector,
    const char *subj, unsigned matches)
{
	// should have PCRE2_JIT_STACK_DEFAULT == 32k available
	char buf[16 * 1024];
	struct rvb out[1] = {0};
	struct rvb overflow[1] = {0};
	int r;

	rvb_init_wr(out, buf, sizeof *buf);

	re_vfp_subst(out, overflow, replacement, ovector, subj, matches);

	if (act == VDP_END) {
		if (rvb_is(overflow))
			overflow->flags |= RVB_F_END;
		else
			out->flags |=  RVB_F_END;
	}

	r = rvb_vdp_bytes(out, vdx);
	if (rvb_is(overflow)) {
		if (r)
			rvb_fini(overflow);
		else
			r = rvb_vdp_bytes(overflow, vdx);
	}
	return (r);
}

static int v_matchproto_(vdp_bytes_f)
re_vdp_bytes(struct vdp_ctx *vdx, enum vdp_action act, void **priv,
    const void *ptr, ssize_t len)
{
	const struct re_filter_subst *sub;
	struct rvb in[1] = {0}, *b;
	struct re_flt_state *res;
	PCRE2_SIZE *ovector;
	int i, r;

	CHECK_OBJ_NOTNULL(vdx, VDP_CTX_MAGIC);
	AN(priv);
	CAST_OBJ_NOTNULL(res, *priv, RE_FLT_STATE_MAGIC);

#ifdef TEST_REDUCED_BUFFER
	const char *p = ptr;
	const ssize_t n = TEST_REDUCED_BUFFER;
	VSLb(res->vsl, SLT_Debug, "TEST_REDUCED_BUFFER %u",
	     TEST_REDUCED_BUFFER);
	while (len > n) {
		if (re_vdp_bytes(vdx, VDP_NULL, priv, p, n))
			return (vdx->retval);
		p += n;
		len -= n;
	}
	ptr = p;
#endif
	/*
	 * cut short for VDP_FLUSH, VDP_END etc -
	 * process only if we have at least one byte
	 */
	if (len == 0 && (! rvb_is(res->i) || rvb_rl(res->i) == 0))
		return (VDP_bytes(vdx, act, ptr, len));
	if (len == 0 && ptr == NULL)
		ptr = "";

	assert(len >= 0);
	rvb_init_ro(in, ptr, (size_t)len);
	if (act == VDP_END)
		in->flags |= RVB_F_END;
	else if (act == VDP_NULL)
		in->flags |= RVB_F_STABLE;

	VFP_DBG(res->vsl, SLT_Debug, "---");

	RVB_DBG(res->vsl, res->i);
	RVB_DBG(res->vsl, in);

	// we never buffer output in the VDP
	AZ(rvb_is(res->o));

	// if we have an input buffer, transfer to it
	if (rvb_is(res->i)) {
		rvb_transfer(res->i, in, res->re_min_length << 1);
		b = res->i;
	}
	else
		b = in;

	RVB_DBG(res->vsl, b);

	if (b->flags & RVB_F_END)
		res->re_options &= ~PCRE2_PARTIAL_HARD;

	//lint --e{801} goto
more:
	AN(rvb_rl(b));

	// no replacements left == noop, push buffers
	if (VSLIST_EMPTY(res->head))
		return (rvb_vdp_bytes(b, vdx));

	/* XXX lookbehind issue: we would need to keep some
	 *     context around to fully support lookbehind
	 *
	 *     make it work without first and maybe revisit later
	 */

	i = pcre2_match(res->re, (PCRE2_SPTR)rvb_r(b), rvb_rl(b), 0UL,
	    res->re_options, res->re_data, res->re_ctx);

	if (i < PCRE2_ERROR_PARTIAL) {
		VFP_DBG(res->vsl, SLT_VCL_Error,
		     "vmod re: regex match returned %d", i);
		return (-1);
	}

	if (i == PCRE2_ERROR_PARTIAL) {
		AZ(b->flags & RVB_F_END);

		// save input if not already done above
		if (b == in)
			rvb_transfer(res->i, in, res->re_min_length << 1);
		else if (b == res->i)
			rvb_compact(res->i);
		else
			WRONG("b for partial match");

		// we should be called again...
		return (0);
	}

	res->re_options |= PCRE2_NOTBOL;

	if (i == PCRE2_ERROR_NOMATCH)
		return (rvb_vdp_bytes(b, vdx));

	assert(i >= 0);

	ovector = pcre2_get_ovector_pointer(res->re_data);
	assert (i <= (int)pcre2_get_ovector_count(res->re_data));

	VFP_DBG(res->vsl, SLT_Debug, "match %s %zd %zd", rvb_r(b),
	     ovector[0], ovector[1]);

	sub = re_flt_sub(res);
	if (sub == NULL) {
		AZ(ovector[0]);
		assert(rvb_rl(b) >= ovector[1]);

		if (rvb_rl(b) == ovector[1] && b->flags & RVB_F_END)
			act = VDP_END;
		else
			act = VDP_NULL;

		r = VDP_bytes(vdx, act, rvb_r(b), (ssize_t)ovector[1]);

		rvb_consume(b, ovector[1]);

		if (r || act == VDP_END || rvb_rl(b) == 0)
			return (r);

		goto more;
	}

	/* keep bit before match
	 * consume match
	 * output replacement
	 */

	if (ovector[0] > 0) {
		rvb_ref_prefix(res->o, b, ovector[0]);
		rvb_consume(b, ovector[0]);
		if (rvb_vdp_bytes(res->o, vdx))
			return (vdx->retval);
	}

	realign_ovector(ovector, i);

	VFP_DBG(res->vsl, SLT_Debug, "%d subj %.*s repl %s",
	    i, (int)ovector[1], rvb_r(b), sub->s);

	assert(rvb_rl(b) >= ovector[1]);

	// more to come?
	if (rvb_rl(b) == ovector[1] && b->flags & RVB_F_END)
		act = VDP_END;
	else
		act = VDP_NULL;

	if (sub->flags & REFS_F_FIXED)
		r = VDP_bytes(vdx, act, sub->s, (ssize_t)strlen(sub->s));
	else
		r = re_vdp_subst(vdx, act, sub->s, ovector, rvb_r(b), (unsigned)i);

	AZ(ovector[0]);
	rvb_consume(b, ovector[1]);

	if (r || act == VDP_END || rvb_rl(b) == 0)
		return (r);

	goto more;
}

static int v_matchproto_(vdp_fini_f)
re_vdp_fini(struct vdp_ctx *vdx, void **priv)
{
	struct re_flt_state *res;

	CHECK_OBJ_NOTNULL(vdx, VDP_CTX_MAGIC);
	AN(priv);
	if (*priv == NULL)
		return (0);
	TAKE_OBJ_NOTNULL(res, priv, RE_FLT_STATE_MAGIC);

	re_flt_fini(&res);
	return (0);
}
