/*-
 * Copyright (c) 2016 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved
 *
 * Author: Geoffrey Simmons <geoffrey.simmons@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 "vmod_re2.h"

#include "config.h"

#define REGEX_OPTS							\
	VCL_BOOL utf8, VCL_BOOL posix_syntax, VCL_BOOL longest_match,	\
		VCL_INT max_mem, VCL_BOOL literal, VCL_BOOL never_nl,	\
		VCL_BOOL dot_nl, VCL_BOOL never_capture,		\
		VCL_BOOL case_sensitive, VCL_BOOL perl_classes,		\
		VCL_BOOL word_boundary, VCL_BOOL one_line

typedef struct task_match_t {
	unsigned	magic;
#define TASK_MATCH_MAGIC 0xa4b93c57
	vre2		*vre2;
	void		*groups;
	int		ngroups;
	unsigned	never_capture;
} task_match_t;

static char c;
static const void *match_failed = (void *) &c;

static size_t match_sz;

const char * const rewrite_name[] = {
	[SUB]     = "sub",
	[SUBALL]  = "suball",
	[EXTRACT] = "extract",
};

static void
free_task_match(VRT_CTX, void *p)
{
	task_match_t *task_match;
	(void)ctx;

	CAST_OBJ_NOTNULL(task_match, p, TASK_MATCH_MAGIC);
	if (task_match->vre2 != NULL)
		vre2_fini(&task_match->vre2);
}

static const struct vmod_priv_methods match_priv_task_methods[1] = {{
		.magic = VMOD_PRIV_METHODS_MAGIC,
		.type = "vmod_re2_match_priv_task",
		.fini = free_task_match
}};


#define ERR_PREFIX "match(\"%.40s\"): "

static VCL_BOOL
match(VRT_CTX, vre2 * restrict vre2, VCL_STRING restrict subject,
      void * restrict * const groups, const unsigned never_capture,
      int ngroups)
{
	int match = 0, len;
	const char *err;
	char *text = (void *) subject;
	uintptr_t snap;

	AN(groups);
	if (subject == NULL)
		subject = "";
	len = strlen(subject);
	*groups = NULL;

	snap = WS_Snapshot(ctx->ws);
	if (!never_capture) {
		ngroups++;
		if (!WS_Allocated(ctx->ws, subject, len + 1))
			if ((text = WS_Copy(ctx->ws, subject, len + 1))
			    == NULL) {
				VERRNOMEM(ctx, ERR_PREFIX "copying subject",
					  subject);
				return 0;
			}
		if ((*groups = WS_Alloc(ctx->ws, ngroups * match_sz))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "allocating match data",
				  subject);
			WS_Reset(ctx->ws, snap);
			return 0;
		}
	}

	if ((err = vre2_match(vre2, text, len, &match, ngroups, *groups))
	    != NULL) {
		VFAIL(ctx, ERR_PREFIX "%s", subject, err);
		if (!never_capture)
			WS_Reset(ctx->ws, snap);
		return 0;
	}

	if (!match) {
		*groups = (void *)match_failed;
		if (!never_capture)
			WS_Reset(ctx->ws, snap);
	}
	return match;
}

#undef ERR_PREFIX

#define ERR_PREFIX "backref %ld, fallback \"%.40s\": "

static VCL_STRING
backref(VRT_CTX, VCL_INT refnum, VCL_STRING fallback, void * const groups,
	const int ngroups)
{
	const char *err, *capture;
	char *backref;
	int len;

	AN(groups);
	if (groups == match_failed)
		return fallback;

	WS_Assert_Allocated(ctx->ws, groups, (ngroups + 1) * match_sz);

	if ((err = vre2_capture(groups, (int) refnum, &capture, &len))
	    != NULL) {
		VFAIL(ctx, ERR_PREFIX "error retrieving capture: %s", refnum,
		      fallback, err);
		return NULL;
	}

	assert(len >= 0);
	if (capture == NULL)
		return fallback;
	if (len == 0)
		return "";

	WS_Assert_Allocated(ctx->ws, capture, len);
	if ((backref = WS_Copy(ctx->ws, capture, len + 1)) == NULL) {
		VERRNOMEM(ctx, ERR_PREFIX "copying backref", refnum, fallback);
		return NULL;
	}
	backref[len] = '\0';
	return backref;
}

#undef ERR_PREFIX

#define ERR_PREFIX "namedref name=\"%.40s\", fallback=\"%.40s\": "

static VCL_STRING
namedref(VRT_CTX, vre2 * restrict vre2, VCL_STRING name, VCL_STRING fallback,
	 void * const restrict groups, const int ngroups)
{
	int refnum;
	const char *err;

	if ((err = vre2_get_group(vre2, name, &refnum)) != NULL) {
		VFAIL(ctx, ERR_PREFIX "%s", name, fallback, err);
		return NULL;
	}
	if (refnum == -1) {
		VFAIL(ctx, ERR_PREFIX "no such named group", name, fallback);
		return NULL;
	}
	assert(refnum > 0 && refnum <= ngroups);
	return backref(ctx, refnum, fallback, groups, ngroups);
}

#undef ERR_PREFIX

#define ERR_PREFIX \
	"%s(text=\"%.40s\", rewrite=\"%.40s\", fallback=\"%.40s\"): "

static VCL_STRING
rewritef(VRT_CTX, vre2 * restrict vre2, const rewrite_e mode, VCL_STRING text,
	 VCL_STRING rewrite, VCL_STRING fallback)
{
	int match = 0;
	size_t bytes, len;
	char *ret;
	const char *err;

	bytes = WS_ReserveAll(ctx->ws);
	ret = WS_Reservation(ctx->ws);
	if (bytes == 0) {
		VERRNOMEM(ctx, ERR_PREFIX "workspace overflow",
			  rewrite_name[mode], text, rewrite, fallback);
		WS_Release(ctx->ws, 0);
		return NULL;
	}
	if ((err = vre2_rewrite(vre2, mode, text, rewrite, ret, bytes, &match,
				&len))
	    != NULL) {
		VFAIL(ctx, ERR_PREFIX "%s", rewrite_name[mode], text, rewrite,
		      fallback, err);
		WS_Release(ctx->ws, 0);
		return NULL;
	}
	if (!match) {
		WS_Release(ctx->ws, 0);
		return fallback;
	}
	WS_Release(ctx->ws, len + 1);
	return ret;
}

#undef ERR_PREFIX

/* Event function */

