OSSP CVS Repository

ossp - ossp-pkg/var/var.c 1.8
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/var/var.c 1.8
/*
**  VAR - OSSP variable expression library.
**  Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
**  This file is part of OSSP VAR, an extensible data serialization
**  library which can be found at http://www.ossp.org/pkg/var/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  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.
**
**  var.c: VAR library implementation.
*/

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <regex.h>
#include "var.h"

/* The default configuration for the parser. */

const var_config_t var_config_default = {
    '$',			/* varinit */
    '{',			/* startdelim */
    '}',			/* enddelim */
    '\\',			/* escape */
    "a-zA-Z0-9_"		/* namechars */
};

/* Routines for manipulation of tokenbufs. */

#define VAR_INITIAL_BUFFER_SIZE 64

typedef struct {
    const char *begin;
    const char *end;
    size_t buffer_size;
} tokenbuf;

static void init_tokenbuf(tokenbuf * buf)
{
    buf->begin = buf->end = NULL;
    buf->buffer_size = 0;
}

static void move_tokenbuf(tokenbuf * src, tokenbuf * dst)
{
    dst->begin = src->begin;
    dst->end = src->end;
    dst->buffer_size = src->buffer_size;
    init_tokenbuf(src);
}

static int assign_to_tokenbuf(tokenbuf * buf, const char *data, size_t len)
{
    char *p = malloc(len + 1);
    if (p) {
	memcpy(p, data, len);
	buf->begin = p;
	buf->end = p + len;
	buf->buffer_size = len + 1;
	*((char *) (buf->end)) = '\0';
	return 1;
    } else
	return 0;
}

static int append_to_tokenbuf(tokenbuf * output, const char *data,
			      size_t len)
{
    char *new_buffer;
    size_t new_size;

    /* Is the tokenbuffer initialized at all? If not, allocate a
       standard-sized buffer to begin with. */

    if (output->begin == NULL) {
	if ((output->begin = output->end =
	     malloc(VAR_INITIAL_BUFFER_SIZE)) == NULL)
	    return 0;
	else
	    output->buffer_size = VAR_INITIAL_BUFFER_SIZE;
    }

    /* Does the token contain text, but no buffer has been allocated
       yet? */

    if (output->buffer_size == 0) {
	/* Check whether data borders to output. If, we can append
	   simly by increasing the end pointer. */

	if (output->end == data) {
	    output->end += len;
	    return 1;
	}

	/* OK, so copy the contents of output into an allocated buffer
	   so that we can append that way. */

	else {
	    char *tmp = malloc(output->end - output->begin + len + 1);
	    if (!tmp)
		return 0;
	    memcpy(tmp, output->begin, output->end - output->begin);
	    output->buffer_size = output->end - output->begin;
	    output->begin = tmp;
	    output->end = tmp + output->buffer_size;
	    output->buffer_size += len + 1;
	}
    }

    /* Does the token fit into the current buffer? If not, realloc a
       larger buffer that fits. */

    if ((output->buffer_size - (output->end - output->begin)) <= len) {
	new_size = output->buffer_size;
	do {
	    new_size *= 2;
	}
	while ((new_size - (output->end - output->begin)) <= len);
	new_buffer = realloc((char *) output->begin, new_size);
	if (new_buffer == NULL)
	    return 0;
	output->end = new_buffer + (output->end - output->begin);
	output->begin = new_buffer;
	output->buffer_size = new_size;
    }

    /* Append the data at the end of the current buffer. */

    memcpy((char *) output->end, data, len);
    output->end += len;
    *((char *) output->end) = '\0';
    return 1;
}

static void free_tokenbuf(tokenbuf * buf)
{
    if (buf->begin != NULL && buf->buffer_size > 0)
	free((char *) buf->begin);
    buf->begin = buf->end = NULL;
    buf->buffer_size = 0;
}

static size_t tokenbuf2int(tokenbuf * number)
{
    const char *p;
    size_t num = 0;
    for (p = number->begin; p != number->end; ++p) {
	num *= 10;
	num += *p - '0';
    }
    return num;
}

/* Routines for the expansion of quoted-pair expressions. */

static void expand_range(char a, char b, char class[256])
{
    do {
	class[(int) a] = 1;
    }
    while (++a <= b);
}

static var_rc_t expand_character_class(const char *desc, char class[256])
{
    size_t i;

    /* Clear the class array. */

    for (i = 0; i < 256; ++i)
	class[i] = 0;

    /* Walk through the class description and set the appropriate
       entries in the array. */

    while (*desc != '\0') {
	if (desc[1] == '-' && desc[2] != '\0') {
	    if (desc[0] > desc[2])
		return VAR_INCORRECT_CLASS_SPEC;
	    expand_range(desc[0], desc[2], class);
	    desc += 3;
	} else {
	    class[(int) *desc] = 1;
	    ++desc;
	}
    }

    return VAR_OK;
}

static int isoct(char c)
{
    if (c >= '0' && c <= '7')
	return 1;
    else
	return 0;
}

