/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2008 Linpro 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.
 *
 * $Id: vcc_string.c 3728 2009-02-11 06:55:23Z tfheen $
 */

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <regex.h>

#include "vsb.h"

#include "vcc_priv.h"
#include "vcc_compile.h"
#include "libvarnish.h"

#include "vrt.h"

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

/* finalizer to be called upon exit of function parse */
void
vcc_proc_finalize_regexp(struct tokenlist *tl, struct proc *p)
{
	struct procpm	*pm;
	int		max = -1;

	/*
	 * iterate through struct procpm list in reverse order to
	 * get maximum possible back reference
	 */

	VTAILQ_FOREACH_REVERSE(pm, &p->pmatches, procpmh, list) {
		if (pm->max_pmatch > max)
			max = pm->max_pmatch;

		Fh(tl, 0, "#define VGC_re_%u_maxpmatch %d\n", pm->re,
		    max);
	}
}

static char *
_vcc_regexp(struct tokenlist *tl, int regsub)
{
	char buf[BUFSIZ], *p;
	regex_t	t;
	int i;
	struct procpm	*pm;

	Expect(tl, CSTR);
	if (tl->err)
		return (NULL);
	memset(&t, 0, sizeof t);

	/* 
	 * At VCC compile time, do the test compile with subexpression check. At
	 * VCL ini time, we'll know how many subexpression backreferences we
	 * need at most and will compile the re accordingly
	 */
	i = regcomp(&t, tl->t->dec, REG_EXTENDED);
	if (i != 0) {
		(void)regerror(i, &t, buf, sizeof buf);
		vsb_printf(tl->sb,
		    "Regexp compilation error:\n\n%s\n\n", buf);
		vcc_ErrWhere(tl, tl->t);
		return (NULL);
	}
	regfree(&t);

	/*
	 * Number of possible back references: As any match operator might fail
	 * (preserving the previous content in the pmatch array), the maximum
	 * back reference for a particular match operator is the maximum of all
	 * back references following it.
	 *
	 * Thus we note the max number of pmatch backreferences after each match
	 */

	if (regsub) {
		/* static number of sub-REs for regsub */
		Fh(tl, 0, "#define VGC_re_%u_maxpmatch %d\n", tl->recnt,
		    9);
	} else {
		/* 
		 * Alloc and init struct procpm for pmatch xrefs after this
		 * RE
		 */
		pm = TlAlloc(tl, sizeof *pm);
		assert(pm != NULL);
		pm->re = tl->recnt;
		pm->max_pmatch = -1;
		VTAILQ_INSERT_TAIL(&tl->curproc->pmatches, pm, list);
	}

	sprintf(buf, "VGC_re_%u", tl->recnt);
	p = TlAlloc(tl, strlen(buf) + 1);
	strcpy(p, buf);

	Fh(tl, 0, "static void *%s;\n", buf);
	Fi(tl, 0, "\tVRT_re_init(&%s, ", buf);
	EncToken(tl->fi, tl->t);
	Fi(tl, 0, ", VGC_re_%u_maxpmatch);\n", tl->recnt);
	Ff(tl, 0, "\tVRT_re_fini(%s);\n", buf);

	tl->recnt++;

	return (p);
}

char *
vcc_regexp(struct tokenlist *tl)
{
	return (_vcc_regexp(tl, 0));
}

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

static int
vcc_regsub(struct tokenlist *tl, int all)
{
	char *p;

	vcc_NextToken(tl);

	Fb(tl, 0, "VRT_regsub(sp, %d, ", all);

	Expect(tl, '(');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);

	if (!vcc_StringVal(tl)) {
		vcc_ExpectedStringval(tl);
		return (0);
	}

	Expect(tl, ',');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);

	Expect(tl, CSTR);
	if (tl->err)
		return (0);

	p = _vcc_regexp(tl, 1);

	vcc_NextToken(tl);
	Fb(tl, 0, ", %s, ", p);

	Expect(tl, ',');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);

	if (!vcc_StringVal(tl)) {
		vcc_ExpectedStringval(tl);
		return (0);
	}

	Expect(tl, ')');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);
	Fb(tl, 0, ")");

	return (1);
}

static int
vcc_digest(struct tokenlist *tl, const char *name_b)
{
	char p[30];
	int  l = tl->t->e - name_b;

	assert(l > 0);
	assert(l < 30);
	
	memcpy(p, name_b, l);
	p[l] = '\0';

	Fb(tl, 0, "VRT_digest_%s(sp, ", p);

	vcc_NextToken(tl);


	Expect(tl, '(');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);

	if (strstr(p, "hmac")) {
		if (!vcc_StringVal(tl)) {
			if (tl->err)
				return (0);
			vcc_ExpectedStringval(tl);
			return (0);
		}

		if (tl->t->tok != ',') {
			if (tl->err)
				return (0);
			vsb_printf(tl->sb,
			    "Expected comma\n");
			vcc_ErrWhere(tl, tl->t);
			return (0);
		}

		Fb(tl, 0, ", ");
		vcc_NextToken(tl);
	}

	/* from vcc_action.c:parse_set */
	if (!vcc_StringVal(tl)) {
		if (tl->err)
			return (0);
		vcc_ExpectedStringval(tl);
		return (0);
	}
	do
		Fb(tl, 0, ", ");
	while (vcc_StringVal(tl));

	if (tl->t->tok != ')') {
		if (tl->err)
			return (0);
		vsb_printf(tl->sb,
		    "Expected variable, string or closing parenthesis\n");
		vcc_ErrWhere(tl, tl->t);
		return (0);
	}
	Fb(tl, 0, "vrt_magic_string_end");

	vcc_NextToken(tl);
	Fb(tl, 0, ")");

	return (1);
}