int
vmod_event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
{
	(void) ctx;
	(void) priv;

	if (e == VCL_EVENT_LOAD)
		match_sz = vre2_matchsz();
	return 0;
}

/* Object regex */

VCL_VOID
vmod_regex__init(const struct vrt_ctx *ctx, struct vmod_re2_regex **rep,
		 const char *vcl_name, VCL_STRING pattern, REGEX_OPTS)
{
	struct vmod_re2_regex *re;
	const char *err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(rep);
	AZ(*rep);
	AN(vcl_name);
	AN(pattern);
	ALLOC_OBJ(re, VMOD_RE2_REGEX_MAGIC);
	AN(re);
	*rep = re;

	if ((err = vre2_init(&re->vre2, pattern, utf8, posix_syntax,
			     longest_match, max_mem, literal, never_nl, dot_nl,
			     never_capture, case_sensitive, perl_classes,
			     word_boundary, one_line)) != NULL) {
		VFAIL(ctx, "%s constructor: cannot compile '%.40s': %s",
		      vcl_name, pattern, err);
		return;
	}
	if (! never_capture) {
		if ((err = vre2_ngroups(re->vre2, &re->ngroups)) != NULL) {
			VFAIL(ctx, "%s constructor: cannot obtain number of "
			      "capturing groups: %s", vcl_name, err);
			return;
		}
		assert(re->ngroups >= 0);
	}
	re->never_capture = never_capture;
	re->vcl_name = strdup(vcl_name);
}

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

	if (rep == NULL || *rep == NULL)
		return;
	CHECK_OBJ(*rep, VMOD_RE2_REGEX_MAGIC);
	re = *rep;
	*rep = NULL;
	vre2_fini(&re->vre2);
	if (re->vcl_name != NULL)
		free(re->vcl_name);
	FREE_OBJ(re);
}

#define ERR_PREFIX "%s.match(subject=\"%.40s\"): "

