*** /dev/null Sat Nov 23 01:35:47 2024
--- - Sat Nov 23 01:35:54 2024
***************
*** 0 ****
--- 1,1708 ----
+ /*
+ ** 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.h: VAR library API
+ */
+
+ #include <assert.h>
+ #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])
+ {
+ assert(a <= b);
+ assert(class != NULL);
+
+ do
+ {
+ class[(int)a] = 1;
+ }
+ while (++a <= b);
+ }
+
+ static var_rc_t expand_character_class(const char* desc, char class[256])
+ {
+ size_t i;
+
+ assert(desc != NULL);
+ assert(class != NULL);
+
+ /* 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 expand_named_characters(const char* src, size_t len, char* dst)
+ {
+ const char* end = src + len;
+ var_rc_t rc;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ 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 ((rc = expand_octal(&src, &dst, end)) != 0)
+ return rc;
+ break;
+ default:
+ *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] == '-')
+ {
+ printf("Expand class.\n");
+ 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
+ {
+ printf("Copy verbatim.\n");
+ 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;
+
+ printf("Transpose from '%s' to '%s'.\n",
+ srcclass.begin, dstclass.begin);
+
+ 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;
+
+ printf("Padding data '%s' to width '%d' by filling in '%s' to position '%c'.\n",
+ data->begin, width, fill->begin, position);
+
+ 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)
+ {
+ printf("Missing %d characters at the end of the data string.\n", i);
+ i = i / (fill->end - fill->begin);
+ printf("That's %d times the padding string.\n", i);
+ 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);
+ printf("Plus a remainder of %d characters.\n", i);
+ 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)
+ {
+ printf("Missing %d characters at the beginning of the data string.\n", i);
+ i = i / (fill->end - fill->begin);
+ printf("That's %d times the padding string.\n", i);
+ 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);
+ printf("Plus a remainder of %d characters.\n", i);
+ 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. */
+
+ printf("Missing %d characters at the beginning of the data string.\n", i);
+ i = i / (fill->end - fill->begin);
+ printf("That's %d times the padding string.\n", i);
+ 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);
+ printf("Plus a remainder of %d characters.\n", i);
+ 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);
+ printf("Missing %d characters at the end of the data string.\n", i);
+ i = i / (fill->end - fill->begin);
+ printf("That's %d times the padding string.\n", i);
+ 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);
+ printf("Plus a remainder of %d characters.\n", i);
+ 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;
+
+ /* Assert everything is as we expect it. */
+
+ assert(input_buf != NULL);
+ assert(result != NULL);
+ assert(result_len != NULL);
+ assert(lookup != NULL);
+
+ /* 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;
+ }
|