OSSP CVS Repository

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

ossp-pkg/var/var.c 1.6
/*
**  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 '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 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;

    printf("Search '%s' in '%s' and replace it with '%s'.\n",
           search->begin, data->begin, replace->begin);

    for (p = flags->begin; p != flags->end; ++p)
        {
        switch (tolower(*p))
            {
            case 'i':
                case_insensitive = 1;
                printf("case_insensitive = 1;\n");
                break;
            case 'g':
                global = 1;
                printf("global = 1;\n");
                break;
            case 't':
                no_regex = 1;
                printf("no_regex = 1;\n");
                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;
        regex_t preg;
        regmatch_t pmatch[33];
        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. */

        printf("data is.................: '%s'\n", mydata.begin);
        printf("regex search pattern is.: '%s'\n", tmp.begin);
        printf("regex replace pattern is: '%s'\n", replace->begin);
        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;
            }
        printf("Subexpression in pattern: '%d'\n", preg.re_nsub);

        /* 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)
                {
                printf("No match; appending remainder ('%s') to output string.\n", p);
                append_to_tokenbuf(&tmp, p, mydata.end - p);
                break;
                }
            else
                {
                if (!append_to_tokenbuf(&tmp, p, pmatch[0].rm_so) ||
                    !append_to_tokenbuf(&tmp, replace->begin, replace->end - replace->begin))
                    {
                    regfree(&preg);
                    free_tokenbuf(&tmp);
                    free_tokenbuf(&mydata);
                    return VAR_OUT_OF_MEMORY;
                    }
                else
                    p += pmatch[0].rm_eo;
                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