static var_rc_t expand_octal(const char **src, char **dst, const char *end)
{
    unsigned char c;

    if (end - *src < 3)
	return VAR_INCOMPLETE_OCTAL;
    if (!isoct(**src) || !isoct((*src)[1]) || !isoct((*src)[2]))
	return VAR_INVALID_OCTAL;

    c = **src - '0';
    if (c > 3)
	return VAR_OCTAL_TOO_LARGE;
    c *= 8;
    ++(*src);

    c += **src - '0';
    c *= 8;
    ++(*src);

    c += **src - '0';

    **dst = (char) c;
    ++(*dst);
    return VAR_OK;
}

static int ishex(char c)
{
    if ((c >= '0' && c <= '9') ||
	(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
	return 1;
    else
	return 0;
}

static var_rc_t expand_simple_hex(const char **src, char **dst,
				  const char *end)
{
    unsigned char c = 0;

    if (end - *src < 2)
	return VAR_INCOMPLETE_HEX;
    if (!ishex(**src) || !ishex((*src)[1]))
	return VAR_INVALID_HEX;

    if (**src >= '0' && **src <= '9')
	c = **src - '0';
    else if (c >= 'a' && c <= 'f')
	c = **src - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
	c = **src - 'A' + 10;

    c = c << 4;
    ++(*src);

    if (**src >= '0' && **src <= '9')
	c += **src - '0';
    else if (**src >= 'a' && **src <= 'f')
	c += **src - 'a' + 10;
    else if (**src >= 'A' && **src <= 'F')
	c += **src - 'A' + 10;

    **dst = (char) c;
    ++(*dst);
    return VAR_OK;
}

static var_rc_t expand_grouped_hex(const char **src, char **dst,
				   const char *end)
{
    var_rc_t rc;

    while (*src < end && **src != '}') {
	if ((rc = expand_simple_hex(src, dst, end)) != 0)
	    return rc;
	++(*src);
    }
    if (*src == end)
	return VAR_INCOMPLETE_GROUPED_HEX;

    return VAR_OK;
}

static var_rc_t expand_hex(const char **src, char **dst, const char *end)
{
    if (*src == end)
	return VAR_INCOMPLETE_HEX;
    if (**src == '{') {
	++(*src);
	return expand_grouped_hex(src, dst, end);
    } else
	return expand_simple_hex(src, dst, end);
}

var_rc_t var_unescape(const char *src, size_t len, char *dst,
		      int unescape_all)
{
    const char *end = src + len;
    var_rc_t rc;

    while (src < end) {
	if (*src == '\\') {
	    if (++src == end)
		return VAR_INCOMPLETE_NAMED_CHARACTER;
	    switch (*src) {
	    case '\\':
		if (!unescape_all) {
		    *dst++ = '\\';
		}
		*dst++ = '\\';
		break;
	    case 'n':
		*dst++ = '\n';
		break;
	    case 't':
		*dst++ = '\t';
		break;
	    case 'r':
		*dst++ = '\r';
		break;
	    case 'x':
		++src;
		if ((rc = expand_hex(&src, &dst, end)) != 0)
		    return rc;
		break;
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		if (end - src >= 3 && isdigit(src[1]) && isdigit(src[2])) {
		    if ((rc = expand_octal(&src, &dst, end)) != 0)
			return rc;
		    break;
		}
	    default:
		if (!unescape_all) {
		    *dst++ = '\\';
		}
		*dst++ = *src;
	    }
	    ++src;
	} else
	    *dst++ = *src++;
    }
    *dst = '\0';
    return VAR_OK;
}

/* The recursive-descent parser for variable expressions. */

static int variable(const char *, const char *, const var_config_t *,
		    const char[256], var_cb_t, void *, int, tokenbuf *);
static int command(const char *, const char *, const var_config_t *,
		   const char[256], var_cb_t, void *, int, tokenbuf *);

static int text(const char *begin, const char *end, char varinit,
		char escape)
{
    const char *p;
    for (p = begin; p != end && *p != varinit; ++p) {
	if (*p == escape) {
	    if (p + 1 == end)
		return VAR_INCOMPLETE_QUOTED_PAIR;
	    else
		++p;
	}
    }
    return p - begin;
}

static int varname(const char *begin, const char *end,
		   const char nameclass[256])
{
    const char *p;
    for (p = begin; p != end && nameclass[(int) *p]; ++p);
    return p - begin;
}

static int number(const char *begin, const char *end)
{
    const char *p;
    for (p = begin; p != end && isdigit(*p); ++p);
    return p - begin;
}

static int substext(const char *begin, const char *end,
		    const var_config_t * config)
{
    const char *p;
    for (p = begin; p != end && *p != config->varinit && *p != '/'; ++p) {
	if (*p == config->escape) {
	    if (p + 1 == end)
		return VAR_INCOMPLETE_QUOTED_PAIR;
	    else
		++p;
	}
    }
    return p - begin;
}

static int exptext(const char *begin, const char *end,
		   const var_config_t * config)
{
    const char *p;
    for (p = begin;
	 p != end && *p != config->varinit && *p != config->enddelim
	 && *p != ':'; ++p) {
	if (*p == config->escape) {
	    if (p + 1 == end)
		return VAR_INCOMPLETE_QUOTED_PAIR;
	    else
		++p;
	}
    }
    return p - begin;
}


static int expression(const char *begin, const char *end,
		      const var_config_t * config,
		      const char nameclass[256], var_cb_t lookup,
		      void *lookup_context, int force_expand,
		      tokenbuf * result)
{
    const char *p = begin;
    const char *data;
    size_t len, buffer_size;
    int failed = 0;
    int rc;
    tokenbuf name;
    tokenbuf tmp;

    /* Clear the tokenbufs to make sure we have a defined state. */

    init_tokenbuf(&name);
    init_tokenbuf(&tmp);
    init_tokenbuf(result);

    /* Expect STARTDELIM. */

    if (p == end || *p != config->startdelim)
	return 0;

    if (++p == end)
	return VAR_INCOMPLETE_VARIABLE_SPEC;

    /* Get the name of the variable to expand. The name may consist of
       an arbitrary number of VARNAMEs and VARIABLEs. */

    do {
	rc = varname(p, end, nameclass);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    if (!append_to_tokenbuf(&name, p, rc)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    } else
		p += rc;
	}

	rc = variable(p, end, config, nameclass, lookup, lookup_context,
		      force_expand, &tmp);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    if (!append_to_tokenbuf(&name, tmp.begin, tmp.end - tmp.begin)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    } else
		p += rc;
	}
    }
    while (rc > 0);

    /* We must have the complete variable name now, so make sure we
       do. */

    if (name.begin == name.end) {
	rc = VAR_INCOMPLETE_VARIABLE_SPEC;
	goto error_return;
    }

    /* Now we have the name of the variable stored in "name". We
       expect an ENDDELIM here. */

    if (p == end || (*p != config->enddelim && *p != ':')) {
	rc = VAR_INCOMPLETE_VARIABLE_SPEC;
	goto error_return;
    } else
	++p;

    /* Use the lookup callback to get the variable's contents. */

    rc = (*lookup) (lookup_context, name.begin, name.end - name.begin,
		    &data, &len, &buffer_size);
    if (rc < 0)
	goto error_return;
    else if (rc == 0) {
	/* The variable is undefined. What we'll do now depends on the
	   force_expand flag. */

	if (force_expand) {
	    rc = VAR_UNDEFINED_VARIABLE;
	    goto error_return;
	} else {
	    /* Initialize result to point back to the original text in
	       the buffer. */

	    result->begin = begin - 1;
	    result->end = p;
	    result->buffer_size = 0;
	    failed = 1;
	}
    } else {
	/* The preliminary result is the contents of the variable.
	   This may be modified by the commands that may follow. */

	result->begin = data;
	result->end = data + len;
	result->buffer_size = buffer_size;
    }

    if (p[-1] == ':') {
	/* Parse and execute commands. */

	free_tokenbuf(&tmp);
	--p;
	while (p != end && *p == ':') {
	    ++p;
	    if (!failed)
		rc = command(p, end, config, nameclass, lookup,
			     lookup_context, force_expand, result);
	    else
		rc = command(p, end, config, nameclass, lookup,
			     lookup_context, force_expand, &tmp);
	    if (rc < 0)
		goto error_return;
	    p += rc;
	    if (failed)
		result->end += rc;
	}

	if (p == end || *p != config->enddelim) {
	    rc = VAR_INCOMPLETE_VARIABLE_SPEC;
	    goto error_return;
	}
	++p;
	if (failed)
	    ++result->end;
    }

    /* Exit gracefully. */

    free_tokenbuf(&name);
    free_tokenbuf(&tmp);
    return p - begin;

    /* Exit in case of an error. */

  error_return:
    free_tokenbuf(&name);
    free_tokenbuf(&tmp);
    free_tokenbuf(result);
    return rc;
}