VCL_BOOL
vmod_regex_match(const struct vrt_ctx *ctx, struct vmod_re2_regex *re,
		 VCL_STRING subject)
{
	VCL_BOOL ret;
	struct vmod_priv *task;
	task_match_t *task_match;

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

	task = VRT_priv_task(ctx, re);
	if (task == NULL) {
		VFAIL(ctx, ERR_PREFIX "No priv_task - workspace overflow?",
		      re->vcl_name, subject);
		return 0;
	}
	if (task->priv == NULL) {
		if ((task->priv = WS_Alloc(ctx->ws, sizeof(*task_match)))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "allocating match data",
				  re->vcl_name, subject);
			return 0;
		}
		task->len = sizeof(*task_match);
		AZ(task->methods);
		task_match = (task_match_t *)task->priv;
		task_match->magic = TASK_MATCH_MAGIC;
	}
	else {
		WS_Assert_Allocated(ctx->ws, task->priv, sizeof(*task_match));
		CAST_OBJ(task_match, task->priv, TASK_MATCH_MAGIC);
	}

	ret = match(ctx, re->vre2, subject, &task_match->groups,
		    re->never_capture, re->ngroups);
	return ret;
}

#undef ERR_PREFIX

#define ERR_PREFIX "%s.backref(ref=%ld, fallback=\"%.40s\"): "

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

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE2_REGEX_MAGIC);
	assert(refnum >= 0);
	if (fallback == NULL) {
		VFAIL(ctx, ERR_PREFIX "fallback is undefined", re->vcl_name,
		      refnum, "<undefined>");
		return NULL;
	}

	if (re->never_capture) {
		VFAIL(ctx, ERR_PREFIX "never_capture is true for object %s",
		     re->vcl_name, refnum, fallback, re->vcl_name);
		return NULL;
	}
	if (refnum > re->ngroups) {
		VFAIL(ctx, ERR_PREFIX "backref out of range (max %d)",
		     re->vcl_name, refnum, fallback, re->ngroups);
		return NULL;
	}

	task = VRT_priv_task(ctx, re);
	if (task == NULL) {
		VFAIL(ctx, ERR_PREFIX "No priv_task - workspace overflow?",
		      re->vcl_name, refnum, fallback);
		return NULL;
	}
	if (task->priv == NULL) {
		VFAIL(ctx, ERR_PREFIX "backref called without prior match",
		      re->vcl_name, refnum, fallback);
		return NULL;
	}
	WS_Assert_Allocated(ctx->ws, task->priv, sizeof(*task_match));
	CAST_OBJ(task_match, task->priv, TASK_MATCH_MAGIC);
	return backref(ctx, refnum, fallback, task_match->groups, re->ngroups);
}

#undef ERR_PREFIX

#define ERR_PREFIX "%s.namedref(name=\"%.40s\", fallback=\"%.40s\"): "

VCL_STRING
vmod_regex_namedref(VRT_CTX, struct vmod_re2_regex *re, VCL_STRING name,
		    VCL_STRING fallback)
{
	struct vmod_priv *task;
	task_match_t *task_match;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE2_REGEX_MAGIC);
	if (fallback == NULL) {
		VFAIL(ctx, "%s.namedref(): fallback is undefined",
		      re->vcl_name);
		return NULL;
	}
	if (name == NULL || name[0] == '\0') {
		VFAIL(ctx, ERR_PREFIX "name is empty", re->vcl_name, "",
		      fallback);
		return NULL;
	}

	if (re->never_capture) {
		VFAIL(ctx, ERR_PREFIX "never_capture is true for object %s",
		      re->vcl_name, name, fallback, re->vcl_name);
		return NULL;
	}

	task = VRT_priv_task(ctx, re);
	if (task == NULL) {
		VFAIL(ctx, ERR_PREFIX "No priv_task - workspace overflow?",
		      re->vcl_name, name, fallback);
		return 0;
	}
	if (task->priv == NULL) {
		VFAIL(ctx, ERR_PREFIX "namedref called without prior match",
		      re->vcl_name, name, fallback);
		return NULL;
	}
	WS_Assert_Allocated(ctx->ws, task->priv, sizeof(*task_match));
	CAST_OBJ(task_match, task->priv, TASK_MATCH_MAGIC);
	return namedref(ctx, re->vre2, name, fallback, task_match->groups,
			re->ngroups);
}

#undef ERR_PREFIX