static int
vcc_urlcode(struct tokenlist *tl)
{
	char p[30];
	int  l = tl->t->e - tl->t->b;

	assert(l > 0);
	assert(l < 30);
	
	memcpy(p, tl->t->b, l);
	p[l] = '\0';

	Fb(tl, 0, "VRT_%s(sp, ", p);

	vcc_NextToken(tl);

	Expect(tl, '(');
	if (tl->err)
		return (0);
	vcc_NextToken(tl);

	/* from vcc_action.c:parse_set */
	if (!vcc_StringVal(tl)) {
		if (tl->err)
			return (0);
		vcc_ExpectedStringval(tl);
		return (0);
	}
	do
		Fb(tl, 0, ", ");
	while (vcc_StringVal(tl));

	if (tl->t->tok != ')') {
		if (tl->err)
			return (0);
		vsb_printf(tl->sb,
		    "Expected variable, string or closing parenthesis\n");
		vcc_ErrWhere(tl, tl->t);
		return (0);
	}
	Fb(tl, 0, "vrt_magic_string_end");
	
	vcc_NextToken(tl);
	Fb(tl, 0, ")");

	return (1);
}

/*--------------------------------------------------------------------
 * Parse a string value and emit something that results in a usable
 * "const char *".
 * There are three possible outcomes:
 *	tl->err != 0 means something bad happened and a message is emitted.
 *	return (0) means "could not use this token"
 *	return (1) means "done"
 */

int
vcc_StringVal(struct tokenlist *tl)
{
	struct var *vp;

	if (tl->t->tok == CSTR) {
		EncToken(tl->fb, tl->t);
		vcc_NextToken(tl);
		return (1);
	} 
	if (tl->t->tok == ID) {
		const char *p;

		if (vcc_IdIs(tl->t, "regsub"))
			return (vcc_regsub(tl, 0));
		if (vcc_IdIs(tl->t, "regsuball"))
			return (vcc_regsub(tl, 1));
		if (vcc_IdIs(tl->t, "now")) {
			Fb(tl, 0, "VRT_time_string(sp, VRT_r_now(sp))");
			vcc_NextToken(tl);
			return 1;
		}
		if (vcc_IdIs(tl->t, "IP")) {
			/* 
			 * actually, no-one should need to get the string val of
			 * something which needs to get converted to an IP
			 * address first, but it's convenient for testing...
			 */
			Fb(tl, 0, "VRT_IP_string(sp, ");
			if (!vcc_IpVal(tl)) {
				if (tl->err)
					return (0);
				vcc_ExpectedIpval(tl);
				return(0);
			}
			Fb(tl, 0, ")");
			return(1);
		}
		if ((p = vcc_IdBegin(tl->t, "digest_"))) {
			return (vcc_digest(tl, p));
		}
		if (vcc_IdIs(tl->t, "urlencode") ||
		    vcc_IdIs(tl->t, "urldecode")) {
			return (vcc_urlcode(tl));
		}
		return (0);
	}
	if (tl->t->tok == VAR) {
		vp = vcc_FindVar(tl, tl->t, vcc_vars);
		if (tl->err)
			return (0);
		assert(vp != NULL);
		switch (vp->fmt) {
		case STRING:
			Fb(tl, 0, "%s", vp->rname);
			break;
		case IP:
			Fb(tl, 0, "VRT_IP_string(sp, %s)", vp->rname);
			break;
		case UINT32:
			Fb(tl, 0, "VRT_uint32_string(sp, %s)", vp->rname);
			break;
		case INT:
			Fb(tl, 0, "VRT_int_string(sp, %s)", vp->rname);
			break;
		case FLOAT:
			Fb(tl, 0, "VRT_double_string(sp, %s)", vp->rname);
			break;
		case DIGEST:
			Fb(tl, 0, "VRT_r_dir_digest(sp)");
			break;
		case TIME:
			Fb(tl, 0, "VRT_time_string(sp, %s)", vp->rname);
			break;
		case RTIME:
			Fb(tl, 0, "VRT_double_string(sp, %s)", vp->rname);
			break;
		case BACKEND:
			Fb(tl, 0, "VRT_backend_string(sp)");
			break;
		default:
			vsb_printf(tl->sb, "String representation of '%s'"
			    " not implemented yet.\n", vp->name);
			vcc_ErrWhere(tl, tl->t);
			return (0);
		}
		vcc_NextToken(tl);
		return (1);
	}
	return (0);
}

void
vcc_ExpectedStringval(struct tokenlist *tl)
{

	if (!tl->err) {
		vsb_printf(tl->sb, "Expected string variable or constant\n");
		vcc_ErrWhere(tl, tl->t);
	}
}