static int variable(const char *begin, const char *end,
		    const var_config_t * config, const char nameclass[256],
		    var_cb_t lookup, void *lookup_context,
		    int force_expand, tokenbuf * result)
{
    const char *p = begin;
    const char *data;
    size_t len, buffer_size;
    int rc, rc2;

    /* Clear the result tokenbuf to make sure we're in a defined
       state. */

    init_tokenbuf(result);

    /* Expect VARINIT. */

    if (p == end || *p != config->varinit)
	return 0;

    if (++p == end)
	return VAR_INCOMPLETE_VARIABLE_SPEC;

    /* Try to read the variable name. If that fails, we're parsing a
       complex expression. */

    rc = varname(p, end, nameclass);
    if (rc < 0)
	return rc;
    else if (rc > 0) {
	rc2 = (*lookup) (lookup_context, p, rc, &data, &len, &buffer_size);
	if (rc2 < 0)
	    return rc2;
	else if (rc2 == 0) {
	    if (force_expand)
		return VAR_UNDEFINED_VARIABLE;
	    else {
		result->begin = begin;
		result->end = begin + 1 + rc;
		result->buffer_size = 0;
		return 1 + rc;
	    }
	} else {
	    result->begin = data;
	    result->end = data + len;
	    result->buffer_size = buffer_size;
	    return 1 + rc;
	}
    }

    /* OK, we're dealing with a complex expression here. */

    rc = expression(p, end, config, nameclass, lookup, lookup_context,
		    force_expand, result);
    if (rc > 0)
	++rc;
    return rc;
}