static VCL_STRING
rewrite_method(VRT_CTX, const rewrite_e mode,
	       struct vmod_re2_regex * restrict re, VCL_STRING restrict text,
	       VCL_STRING restrict rewrite, VCL_STRING restrict fallback)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE2_REGEX_MAGIC);
	if (fallback == NULL) {
		VFAIL(ctx, "%s.%s(): fallback is undefined", re->vcl_name,
		     rewrite_name[mode]);
		return NULL;
	}
	if (text == NULL) {
		VFAIL(ctx, "%s.%s(text=<undefined>, fallback=\"%.40s\"): "
		      "text is undefined", re->vcl_name, rewrite_name[mode],
		      fallback);
		return NULL;
	}
	if (rewrite == NULL) {
		VFAIL(ctx, "%s.%s(text=\"%.40s\", rewrite=<undefined>, "
		     "fallback=\"%.40s\"): rewrite is undefined", re->vcl_name,
		     rewrite_name[mode], text, fallback);
		return NULL;
	}

	return rewritef(ctx, re->vre2, mode, text, rewrite, fallback);
}

VCL_STRING
vmod_regex_sub(VRT_CTX, struct vmod_re2_regex *re, VCL_STRING text,
	       VCL_STRING rewrite, VCL_STRING fallback)
{
	return rewrite_method(ctx, SUB, re, text, rewrite, fallback);
}

VCL_STRING
vmod_regex_suball(VRT_CTX, struct vmod_re2_regex *re, VCL_STRING text,
		  VCL_STRING rewrite, VCL_STRING fallback)
{
	return rewrite_method(ctx, SUBALL, re, text, rewrite, fallback);
}

VCL_STRING
vmod_regex_extract(VRT_CTX, struct vmod_re2_regex *re, VCL_STRING text,
		   VCL_STRING rewrite, VCL_STRING fallback)
{
	return rewrite_method(ctx, EXTRACT, re, text, rewrite, fallback);
}

static VCL_INT
cost(VRT_CTX, vre2 * const restrict vre2, const char * const restrict context)
{
	int cost;
	const char *err;

	if ((err = vre2_cost(vre2, &cost)) != NULL) {
		VFAIL(ctx, "%s.cost(): Cannot retrieve cost: %s", context,
		     err);
		return (-1);
	}
	return cost;
}

VCL_INT
vmod_regex_cost(VRT_CTX, struct vmod_re2_regex *re)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(re, VMOD_RE2_REGEX_MAGIC);

	return cost(ctx, re->vre2, re->vcl_name);
}

/* Regex function interface */

#define ERR_PREFIX "re2.match(pattern=\"%.40s\", text=\"%.40s\"): "

VCL_BOOL
vmod_match(VRT_CTX, struct vmod_priv *priv, VCL_STRING pattern,
	   VCL_STRING subject, REGEX_OPTS)
{
	vre2 *vre2 = NULL;
	task_match_t *task_match;
	const char *err;
	void *groups = NULL, **groupsp = &groups;
	int ngroups = 0;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	if (subject == NULL)
		subject = "";
	if (pattern == NULL) {
		VFAIL(ctx, ERR_PREFIX "pattern is undefined", "<undefined>",
		      subject);
		return 0;
	}

	if ((err = vre2_init(&vre2, pattern, utf8, posix_syntax, longest_match,
			     max_mem, literal, never_nl, dot_nl, never_capture,
			     case_sensitive, perl_classes, word_boundary,
			     one_line))
	    != NULL) {
		VFAIL(ctx, ERR_PREFIX "Cannot compile: %s", pattern, subject,
		      err);
		vre2_fini(&vre2);
		return 0;
	}
	if (! never_capture) {
		if ((err = vre2_ngroups(vre2, &ngroups)) != NULL) {
			VFAIL(ctx,
			      ERR_PREFIX "Cannot obtain number of capturing "
			      "groups: %s", pattern, subject, err);
			vre2_fini(&vre2);
			return 0;
		}
		assert(ngroups >= 0);
	}
	if (priv->priv == NULL) {
		if ((task_match = WS_Alloc(ctx->ws, sizeof(*task_match)))
		    == NULL) {
			VERRNOMEM(ctx, ERR_PREFIX "allocating match data",
				  pattern, subject);
			vre2_fini(&vre2);
			return 0;
		}
		priv->priv = task_match;
		priv->len = sizeof(*task_match);
		priv->methods = match_priv_task_methods;
		task_match->magic = TASK_MATCH_MAGIC;
	}
	else {
		WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task_match));
		CAST_OBJ(task_match, priv->priv, TASK_MATCH_MAGIC);
	}
	task_match->ngroups = ngroups;
	task_match->never_capture = never_capture;
	task_match->vre2 = vre2;
	groupsp = &task_match->groups;

	return match(ctx, vre2, subject, groupsp, never_capture, ngroups);
}