static int exptext_or_variable(const char *begin, const char *end,
			       const var_config_t * config,
			       const char nameclass[256], var_cb_t lookup,
			       void *lookup_context, int force_expand,
			       tokenbuf * result)
{
    const char *p = begin;
    tokenbuf tmp;
    int rc;

    init_tokenbuf(result);
    init_tokenbuf(&tmp);

    if (begin == end)
	return 0;

    do {
	rc = exptext(p, end, config);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    if (!append_to_tokenbuf(result, p, rc)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    } else
		p += rc;
	}

	rc = variable(p, end, config, nameclass, lookup, lookup_context,
		      force_expand, &tmp);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    p += rc;
	    if (!append_to_tokenbuf
		(result, tmp.begin, tmp.end - tmp.begin)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    }
	}
    }
    while (rc > 0);

    free_tokenbuf(&tmp);
    return p - begin;

  error_return:
    free_tokenbuf(&tmp);
    free_tokenbuf(result);
    return rc;
}

static int substext_or_variable(const char *begin, const char *end,
				const var_config_t * config,
				const char nameclass[256], var_cb_t lookup,
				void *lookup_context, int force_expand,
				tokenbuf * result)
{
    const char *p = begin;
    tokenbuf tmp;
    int rc;

    init_tokenbuf(result);
    init_tokenbuf(&tmp);

    if (begin == end)
	return 0;

    do {
	rc = substext(p, end, config);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    if (!append_to_tokenbuf(result, p, rc)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    } else
		p += rc;
	}

	rc = variable(p, end, config, nameclass, lookup, lookup_context,
		      force_expand, &tmp);
	if (rc < 0)
	    goto error_return;
	else if (rc > 0) {
	    p += rc;
	    if (!append_to_tokenbuf
		(result, tmp.begin, tmp.end - tmp.begin)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    }
	}
    }
    while (rc > 0);

    free_tokenbuf(&tmp);
    return p - begin;

  error_return:
    free_tokenbuf(&tmp);
    free_tokenbuf(result);
    return rc;
}


static int expand_class_description(tokenbuf * src, tokenbuf * dst)
{
    unsigned char c, d;
    const char *p = src->begin;
    while (p != src->end) {
	if ((src->end - p) >= 3 && p[1] == '-') {
	    if (*p > p[2])
		return VAR_INCORRECT_TRANSPOSE_CLASS_SPEC;
	    for (c = *p, d = p[2]; c <= d; ++c) {
		if (!append_to_tokenbuf(dst, (char *) &c, 1))
		    return VAR_OUT_OF_MEMORY;
	    }
	    p += 3;
	} else {
	    if (!append_to_tokenbuf(dst, p, 1))
		return VAR_OUT_OF_MEMORY;
	    else
		++p;
	}
    }
    return VAR_OK;
}

static int transpose(tokenbuf * data, tokenbuf * search,
		     tokenbuf * replace)
{
    tokenbuf srcclass, dstclass;
    const char *p;
    int rc;
    size_t i;

    init_tokenbuf(&srcclass);
    init_tokenbuf(&dstclass);

    if ((rc = expand_class_description(search, &srcclass)) != VAR_OK)
	goto error_return;
    if ((rc = expand_class_description(replace, &dstclass)) != VAR_OK)
	goto error_return;

    if (srcclass.begin == srcclass.end) {
	rc = VAR_EMPTY_TRANSPOSE_CLASS;
	goto error_return;
    }
    if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
	rc = VAR_TRANSPOSE_CLASSES_MISMATCH;
	goto error_return;
    }

    if (data->buffer_size == 0) {
	tokenbuf tmp;
	if (!assign_to_tokenbuf
	    (&tmp, data->begin, data->end - data->begin)) {
	    rc = VAR_OUT_OF_MEMORY;
	    goto error_return;
	}
	move_tokenbuf(&tmp, data);
    }

    for (p = data->begin; p != data->end; ++p) {
	for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) {
	    if (*p == srcclass.begin[i]) {
		*((char *) p) = dstclass.begin[i];
		break;
	    }
	}
    }

    free_tokenbuf(&srcclass);
    free_tokenbuf(&dstclass);
    return VAR_OK;

  error_return:
    free_tokenbuf(search);
    free_tokenbuf(replace);
    free_tokenbuf(&srcclass);
    free_tokenbuf(&dstclass);
    return rc;
}

static int cut_out_offset(tokenbuf * data, tokenbuf * number1,
			  tokenbuf * number2, int isrange)
{
    tokenbuf res;
    const char *p;
    size_t num1 = tokenbuf2int(number1);
    size_t num2 = tokenbuf2int(number2);

    /* Determine begin of result string. */

    if ((data->end - data->begin) < num1)
	return VAR_OFFSET_OUT_OF_BOUNDS;
    else
	p = data->begin + num1;

    /* If num2 is zero, we copy the rest from there. */

    if (num2 == 0) {
	if (!assign_to_tokenbuf(&res, p, data->end - p))
	    return VAR_OUT_OF_MEMORY;
    } else {			/* OK, then use num2. */

	if (isrange) {
	    if ((p + num2) > data->end)
		return VAR_RANGE_OUT_OF_BOUNDS;
	    if (!assign_to_tokenbuf(&res, p, num2))
		return VAR_OUT_OF_MEMORY;
	} else {
	    if (num2 < num1)
		return VAR_OFFSET_LOGIC_ERROR;
	    if ((data->begin + num2) > data->end)
		return VAR_RANGE_OUT_OF_BOUNDS;
	    if (!assign_to_tokenbuf(&res, p, (data->begin + num2) - p))
		return VAR_OUT_OF_MEMORY;
	}
    }
    free_tokenbuf(data);
    move_tokenbuf(&res, data);
    return VAR_OK;
}

static int expand_regex_replace(const char *data, tokenbuf * orig,
				regmatch_t * pmatch, tokenbuf * expanded)
{
    const char *p = orig->begin;
    size_t i;

    init_tokenbuf(expanded);

    while (p != orig->end) {
	if (*p == '\\') {
	    if (orig->end - p <= 1) {
		free_tokenbuf(expanded);
		return VAR_INCOMPLETE_QUOTED_PAIR;
	    } else
		++p;
	    if (*p == '\\') {
		if (!append_to_tokenbuf(expanded, p, 1)) {
		    free_tokenbuf(expanded);
		    return VAR_OUT_OF_MEMORY;
		}
		++p;
		continue;
	    }
	    if (!isdigit(*p)) {
		free_tokenbuf(expanded);
		return VAR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
	    }
	    i = *p - '0';
	    ++p;
	    if (pmatch[i].rm_so == -1) {
		free_tokenbuf(expanded);
		return VAR_SUBMATCH_OUT_OF_RANGE;
	    }
	    if (!append_to_tokenbuf(expanded, data + pmatch[i].rm_so,
				    pmatch[i].rm_eo - pmatch[i].rm_so)) {
		free_tokenbuf(expanded);
		return VAR_OUT_OF_MEMORY;
	    }
	} else {
	    if (!append_to_tokenbuf(expanded, p, 1)) {
		free_tokenbuf(expanded);
		return VAR_OUT_OF_MEMORY;
	    }
	    ++p;
	}
    }

    return VAR_OK;
}

static int search_and_replace(tokenbuf * data, tokenbuf * search,
			      tokenbuf * replace, tokenbuf * flags)
{
    const char *p;
    int case_insensitive = 0;
    int global = 0;
    int no_regex = 0;
    int rc;

    if (search->begin == search->end)
	return VAR_EMPTY_SEARCH_STRING;

    for (p = flags->begin; p != flags->end; ++p) {
	switch (tolower(*p)) {
	case 'i':
	    case_insensitive = 1;
	    break;
	case 'g':
	    global = 1;
	    break;
	case 't':
	    no_regex = 1;
	    break;
	default:
	    return VAR_UNKNOWN_REPLACE_FLAG;
	}
    }

    if (no_regex) {
	tokenbuf tmp;
	init_tokenbuf(&tmp);

	for (p = data->begin; p != data->end;) {
	    if (case_insensitive)
		rc = strncasecmp(p, search->begin,
				 search->end - search->begin);
	    else
		rc = strncmp(p, search->begin,
			     search->end - search->begin);
	    if (rc != 0) {	/* no match, copy character */
		if (!append_to_tokenbuf(&tmp, p, 1)) {
		    free_tokenbuf(&tmp);
		    return VAR_OUT_OF_MEMORY;
		}
		++p;
	    } else {
		append_to_tokenbuf(&tmp, replace->begin,
				   replace->end - replace->begin);
		p += search->end - search->begin;
		if (!global) {
		    if (!append_to_tokenbuf(&tmp, p, data->end - p)) {
			free_tokenbuf(&tmp);
			return VAR_OUT_OF_MEMORY;
		    }
		    break;
		}
	    }
	}

	free_tokenbuf(data);
	move_tokenbuf(&tmp, data);
    } else {
	tokenbuf tmp;
	tokenbuf mydata;
	tokenbuf myreplace;
	regex_t preg;
	regmatch_t pmatch[10];
	int regexec_flag;

	/* Copy the pattern and the data to our own buffer to make
	   sure they're terminated with a null byte. */

	if (!assign_to_tokenbuf
	    (&tmp, search->begin, search->end - search->begin))
	    return VAR_OUT_OF_MEMORY;
	if (!assign_to_tokenbuf
	    (&mydata, data->begin, data->end - data->begin)) {
	    free_tokenbuf(&tmp);
	    return VAR_OUT_OF_MEMORY;
	}

	/* Compile the pattern. */

	rc = regcomp(&preg, tmp.begin,
		     REG_EXTENDED | ((case_insensitive) ? REG_ICASE : 0));
	free_tokenbuf(&tmp);
	if (rc != 0) {
	    free_tokenbuf(&mydata);
	    return VAR_INVALID_REGEX_IN_REPLACE;
	}

	/* Match the pattern and create the result string in the tmp
	   buffer. */

	for (p = mydata.begin; p != mydata.end;) {
	    if (p == mydata.begin || p[-1] == '\n')
		regexec_flag = 0;
	    else
		regexec_flag = REG_NOTBOL;
	    if (regexec
		(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch,
		 regexec_flag) == REG_NOMATCH) {
		append_to_tokenbuf(&tmp, p, mydata.end - p);
		break;
	    } else {
		rc = expand_regex_replace(p, replace, pmatch, &myreplace);
		if (rc != VAR_OK) {
		    regfree(&preg);
		    free_tokenbuf(&tmp);
		    free_tokenbuf(&mydata);
		    return rc;
		}
		if (!append_to_tokenbuf(&tmp, p, pmatch[0].rm_so) ||
		    !append_to_tokenbuf(&tmp, myreplace.begin,
					myreplace.end - myreplace.begin)) {
		    regfree(&preg);
		    free_tokenbuf(&tmp);
		    free_tokenbuf(&mydata);
		    free_tokenbuf(&myreplace);
		    return VAR_OUT_OF_MEMORY;
		} else {
		    p += (pmatch[0].rm_eo > 0) ? pmatch[0].rm_eo : 1;
		    free_tokenbuf(&myreplace);
		}
		if (!global) {
		    append_to_tokenbuf(&tmp, p, mydata.end - p);
		    break;
		}
	    }
	}

	regfree(&preg);
	free_tokenbuf(data);
	move_tokenbuf(&tmp, data);
	free_tokenbuf(&mydata);
    }

    return VAR_OK;
}