#undef ERR_PREFIX

#define ERR_PREFIX "re2.backref(ref=%ld, fallback=\"%.40s\"): "

VCL_STRING
vmod_backref(VRT_CTX, struct vmod_priv *priv, VCL_INT refnum,
	     VCL_STRING fallback)
{
	task_match_t *task_match;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(priv);
	assert(refnum >= 0);
	if (fallback == NULL) {
		VFAIL(ctx, ERR_PREFIX "fallback is undefined",  refnum,
		      "<undefined>");
		return NULL;
	}
	if (priv->priv == NULL) {
		VFAIL(ctx, ERR_PREFIX "called without previous match", refnum,
		      fallback);
		return NULL;
	}

	WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task_match));
	CAST_OBJ(task_match, priv->priv, TASK_MATCH_MAGIC);

	if (task_match->never_capture) {
		VFAIL(ctx,
		      ERR_PREFIX "never_capture was true in previous match",
		      refnum, fallback);
		return NULL;
	}
	if (refnum > task_match->ngroups) {
		VFAIL(ctx, ERR_PREFIX "backref out of range (max %d)", refnum,
		      fallback, task_match->ngroups);
		return NULL;
	}
	return backref(ctx, refnum, fallback, task_match->groups,
		       task_match->ngroups);
}

#undef ERR_PREFIX

#define ERR_PREFIX "re2.namedref(name=\"%.40s\", fallback=\"%.40s\"): "

VCL_STRING
vmod_namedref(VRT_CTX, struct vmod_priv *priv, VCL_STRING name,
	      VCL_STRING fallback)
{
	task_match_t *task_match;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(priv);
	if (fallback == NULL) {
		VFAIL0(ctx, "re2.namedref(): fallback is undefined");
		return NULL;
	}
	if (name == NULL || name[0] == '\0') {
		VFAIL(ctx, ERR_PREFIX "name is empty", "", fallback);
		return NULL;
	}
	if (priv->priv == NULL) {
		VFAIL(ctx, ERR_PREFIX "called without previous match", name,
		      fallback);
		return NULL;
	}

	WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task_match));
	CAST_OBJ(task_match, priv->priv, TASK_MATCH_MAGIC);

	if (task_match->never_capture) {
		VFAIL(ctx,
		      ERR_PREFIX "never_capture was true in previous match",
		      name, fallback);
		return NULL;
	}
	return namedref(ctx, task_match->vre2, name, fallback,
			task_match->groups, task_match->ngroups);
}

#undef ERR_PREFIX

#define ERR_PREFIX "re2.%s(pattern=\"%.40s\", text=\"%.40s\", rewrite=\"%.40s\", fallback=\"%.40s\"): "