static int padding(tokenbuf * data, tokenbuf * widthstr, tokenbuf * fill,
		   char position)
{
    tokenbuf result;
    size_t width = tokenbuf2int(widthstr);
    int i;

    if (fill->begin == fill->end)
	return VAR_EMPTY_PADDING_FILL_STRING;

    init_tokenbuf(&result);

    if (position == 'l') {
	i = width - (data->end - data->begin);
	if (i > 0) {
	    i = i / (fill->end - fill->begin);
	    while (i > 0) {
		if (!append_to_tokenbuf
		    (data, fill->begin, fill->end - fill->begin))
		    return VAR_OUT_OF_MEMORY;
		--i;
	    }
	    i = (width - (data->end - data->begin)) % (fill->end -
						       fill->begin);
	    if (!append_to_tokenbuf(data, fill->begin, i))
		return VAR_OUT_OF_MEMORY;
	}
    } else if (position == 'r') {
	i = width - (data->end - data->begin);
	if (i > 0) {
	    i = i / (fill->end - fill->begin);
	    while (i > 0) {
		if (!append_to_tokenbuf
		    (&result, fill->begin, fill->end - fill->begin)) {
		    free_tokenbuf(&result);
		    return VAR_OUT_OF_MEMORY;
		}
		--i;
	    }
	    i = (width - (data->end - data->begin)) % (fill->end -
						       fill->begin);
	    if (!append_to_tokenbuf(&result, fill->begin, i)) {
		free_tokenbuf(&result);
		return VAR_OUT_OF_MEMORY;
	    }
	    if (!append_to_tokenbuf
		(&result, data->begin, data->end - data->begin)) {
		free_tokenbuf(&result);
		return VAR_OUT_OF_MEMORY;
	    }

	    free_tokenbuf(data);
	    move_tokenbuf(&result, data);
	}
    } else if (position == 'c') {
	i = (width - (data->end - data->begin)) / 2;
	if (i > 0) {
	    /* Create the prefix. */

	    i = i / (fill->end - fill->begin);
	    while (i > 0) {
		if (!append_to_tokenbuf
		    (&result, fill->begin, fill->end - fill->begin)) {
		    free_tokenbuf(&result);
		    return VAR_OUT_OF_MEMORY;
		}
		--i;
	    }
	    i = ((width - (data->end - data->begin)) / 2) % (fill->end -
							     fill->begin);
	    if (!append_to_tokenbuf(&result, fill->begin, i)) {
		free_tokenbuf(&result);
		return VAR_OUT_OF_MEMORY;
	    }

	    /* Append the actual data string. */

	    if (!append_to_tokenbuf
		(&result, data->begin, data->end - data->begin)) {
		free_tokenbuf(&result);
		return VAR_OUT_OF_MEMORY;
	    }

	    /* Append the suffix. */

	    i = width - (result.end - result.begin);
	    i = i / (fill->end - fill->begin);
	    while (i > 0) {
		if (!append_to_tokenbuf
		    (&result, fill->begin, fill->end - fill->begin)) {
		    free_tokenbuf(&result);
		    return VAR_OUT_OF_MEMORY;
		}
		--i;
	    }
	    i = width - (result.end - result.begin);
	    if (!append_to_tokenbuf(&result, fill->begin, i)) {
		free_tokenbuf(&result);
		return VAR_OUT_OF_MEMORY;
	    }

	    /* Move string from temporary buffer to data buffer. */

	    free_tokenbuf(data);
	    move_tokenbuf(&result, data);
	}
    }

    return VAR_OK;
}

static int command(const char *begin, const char *end,
		   const var_config_t * config, const char nameclass[256],
		   var_cb_t lookup, void *lookup_context, int force_expand,
		   tokenbuf * data)
{
    const char *p = begin;
    tokenbuf tmptokbuf;
    tokenbuf search, replace, flags;
    tokenbuf number1, number2;
    int isrange;
    int rc;

    init_tokenbuf(&tmptokbuf);
    init_tokenbuf(&search);
    init_tokenbuf(&replace);
    init_tokenbuf(&flags);
    init_tokenbuf(&number1);
    init_tokenbuf(&number2);

    if (begin == end)
	return 0;

    switch (tolower(*p)) {
    case 'l':			/* Turn data to lowercase. */
	if (data->begin) {
	    char *ptr;
	    /* If the buffer does not life in an allocated buffer,
	       we have to copy it before modifying the contents. */

	    if (data->buffer_size == 0) {
		if (!assign_to_tokenbuf
		    (data, data->begin, data->end - data->begin)) {
		    rc = VAR_OUT_OF_MEMORY;
		    goto error_return;
		}
	    }
	    for (ptr = (char *) data->begin; ptr != data->end; ++ptr)
		*ptr = tolower(*ptr);
	}
	++p;
	break;

    case 'u':			/* Turn data to uppercase. */
	if (data->begin) {
	    char *ptr;
	    if (data->buffer_size == 0) {
		if (!assign_to_tokenbuf
		    (data, data->begin, data->end - data->begin)) {
		    rc = VAR_OUT_OF_MEMORY;
		    goto error_return;
		}
	    }
	    for (ptr = (char *) data->begin; ptr != data->end; ++ptr)
		*ptr = toupper(*ptr);
	}
	++p;
	break;

    case 'o':			/* Cut out substrings. */
	++p;
	rc = number(p, end);
	if (rc == 0) {
	    rc = VAR_MISSING_START_OFFSET;
	    goto error_return;
	} else {
	    number1.begin = p;
	    number1.end = p + rc;
	    number1.buffer_size = 0;
	    p += rc;
	}

	if (*p == ',') {
	    isrange = 0;
	    ++p;
	} else if (*p == '-') {
	    isrange = 1;
	    ++p;
	} else {
	    rc = VAR_INVALID_OFFSET_DELIMITER;
	    goto error_return;
	}

	rc = number(p, end);
	number2.begin = p;
	number2.end = p + rc;
	number2.buffer_size = 0;
	p += rc;
	if (data->begin) {
	    rc = cut_out_offset(data, &number1, &number2, isrange);
	    if (rc < 0)
		goto error_return;
	}
	break;

    case '#':			/* Substitute length of the string. */
	if (data->begin) {
	    char buf[1024];
	    sprintf(buf, "%d", data->end - data->begin);
	    free_tokenbuf(data);
	    if (!assign_to_tokenbuf(data, buf, strlen(buf))) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    }
	}
	++p;
	break;

    case '-':			/* Substitute parameter if data is empty. */
	++p;
	rc = exptext_or_variable(p, end, config, nameclass, lookup,
				 lookup_context, force_expand, &tmptokbuf);
	if (rc < 0)
	    goto error_return;
	else if (rc == 0) {
	    rc = VAR_MISSING_PARAMETER_IN_COMMAND;
	    goto error_return;
	} else
	    p += rc;
	if (data->begin != NULL && data->begin == data->end) {
	    free_tokenbuf(data);
	    move_tokenbuf(&tmptokbuf, data);
	}
	break;

    case '*':			/* Return "" if data is not empty, parameter otherwise. */
	++p;
	rc = exptext_or_variable(p, end, config, nameclass, lookup,
				 lookup_context, force_expand, &tmptokbuf);
	if (rc < 0)
	    goto error_return;
	else if (rc == 0) {
	    rc = VAR_MISSING_PARAMETER_IN_COMMAND;
	    goto error_return;
	} else
	    p += rc;
	if (data->begin != NULL) {
	    if (data->begin == data->end) {
		free_tokenbuf(data);
		move_tokenbuf(&tmptokbuf, data);
	    } else {
		free_tokenbuf(data);
		data->begin = data->end = "";
		data->buffer_size = 0;
	    }
	}
	break;

    case '+':			/* Substitute parameter if data is not empty. */
	++p;
	rc = exptext_or_variable(p, end, config, nameclass, lookup,
				 lookup_context, force_expand, &tmptokbuf);
	if (rc < 0)
	    goto error_return;
	else if (rc == 0) {
	    rc = VAR_MISSING_PARAMETER_IN_COMMAND;
	    goto error_return;
	} else
	    p += rc;
	if (data->begin != NULL) {
	    if (data->begin != data->end) {
		free_tokenbuf(data);
		move_tokenbuf(&tmptokbuf, data);
	    }
	}
	break;

    case 's':			/* Search and replace. */
	++p;

	if (*p != '/')
	    return VAR_MALFORMATTED_REPLACE;
	else
	    ++p;

	rc = substext_or_variable(p, end, config, nameclass, lookup,
				  lookup_context, force_expand, &search);
	if (rc < 0)
	    goto error_return;
	else
	    p += rc;

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_REPLACE;
	    goto error_return;
	} else
	    ++p;

	rc = substext_or_variable(p, end, config, nameclass, lookup,
				  lookup_context, force_expand, &replace);
	if (rc < 0)
	    goto error_return;
	else
	    p += rc;

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_REPLACE;
	    goto error_return;
	} else
	    ++p;

	rc = exptext(p, end, config);
	if (rc < 0)
	    goto error_return;
	else {
	    flags.begin = p;
	    flags.end = p + rc;
	    flags.buffer_size = 0;
	    p += rc;
	}

	if (data->begin) {
	    rc = search_and_replace(data, &search, &replace, &flags);
	    if (rc < 0)
		goto error_return;
	}
	break;

    case 'y':			/* Transpose characters from class A to class B. */
	++p;

	if (*p != '/')
	    return VAR_MALFORMATTED_TRANSPOSE;
	else
	    ++p;

	rc = substext_or_variable(p, end, config, nameclass, lookup,
				  lookup_context, force_expand, &search);
	if (rc < 0)
	    goto error_return;
	else
	    p += rc;

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_TRANSPOSE;
	    goto error_return;
	} else
	    ++p;

	rc = substext_or_variable(p, end, config, nameclass, lookup,
				  lookup_context, force_expand, &replace);
	if (rc < 0)
	    goto error_return;
	else
	    p += rc;

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_TRANSPOSE;
	    goto error_return;
	} else
	    ++p;

	if (data->begin) {
	    rc = transpose(data, &search, &replace);
	    if (rc < 0)
		goto error_return;
	}
	break;


    case 'p':			/* Padding. */
	++p;

	if (*p != '/')
	    return VAR_MALFORMATTED_PADDING;
	else
	    ++p;

	rc = number(p, end);
	if (rc == 0) {
	    rc = VAR_MISSING_PADDING_WIDTH;
	    goto error_return;
	} else {
	    number1.begin = p;
	    number1.end = p + rc;
	    number1.buffer_size = 0;
	    p += rc;
	}

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_PADDING;
	    goto error_return;
	} else
	    ++p;

	rc = substext_or_variable(p, end, config, nameclass, lookup,
				  lookup_context, force_expand, &replace);
	if (rc < 0)
	    goto error_return;
	else
	    p += rc;

	if (*p != '/') {
	    rc = VAR_MALFORMATTED_PADDING;
	    goto error_return;
	} else
	    ++p;

	if (*p != 'l' && *p != 'c' && *p != 'r') {
	    rc = VAR_MALFORMATTED_PADDING;
	    goto error_return;
	} else
	    ++p;

	if (data->begin) {
	    rc = padding(data, &number1, &replace, p[-1]);
	    if (rc < 0)
		goto error_return;
	}
	break;

    default:
	return VAR_UNKNOWN_COMMAND_CHAR;
    }

    /* Exit gracefully. */

    free_tokenbuf(&tmptokbuf);
    free_tokenbuf(&search);
    free_tokenbuf(&replace);
    free_tokenbuf(&flags);
    free_tokenbuf(&number1);
    free_tokenbuf(&number2);
    return p - begin;

  error_return:
    free_tokenbuf(data);
    free_tokenbuf(&tmptokbuf);
    free_tokenbuf(&search);
    free_tokenbuf(&replace);
    free_tokenbuf(&flags);
    free_tokenbuf(&number1);
    free_tokenbuf(&number2);
    return rc;
}