static VCL_STRING
rewrite_function(VRT_CTX, const rewrite_e mode, VCL_STRING restrict pattern,
		 VCL_STRING restrict text, VCL_STRING restrict rewrite,
		 VCL_STRING restrict fallback, REGEX_OPTS)
{
	vre2 *vre2 = NULL;
	const char *ret, *err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	if (fallback == NULL) {
		VFAIL(ctx, "re2.%s(): fallback is undefined",
		      rewrite_name[mode]);
		return NULL;
	}
	if (pattern == NULL) {
		VFAIL(ctx, "re2.%s(pattern=<undefined>, fallback=\"%.40s\"): "
		      "pattern is undefined", rewrite_name[mode], fallback);
		return NULL;
	}
	if (text == NULL) {
		VFAIL(ctx, "re2.%s(pattern=\"%s\", text=<undefined>, "
		      "fallback=\"%.40s\"): text is undefined",
		      rewrite_name[mode], pattern, fallback);
		return NULL;
	}
	if (rewrite == NULL) {
		VFAIL(ctx, "re2.%s(pattern=\"%.40s\", text=\"%.40s\", "
		      "rewrite=<undefined>, fallback=\"%.40s\"): "
		      "rewrite is undefined", rewrite_name[mode], pattern, text,
		      fallback);
		return NULL;
	}

	if ((err = vre2_init(&vre2, pattern, utf8, posix_syntax, longest_match,
			     max_mem, literal, never_nl, dot_nl, never_capture,
			     case_sensitive, perl_classes, word_boundary,
			     one_line))
	    != NULL) {
		VFAIL(ctx, ERR_PREFIX "Cannot compile '%s': %s",
		      rewrite_name[mode], pattern, text, rewrite, fallback,
		      pattern, err);
		vre2_fini(&vre2);
		return NULL;
	}
	ret = rewritef(ctx, vre2, mode, text, rewrite, fallback);
	vre2_fini(&vre2);
	return ret;
}

#undef ERR_PREFIX

VCL_STRING
vmod_sub(VRT_CTX, VCL_STRING pattern, VCL_STRING text, VCL_STRING rewrite,
	 VCL_STRING fallback, REGEX_OPTS)
{
	return rewrite_function(ctx, SUB, pattern, text, rewrite, fallback,
				utf8, posix_syntax, longest_match, max_mem,
				literal, never_nl, dot_nl, never_capture,
				case_sensitive, perl_classes, word_boundary,
				one_line);
}

VCL_STRING
vmod_suball(VRT_CTX, VCL_STRING pattern, VCL_STRING text, VCL_STRING rewrite,
	    VCL_STRING fallback, REGEX_OPTS)
{
	return rewrite_function(ctx, SUBALL, pattern, text, rewrite, fallback,
				utf8, posix_syntax, longest_match, max_mem,
				literal, never_nl, dot_nl, never_capture,
				case_sensitive, perl_classes, word_boundary,
				one_line);
}

VCL_STRING
vmod_extract(VRT_CTX, VCL_STRING pattern, VCL_STRING text, VCL_STRING rewrite,
	     VCL_STRING fallback, REGEX_OPTS)
{
	return rewrite_function(ctx, EXTRACT, pattern, text, rewrite, fallback,
				utf8, posix_syntax, longest_match, max_mem,
				literal, never_nl, dot_nl, never_capture,
				case_sensitive, perl_classes, word_boundary,
				one_line);
}

#define ERR_PREFIX "re2.quotemeta(\"%.40s\"): "

VCL_STRING
vmod_quotemeta(VRT_CTX, VCL_STRING unquoted)
{
	size_t bytes, len;
	char *ret;
	const char *err;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	bytes = WS_ReserveAll(ctx->ws);
	ret = WS_Reservation(ctx->ws);
	if (bytes == 0) {
		VERRNOMEM(ctx,
			  ERR_PREFIX "reserving workspace for the quoted string",
			  unquoted);
		WS_Release(ctx->ws, 0);
		return NULL;
	}
	if ((err = vre2_quotemeta(unquoted, ret, bytes, &len)) != NULL) {
		VFAIL(ctx, ERR_PREFIX "%s", unquoted, err);
		WS_Release(ctx->ws, 0);
		return NULL;
	}
	WS_Release(ctx->ws, len + 1);
	return ret;
}

#undef ERR_PREFIX

VCL_INT
vmod_cost(VRT_CTX, VCL_STRING pattern, REGEX_OPTS)
{
	vre2 *vre2 = NULL;
	const char *err;
	VCL_INT kost;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);

	if ((err = vre2_init(&vre2, pattern, utf8, posix_syntax, longest_match,
			     max_mem, literal, never_nl, dot_nl, never_capture,
			     case_sensitive, perl_classes, word_boundary,
			     one_line))
	    != NULL) {
		VFAIL(ctx, "re2.cost(\"%.40s\"): Cannot compile: %s", pattern,
		      err);
		vre2_fini(&vre2);
		return -1;
	}

	kost = cost(ctx, vre2, "re2");
	vre2_fini(&vre2);
	return kost;
}

VCL_STRING
vmod_version(const struct vrt_ctx *ctx __attribute__((unused)))
{
	return VERSION;
}