static var_rc_t input(const char *begin, const char *end,
		      const var_config_t * config,
		      const char nameclass[256], var_cb_t lookup,
		      void *lookup_context, int force_expand,
		      tokenbuf * output)
{
    int rc;
    tokenbuf result;

    init_tokenbuf(&result);

    do {
	rc = text(begin, end, config->varinit, config->escape);
	if (rc > 0) {
	    if (!append_to_tokenbuf(output, begin, rc)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    }
	    begin += rc;
	} else if (rc < 0)
	    goto error_return;

	rc = variable(begin, end, config, nameclass, lookup,
		      lookup_context, force_expand, &result);
	if (rc > 0) {
	    if (!append_to_tokenbuf
		(output, result.begin, result.end - result.begin)) {
		rc = VAR_OUT_OF_MEMORY;
		goto error_return;
	    } else
		begin += rc;
	} else if (rc < 0)
	    goto error_return;
    }
    while (rc > 0);

    if (begin != end) {
	rc = VAR_INPUT_ISNT_TEXT_NOR_VARIABLE;
	goto error_return;
    }

    return VAR_OK;

  error_return:
    free_tokenbuf(&result);
    return rc;
}

var_rc_t var_expand(const char *input_buf, size_t input_len,
		    char **result, size_t * result_len,
		    var_cb_t lookup, void *lookup_context,
		    const var_config_t * config, int force_expand)
{
    char nameclass[256];
    var_rc_t rc;
    tokenbuf output;

    /* Expand the class description for valid variable names. */

    if (config == NULL)
	config = &var_config_default;
    rc = expand_character_class(config->namechars, nameclass);
    if (rc != VAR_OK)
	return rc;

    /* Make sure that the specials defined in the configuration do not
       appear in the character name class. */

    if (nameclass[(int) config->varinit] ||
	nameclass[(int) config->startdelim] ||
	nameclass[(int) config->enddelim] ||
	nameclass[(int) config->escape])
	return VAR_INVALID_CONFIGURATION;

    /* Call the parser. */

    output.begin = output.end = NULL;
    output.buffer_size = 0;
    rc = input(input_buf, input_buf + input_len, config, nameclass,
	       lookup, lookup_context, force_expand, &output);
    if (rc != VAR_OK) {
	free_tokenbuf(&output);
	return rc;
    }
    *result = (char *) output.begin;
    *result_len = output.end - output.begin;

    return VAR_OK;
}

CVSTrac 2.0.1