ossp-pkg/var/var.c
1.70
/*
** OSSP var - Variable Expansion
** Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
** Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
** This file is part of OSSP var, a variable expansion
** library which can be found at http://www.ossp.org/pkg/lib/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: library implementation
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#if defined(HAVE_PCREPOSIX)
# include <pcreposix.h>
#else
# include <regex.h>
#endif
#include "var.h"
/* unique library identifier */
const char var_id[] = "OSSP var";
/* support for OSSP ex based exception throwing */
#ifdef WITH_EX
#include "ex.h"
#define VAR_RC(rv) \
( (rv) != VAR_OK && (ex_catching && !ex_shielding) \
? (ex_throw(var_id, NULL, (rv)), (rv)) : (rv) )
#else
#define VAR_RC(rv) (rv)
#endif /* WITH_EX */
#ifndef NUL
#define NUL '\0'
#endif
/*
**
** ==== INTERNAL DATA STRUCTURES ====
**
*/
typedef char char_class_t[256]; /* 256 == 2 ^ sizeof(unsigned char)*8 */
/* the external context structure */
struct var_st {
var_syntax_t syntax;
char_class_t syntax_nameclass;
var_cb_value_t cb_value_fct;
void *cb_value_ctx;
};
/* the internal expansion context structure */
typedef struct {
int force_expand;
} var_parse_t;
/* the default syntax configuration */
static const var_syntax_t var_syntax_default = {
'\\', /* escape */
'$', /* delim_init */
'{', /* delim_open */
'}', /* delim_close */
'[', /* index_open */
']', /* index_close */
'#', /* index_mark */
"a-zA-Z0-9_" /* name_chars */
};
/*
**
** ==== TOKEN BUFFER FUNCTIONS ====
**
*/
#define TOKENBUF_INITIAL_BUFSIZE 64
typedef struct {
const char *begin;
const char *end;
size_t buffer_size;
} tokenbuf_t;
static void tokenbuf_init(tokenbuf_t *buf)
{
buf->begin = NULL;
buf->end = NULL;
buf->buffer_size = 0;
return;
}
static void tokenbuf_move(tokenbuf_t *src, tokenbuf_t *dst)
{
dst->begin = src->begin;
dst->end = src->end;
dst->buffer_size = src->buffer_size;
tokenbuf_init(src);
return;
}
static int tokenbuf_assign(tokenbuf_t *buf, const char *data, size_t len)
{
char *p;
if ((p = malloc(len + 1)) == NULL)
return 0;
memcpy(p, data, len);
buf->begin = p;
buf->end = p + len;
buf->buffer_size = len + 1;
*((char *)(buf->end)) = NUL;
return 1;
}
static int tokenbuf_append(tokenbuf_t *output, const char *data, size_t len)
{
char *new_buffer;
size_t new_size;
char *tmp;
/* 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(TOKENBUF_INITIAL_BUFSIZE)) == NULL)
return 0;
output->buffer_size = TOKENBUF_INITIAL_BUFSIZE;
}
/* 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. */
if ((tmp = malloc(output->end - output->begin + len + 1)) == NULL)
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);
if ((new_buffer = realloc((char *)output->begin, new_size)) == 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) = NUL;
return 1;
}
static void tokenbuf_free(tokenbuf_t *buf)
{
if (buf->begin != NULL && buf->buffer_size > 0)
free((char *)buf->begin);
buf->begin = buf->end = NULL;
buf->buffer_size = 0;
return;
}
static size_t tokenbuf_toint(tokenbuf_t *number)
{
const char *p;
size_t num;
num = 0;
for (p = number->begin; p != number->end; ++p) {
num *= 10;
num += *p - '0';
}
return num;
}
/*
**
** ==== ESCAPE SEQUENCE FUNCTIONS ====
**
*/
static void expand_range(char a, char b, char_class_t class)
{
do {
class[(int)a] = 1;
}
while (++a <= b);
}
static var_rc_t expand_character_class(const char *desc, char_class_t class)
{
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 != NUL) {
if (desc[1] == '-' && desc[2] != NUL) {
if (desc[0] > desc[2])
return VAR_ERR_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_ERR_INCOMPLETE_OCTAL;
if (!isoct(**src) || !isoct((*src)[1]) || !isoct((*src)[2]))
return VAR_ERR_INVALID_OCTAL;
c = **src - '0';
if (c > 3)
return VAR_ERR_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_ERR_INCOMPLETE_HEX;
if (!ishex(**src) || !ishex((*src)[1]))
return VAR_ERR_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)) != VAR_OK)
return rc;
++(*src);
}
if (*src == end)
return VAR_ERR_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_ERR_INCOMPLETE_HEX;
if (**src == '{') {
++(*src);
return expand_grouped_hex(src, dst, end);
} else
return expand_simple_hex(src, dst, end);
}
/*
**
** ==== RECURSIVE-DESCEND VARIABLE EXPANSION PARSER ====
**
*/
/* forward declarations */
static int parse_variable(var_t *var, var_parse_t *ctx, const char *begin, const char *end, int force_expand, tokenbuf_t *result, int index_this, int* rel_lookup_flag);
static int parse_command (var_t *var, var_parse_t *ctx, const char *begin, const char *end, int force_expand, tokenbuf_t *data, int index_this, int* rel_lookup_flag);
static int parse_num_exp (var_t *var, var_parse_t *ctx, const char *begin, const char *end, int index_this, int* result, int* failed, int* rel_lookup_flag);
/* parse plain text */
static int
parse_text(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end)
{
const char *p;
/* parse until delim_init (variable construct)
or index_open (loop construct) is found */
for (p = begin; p != end; p++) {
if (*p == var->syntax.escape) {
p++; /* skip next character */
if (p == end)
return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
}
else if (*p == var->syntax.delim_init)
break;
else if ( var->syntax.index_open != NUL
&& ( *p == var->syntax.index_open
|| *p == var->syntax.index_close))
break;
}
return (p - begin);
}
/* parse variable name */
static int
parse_varname(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end)
{
const char *p;
/* parse as long as name class characters are found */
for (p = begin; p != end && var->syntax_nameclass[(int)(*p)]; p++)
;
return (p - begin);
}
/* parse number */
static int
parse_number(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end)
{
const char *p;
/* parse as long as digits are found */
for (p = begin; p != end && isdigit((int)(*p)); p++)
;
return (p - begin);
}
/* parse substitution text */
static int
parse_substext(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end)
{
const char *p;
/* parse until delim_init or '/' */
for (p = begin; p != end && *p != var->syntax.delim_init && *p != '/'; p++) {
if (*p == var->syntax.escape) {
if (p + 1 == end)
return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
p++;
}
}
return (p - begin);
}
/* parse expression? XXX text */
static int
parse_exptext(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end)
{
const char *p;
/* parse until delim_init or delim_close or ':' */
for (p = begin; p != end
&& *p != var->syntax.delim_init
&& *p != var->syntax.delim_close
&& *p != ':'; p++) {
if (*p == var->syntax.escape) {
if (p + 1 == end)
return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
p++;
}
}
return (p - begin);
}
/* convert a string into a decimal number */
static int
convert_num_exp_read_int(const char **begin, const char *end)
{
int num = 0;
do {
num *= 10;
num += **begin - '0';
++(*begin);
} while (isdigit(**begin) && *begin != end);
return num;
}
/* parse number expression operand */
static int
parse_num_exp_operand(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int index_this, int *result, int *failed, int *rel_lookup_flag)
{
const char *p;
tokenbuf_t tmp;
int rc;
p = begin;
tokenbuf_init(&tmp);
if (begin == end)
return VAR_ERR_INCOMPLETE_INDEX_SPEC;
if (*p == '(') {
rc = parse_num_exp(var, ctx, ++p, end, index_this, result, failed,
rel_lookup_flag);
if (rc < 0)
return rc;
p += rc;
if (p == end)
return VAR_ERR_INCOMPLETE_INDEX_SPEC;
if (*p != ')')
return VAR_ERR_UNCLOSED_BRACKET_IN_INDEX;
++p;
}
else if (*p == var->syntax.delim_init) {
rc = parse_variable(var, ctx, p, end,
1, &tmp, index_this, rel_lookup_flag);
if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
*failed = 1;
rc = parse_variable(var, ctx, p, end,
0, &tmp, index_this, rel_lookup_flag);
if (rc < 0)
return rc;
p += rc;
*result = 0;
}
else {
if (rc < 0)
return rc;
p += rc;
rc = parse_num_exp(var, ctx, tmp.begin, tmp.end, index_this, result,
failed, rel_lookup_flag );
tokenbuf_free(&tmp);
if (rc < 0)
return rc;
}
}
else if (var->syntax.index_mark && *p == var->syntax.index_mark) {
p++;
*result = index_this;
(*rel_lookup_flag)++;
}
else if (isdigit(*p)) {
*result = convert_num_exp_read_int(&p, end);
}
else if (*p == '+') {
if (end - p > 1 && isdigit(p[1])) {
p++;
*result = convert_num_exp_read_int(&p, end);
}
else
return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
}
else if (*p == '-') {
if (end - p > 1 && isdigit(p[1])) {
p++;
*result = convert_num_exp_read_int(&p, end);
*result = 0 - *result;
}
else
return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
}
else
return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
return (p - begin);
}
static int
parse_num_exp(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int index_this,
int *result, int *failed, int *rel_lookup_flag)
{
const char *p = begin;
char operator;
int right;
int rc;
if (begin == end)
return VAR_ERR_INCOMPLETE_INDEX_SPEC;
rc = parse_num_exp_operand(var, ctx, p, end, index_this, result,
failed, rel_lookup_flag);
if (rc < 0)
return rc;
p += rc;
while (p != end) {
if (*p == '+' || *p == '-') {
operator = *p++;
rc = parse_num_exp(var, ctx, p, end, index_this, &right, failed,
rel_lookup_flag);
if (rc < 0)
return rc;
p += rc;
if (operator == '+')
*result = *result + right;
else
*result = *result - right;
}
else if (*p == '*' || *p == '/' || *p == '%') {
operator = *p++;
rc = parse_num_exp_operand(var, ctx, p, end, index_this, &right, failed,
rel_lookup_flag);
if (rc < 0)
return rc;
p += rc;
if (operator == '*') {
*result = *result * right;
}
else if (operator == '/') {
if (right == 0) {
if (*failed)
*result = 0;
else
return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
}
else
*result = *result / right;
}
else if (operator == '%') {
if (right == 0) {
if (*failed)
*result = 0;
else
return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
}
else
*result = *result % right;
}
}
else
break;
}
return p - begin;
}
static int
parse_expression(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int force_expand,
tokenbuf_t *result, int index_this, int *rel_lookup_flag)
{
const char *p = begin;
const char *data;
size_t len, buffer_size;
int failed = 0;
int rc;
int idx = 0;
tokenbuf_t name;
tokenbuf_t tmp;
/* Clear the tokenbufs to make sure we have a defined state. */
tokenbuf_init(&name);
tokenbuf_init(&tmp);
tokenbuf_init(result);
/* Expect STARTDELIM. */
if (p == end || *p != var->syntax.delim_open)
return 0;
if (++p == end)
return VAR_ERR_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 = parse_varname(var, ctx, p, end);
if (rc < 0)
goto error_return;
if (rc > 0) {
if (!tokenbuf_append(&name, p, rc)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
p += rc;
}
rc = parse_variable(var, ctx, p, end,
force_expand, &tmp, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc > 0) {
if (!tokenbuf_append(&name, tmp.begin, tmp.end - tmp.begin)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
p += rc;
}
} while (rc > 0);
/* We must have the complete variable name now, so make sure we
do. */
if (name.begin == name.end) {
if (force_expand) {
rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
goto error_return;
}
else {
/* If no force_expand is requested, we have to back-off.
We're not sure whether our approach here is 100% correct,
because it _could_ have side-effects according to Peter
Simons, but as far as we know and tried it, it is
correct. But be warned -- RSE */
result->begin = begin - 1;
result->end = p;
result->buffer_size = 0;
goto goahead;
}
}
/* If the next token is START-INDEX, read the index specification. */
if (var->syntax.index_open && *p == var->syntax.index_open) {
rc = parse_num_exp(var, ctx, ++p, end, index_this, &idx, &failed,
rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc == 0) {
rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
goto error_return;
}
p += rc;
if (p == end) {
rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
goto error_return;
}
if (*p != var->syntax.index_close) {
rc = VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
goto error_return;
}
p++;
}
/* Now we have the name of the variable stored in "name". The next
token here must either be an END-DELIM or a ':'. */
if (p == end || (*p != var->syntax.delim_close && *p != ':')) {
rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
goto error_return;
}
p++;
/* Use the lookup callback to get the variable's contents. */
if (failed) {
result->begin = begin - 1;
result->end = p;
result->buffer_size = 0;
}
else {
rc = (*var->cb_value_fct) (var, var->cb_value_ctx, name.begin, name.end - name.begin, idx,
&data, &len, &buffer_size);
if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
/* The variable is undefined. What we'll do now depends on the
force_expand flag. */
if (force_expand)
goto error_return;
/* 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 if (rc < 0 /* != VAR_OK */) {
goto error_return;
}
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;
}
}
goahead:
if (p[-1] == ':') {
/* Parse and execute commands. */
tokenbuf_free(&tmp);
p--;
while (p != end && *p == ':') {
p++;
if (!failed)
rc = parse_command(var, ctx, p, end,
force_expand, result,
index_this, rel_lookup_flag);
else
rc = parse_command(var, ctx, p, end,
force_expand, &tmp,
index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (failed)
result->end += rc;
}
if (p == end || *p != var->syntax.delim_close) {
rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
goto error_return;
}
p++;
if (failed)
result->end++;
}
/* Exit gracefully. */
tokenbuf_free(&name);
tokenbuf_free(&tmp);
return p - begin;
/* Exit in case of an error. */
error_return:
tokenbuf_free(&name);
tokenbuf_free(&tmp);
tokenbuf_free(result);
return rc;
}
static int
parse_variable(
var_t *var,
var_parse_t *ctx,
const char *begin, const char *end,
int force_expand, tokenbuf_t *result, int index_this,
int *rel_lookup_flag)
{
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. */
tokenbuf_init(result);
/* Expect VARINIT. */
if (p == end || *p != var->syntax.delim_init)
return 0;
if (++p == end)
return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
/* Try to read the variable name. If that fails, we're parsing a
complex expression. */
rc = parse_varname(var, ctx, p, end);
if (rc < 0)
return rc;
if (rc > 0) {
rc2 = (*var->cb_value_fct)(var, var->cb_value_ctx, p, rc, 0, &data, &len, &buffer_size);
if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !force_expand) {
result->begin = begin;
result->end = begin + 1 + rc;
result->buffer_size = 0;
return 1 + rc;
}
if (rc2 < 0 /* != VAR_OK */)
return rc2;
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 = parse_expression(var, ctx, p, end,
force_expand, result, index_this, rel_lookup_flag);
if (rc > 0)
rc++;
return rc;
}
static int
parse_exptext_or_variable(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int force_expand,
tokenbuf_t *result, int index_this,
int *rel_lookup_flag)
{
const char *p = begin;
tokenbuf_t tmp;
int rc;
tokenbuf_init(result);
tokenbuf_init(&tmp);
if (begin == end)
return 0;
do {
rc = parse_exptext(var, ctx, p, end);
if (rc < 0)
goto error_return;
if (rc > 0) {
if (!tokenbuf_append(result, p, rc)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
p += rc;
}
rc = parse_variable(var, ctx, p, end,
force_expand, &tmp, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc > 0) {
p += rc;
if (!tokenbuf_append
(result, tmp.begin, tmp.end - tmp.begin)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
}
} while (rc > 0);
tokenbuf_free(&tmp);
return p - begin;
error_return:
tokenbuf_free(&tmp);
tokenbuf_free(result);
return rc;
}
static int
parse_substext_or_variable(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int force_expand,
tokenbuf_t *result, int index_this,
int *rel_lookup_flag)
{
const char *p = begin;
tokenbuf_t tmp;
int rc;
tokenbuf_init(result);
tokenbuf_init(&tmp);
if (begin == end)
return 0;
do {
rc = parse_substext(var, ctx, p, end);
if (rc < 0)
goto error_return;
if (rc > 0) {
if (!tokenbuf_append(result, p, rc)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
p += rc;
}
rc = parse_variable(var, ctx, p, end,
force_expand, &tmp, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc > 0) {
p += rc;
if (!tokenbuf_append
(result, tmp.begin, tmp.end - tmp.begin)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
}
} while (rc > 0);
tokenbuf_free(&tmp);
return p - begin;
error_return:
tokenbuf_free(&tmp);
tokenbuf_free(result);
return rc;
}
static int parse_class_description(tokenbuf_t *src, tokenbuf_t *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_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC;
for (c = *p, d = p[2]; c <= d; ++c) {
if (!tokenbuf_append(dst, (char *)&c, 1))
return VAR_ERR_OUT_OF_MEMORY;
}
p += 3;
} else {
if (!tokenbuf_append(dst, p, 1))
return VAR_ERR_OUT_OF_MEMORY;
p++;
}
}
return VAR_OK;
}
static int transpose(tokenbuf_t *data, tokenbuf_t *search,
tokenbuf_t *replace)
{
tokenbuf_t srcclass, dstclass;
const char *p;
int rc;
size_t i;
tokenbuf_init(&srcclass);
tokenbuf_init(&dstclass);
if ((rc = parse_class_description(search, &srcclass)) != VAR_OK)
goto error_return;
if ((rc = parse_class_description(replace, &dstclass)) != VAR_OK)
goto error_return;
if (srcclass.begin == srcclass.end) {
rc = VAR_ERR_EMPTY_TRANSPOSE_CLASS;
goto error_return;
}
if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
rc = VAR_ERR_TRANSPOSE_CLASSES_MISMATCH;
goto error_return;
}
if (data->buffer_size == 0) {
tokenbuf_t tmp;
if (!tokenbuf_assign(&tmp, data->begin, data->end - data->begin)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
tokenbuf_move(&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;
}
}
}
tokenbuf_free(&srcclass);
tokenbuf_free(&dstclass);
return VAR_OK;
error_return:
tokenbuf_free(search);
tokenbuf_free(replace);
tokenbuf_free(&srcclass);
tokenbuf_free(&dstclass);
return rc;
}
static int cut_out_offset(tokenbuf_t *data, tokenbuf_t *number1,
tokenbuf_t *number2, int isrange)
{
tokenbuf_t res;
const char *p;
size_t num1;
size_t num2;
num1 = tokenbuf_toint(number1);
num2 = tokenbuf_toint(number2);
/* Determine begin of result string. */
if ((data->end - data->begin) < num1)
return VAR_ERR_OFFSET_OUT_OF_BOUNDS;
else
p = data->begin + num1;
/* If num2 is zero, we copy the rest from there. */
if (num2 == 0) {
if (!tokenbuf_assign(&res, p, data->end - p))
return VAR_ERR_OUT_OF_MEMORY;
} else {
/* OK, then use num2. */
if (isrange) {
if ((p + num2) > data->end)
return VAR_ERR_RANGE_OUT_OF_BOUNDS;
if (!tokenbuf_assign(&res, p, num2))
return VAR_ERR_OUT_OF_MEMORY;
} else {
if (num2 < num1)
return VAR_ERR_OFFSET_LOGIC;
if ((data->begin + num2) > data->end)
return VAR_ERR_RANGE_OUT_OF_BOUNDS;
if (!tokenbuf_assign(&res, p, num2 - num1 + 1))
return VAR_ERR_OUT_OF_MEMORY;
}
}
tokenbuf_free(data);
tokenbuf_move(&res, data);
return VAR_OK;
}
static int parse_regex_replace(const char *data, tokenbuf_t *orig,
regmatch_t *pmatch, tokenbuf_t *expanded)
{
const char *p = orig->begin;
size_t i;
tokenbuf_init(expanded);
while (p != orig->end) {
if (*p == '\\') {
if (orig->end - p <= 1) {
tokenbuf_free(expanded);
return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
}
p++;
if (*p == '\\') {
if (!tokenbuf_append(expanded, p, 1)) {
tokenbuf_free(expanded);
return VAR_ERR_OUT_OF_MEMORY;
}
p++;
continue;
}
if (!isdigit((int)*p)) {
tokenbuf_free(expanded);
return VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
}
i = *p - '0';
p++;
if (pmatch[i].rm_so == -1) {
tokenbuf_free(expanded);
return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
}
if (!tokenbuf_append(expanded, data + pmatch[i].rm_so,
pmatch[i].rm_eo - pmatch[i].rm_so)) {
tokenbuf_free(expanded);
return VAR_ERR_OUT_OF_MEMORY;
}
} else {
if (!tokenbuf_append(expanded, p, 1)) {
tokenbuf_free(expanded);
return VAR_ERR_OUT_OF_MEMORY;
}
p++;
}
}
return VAR_OK;
}
static int search_and_replace(tokenbuf_t *data, tokenbuf_t *search,
tokenbuf_t *replace, tokenbuf_t *flags)
{
const char *p;
int case_insensitive = 0;
int global = 0;
int no_regex = 0;
int rc;
if (search->begin == search->end)
return VAR_ERR_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_ERR_UNKNOWN_REPLACE_FLAG;
}
}
if (no_regex) {
tokenbuf_t tmp;
tokenbuf_init(&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 (!tokenbuf_append(&tmp, p, 1)) {
tokenbuf_free(&tmp);
return VAR_ERR_OUT_OF_MEMORY;
}
++p;
} else {
tokenbuf_append(&tmp, replace->begin,
replace->end - replace->begin);
p += search->end - search->begin;
if (!global) {
if (!tokenbuf_append(&tmp, p, data->end - p)) {
tokenbuf_free(&tmp);
return VAR_ERR_OUT_OF_MEMORY;
}
break;
}
}
}
tokenbuf_free(data);
tokenbuf_move(&tmp, data);
} else {
tokenbuf_t tmp;
tokenbuf_t mydata;
tokenbuf_t 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 (!tokenbuf_assign(&tmp, search->begin, search->end - search->begin))
return VAR_ERR_OUT_OF_MEMORY;
if (!tokenbuf_assign(&mydata, data->begin, data->end - data->begin)) {
tokenbuf_free(&tmp);
return VAR_ERR_OUT_OF_MEMORY;
}
/* Compile the pattern. */
rc = regcomp(&preg, tmp.begin, REG_NEWLINE | REG_EXTENDED|((case_insensitive)?REG_ICASE:0));
tokenbuf_free(&tmp);
if (rc != 0) {
tokenbuf_free(&mydata);
return VAR_ERR_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 ||
p + pmatch[0].rm_so == mydata.end) {
tokenbuf_append(&tmp, p, mydata.end - p);
break;
} else {
rc = parse_regex_replace(p, replace, pmatch, &myreplace);
if (rc != VAR_OK) {
regfree(&preg);
tokenbuf_free(&tmp);
tokenbuf_free(&mydata);
return rc;
}
if (!tokenbuf_append(&tmp, p, pmatch[0].rm_so) ||
!tokenbuf_append(&tmp, myreplace.begin,
myreplace.end - myreplace.begin)) {
regfree(&preg);
tokenbuf_free(&tmp);
tokenbuf_free(&mydata);
tokenbuf_free(&myreplace);
return VAR_ERR_OUT_OF_MEMORY;
} else {
p += pmatch[0].rm_eo;
if (pmatch[0].rm_eo - pmatch[0].rm_so == 0)
{
if (!tokenbuf_append(&tmp, p, 1))
{
regfree(&preg);
tokenbuf_free(&tmp);
tokenbuf_free(&mydata);
tokenbuf_free(&myreplace);
return VAR_ERR_OUT_OF_MEMORY;
}
++p;
}
tokenbuf_free(&myreplace);
}
if (!global) {
if (!tokenbuf_append(&tmp, p, mydata.end - p)) {
regfree(&preg);
tokenbuf_free(&tmp);
tokenbuf_free(&mydata);
return VAR_ERR_OUT_OF_MEMORY;
}
break;
}
}
}
regfree(&preg);
tokenbuf_free(data);
tokenbuf_move(&tmp, data);
tokenbuf_free(&mydata);
}
return VAR_OK;
}
static int padding(tokenbuf_t *data, tokenbuf_t *widthstr, tokenbuf_t *fill,
char position)
{
tokenbuf_t result;
size_t width = tokenbuf_toint(widthstr);
int i;
if (fill->begin == fill->end)
return VAR_ERR_EMPTY_PADDING_FILL_STRING;
tokenbuf_init(&result);
if (position == 'l') {
i = width - (data->end - data->begin);
if (i > 0) {
i = i / (fill->end - fill->begin);
while (i > 0) {
if (!tokenbuf_append
(data, fill->begin, fill->end - fill->begin))
return VAR_ERR_OUT_OF_MEMORY;
i--;
}
i = (width - (data->end - data->begin))
% (fill->end - fill->begin);
if (!tokenbuf_append(data, fill->begin, i))
return VAR_ERR_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 (!tokenbuf_append
(&result, fill->begin, fill->end - fill->begin)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
i--;
}
i = (width - (data->end - data->begin))
% (fill->end - fill->begin);
if (!tokenbuf_append(&result, fill->begin, i)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
tokenbuf_free(data);
tokenbuf_move(&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 (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
i--;
}
i = ((width - (data->end - data->begin)) / 2)
% (fill->end - fill->begin);
if (!tokenbuf_append(&result, fill->begin, i)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
/* Append the actual data string. */
if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
/* Append the suffix. */
i = width - (result.end - result.begin);
i = i / (fill->end - fill->begin);
while (i > 0) {
if (!tokenbuf_append
(&result, fill->begin, fill->end - fill->begin)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
i--;
}
i = width - (result.end - result.begin);
if (!tokenbuf_append(&result, fill->begin, i)) {
tokenbuf_free(&result);
return VAR_ERR_OUT_OF_MEMORY;
}
/* Move string from temporary buffer to data buffer. */
tokenbuf_free(data);
tokenbuf_move(&result, data);
}
}
return VAR_OK;
}
static int
parse_command(
var_t *var, var_parse_t *ctx,
const char *begin, const char *end,
int force_expand,
tokenbuf_t *data, int index_this, int *rel_lookup_flag)
{
const char *p = begin;
tokenbuf_t tmptokbuf;
tokenbuf_t search, replace, flags;
tokenbuf_t number1, number2;
int isrange;
int rc;
tokenbuf_init(&tmptokbuf);
tokenbuf_init(&search);
tokenbuf_init(&replace);
tokenbuf_init(&flags);
tokenbuf_init(&number1);
tokenbuf_init(&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 live in an allocated buffer,
we have to copy it before modifying the contents. */
if (data->buffer_size == 0) {
if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
rc = VAR_ERR_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 (!tokenbuf_assign
(data, data->begin, data->end - data->begin)) {
rc = VAR_ERR_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 = parse_number(var, ctx, p, end);
if (rc == 0) {
rc = VAR_ERR_MISSING_START_OFFSET;
goto error_return;
}
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_ERR_INVALID_OFFSET_DELIMITER;
goto error_return;
}
rc = parse_number(var, ctx, 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[((sizeof(int)*8)/3)+10]; /* sufficient size: <#bits> x log_10(2) + safety */
sprintf(buf, "%d", (int)(data->end - data->begin));
tokenbuf_free(data);
if (!tokenbuf_assign(data, buf, strlen(buf))) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
}
++p;
break;
case '-': /* Substitute parameter if data is empty. */
p++;
rc = parse_exptext_or_variable(var, ctx, p, end,
force_expand, &tmptokbuf,
index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc == 0) {
rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
goto error_return;
}
p += rc;
if (data->begin != NULL && data->begin == data->end) {
tokenbuf_free(data);
tokenbuf_move(&tmptokbuf, data);
}
break;
case '*': /* Return "" if data is not empty, parameter otherwise. */
p++;
rc = parse_exptext_or_variable(var, ctx, p, end,
force_expand, &tmptokbuf, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc == 0) {
rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
goto error_return;
}
p += rc;
if (data->begin != NULL) {
if (data->begin == data->end) {
tokenbuf_free(data);
tokenbuf_move(&tmptokbuf, data);
} else {
tokenbuf_free(data);
data->begin = data->end = "";
data->buffer_size = 0;
}
}
break;
case '+': /* Substitute parameter if data is not empty. */
p++;
rc = parse_exptext_or_variable(var, ctx, p, end,
force_expand, &tmptokbuf, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
if (rc == 0) {
rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
goto error_return;
}
p += rc;
if (data->begin != NULL && data->begin != data->end) {
tokenbuf_free(data);
tokenbuf_move(&tmptokbuf, data);
}
break;
case 's': /* Search and replace. */
p++;
if (*p != '/')
return VAR_ERR_MALFORMATTED_REPLACE;
p++;
rc = parse_substext_or_variable(var, ctx, p, end,
force_expand, &search, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (*p != '/') {
rc = VAR_ERR_MALFORMATTED_REPLACE;
goto error_return;
}
p++;
rc = parse_substext_or_variable(var, ctx, p, end,
force_expand, &replace, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (*p != '/') {
rc = VAR_ERR_MALFORMATTED_REPLACE;
goto error_return;
}
p++;
rc = parse_exptext(var, ctx, p, end);
if (rc < 0)
goto error_return;
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_ERR_MALFORMATTED_TRANSPOSE;
p++;
rc = parse_substext_or_variable(var, ctx, p, end,
force_expand, &search, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (*p != '/') {
rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
goto error_return;
}
p++;
rc = parse_substext_or_variable(var, ctx, p, end,
force_expand, &replace, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (*p != '/') {
rc = VAR_ERR_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_ERR_MALFORMATTED_PADDING;
p++;
rc = parse_number(var, ctx, p, end);
if (rc == 0) {
rc = VAR_ERR_MISSING_PADDING_WIDTH;
goto error_return;
}
number1.begin = p;
number1.end = p + rc;
number1.buffer_size = 0;
p += rc;
if (*p != '/') {
rc = VAR_ERR_MALFORMATTED_PADDING;
goto error_return;
}
p++;
rc = parse_substext_or_variable(var, ctx, p, end,
force_expand, &replace, index_this, rel_lookup_flag);
if (rc < 0)
goto error_return;
p += rc;
if (*p != '/') {
rc = VAR_ERR_MALFORMATTED_PADDING;
goto error_return;
}
p++;
if (*p != 'l' && *p != 'c' && *p != 'r') {
rc = VAR_ERR_MALFORMATTED_PADDING;
goto error_return;
}
p++;
if (data->begin) {
rc = padding(data, &number1, &replace, p[-1]);
if (rc < 0)
goto error_return;
}
break;
default:
return VAR_ERR_UNKNOWN_COMMAND_CHAR;
}
/* Exit gracefully. */
tokenbuf_free(&tmptokbuf);
tokenbuf_free(&search);
tokenbuf_free(&replace);
tokenbuf_free(&flags);
tokenbuf_free(&number1);
tokenbuf_free(&number2);
return p - begin;
error_return:
tokenbuf_free(data);
tokenbuf_free(&tmptokbuf);
tokenbuf_free(&search);
tokenbuf_free(&replace);
tokenbuf_free(&flags);
tokenbuf_free(&number1);
tokenbuf_free(&number2);
return rc;
}
/* expand the loop limits */
static var_rc_t
parse_looplimits(
var_t *var,
var_parse_t *ctx,
const char *begin, const char *end,
int* start, int* step, int* stop, int* open_end)
{
const char *p;
int rc;
int failed;
int dummy;
p = begin;
if (begin == end)
return 0;
if (*p != var->syntax.delim_open)
return 0;
else
p++;
/* Read start value for the loop. */
failed = 0;
rc = parse_num_exp(var, ctx, p, end, 0, start, &failed, &dummy);
if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
*start = 0; /* use default */
else if (rc < 0)
return rc;
else
p += rc;
if (failed)
return VAR_ERR_UNDEFINED_VARIABLE;
if (*p != ',')
return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
else
p++;
/* Read step value for the loop. */
failed = 0;
rc = parse_num_exp(var, ctx, p, end, 0, step, &failed, &dummy);
if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
*step = 1; /* use default */
else if (rc < 0)
return rc;
else
p += rc;
if (failed)
return VAR_ERR_UNDEFINED_VARIABLE;
if (*p != ',') {
if (*p != var->syntax.delim_close)
return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
else {
p++;
*stop = *step;
*step = 1;
if (rc > 0)
*open_end = 0;
else
*open_end = 1;
return p - begin;
}
}
else
p++;
/* Read stop value for the loop. */
failed = 0;
rc = parse_num_exp(var, ctx, p, end, 0, stop, &failed, &dummy);
if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
*stop = 0; /* use default */
*open_end = 1;
}
else if (rc < 0)
return rc;
else {
*open_end = 0;
p += rc;
}
if (failed)
return VAR_ERR_UNDEFINED_VARIABLE;
if (*p != var->syntax.delim_close)
return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
return ++p - begin;
}
/* callback wrapper context */
typedef struct {
var_cb_value_t cb_value_fct;
void *cb_value_ctx;
int *rel_lookup_flag;
} var_wrapper_t;
/* callback wrapper function */
static int
lookup_wrapper(
var_t *var, void *ctx,
const char *var_ptr, size_t var_len, int var_idx,
const char **val_ptr, size_t *val_len, size_t *val_size)
{
char buf[1];
var_wrapper_t *wcon = (var_wrapper_t *)ctx;
int rc;
/* pass through to original callback */
rc = (*wcon->cb_value_fct)(var, wcon->cb_value_ctx,
var_ptr, var_len, var_idx,
val_ptr, val_len, val_size);
/* convert undefined variable into empty variable */
if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
(*wcon->rel_lookup_flag)--;
buf[0] = NUL;
*val_ptr = buf;
*val_len = 0;
*val_size = 0;
return VAR_OK;
}
return rc;
}
/* expand input in general */
static var_rc_t
parse_input(
var_t *var,
var_parse_t *ctx,
const char *begin, const char *end,
int force_expand,
tokenbuf_t *output, int index_this,
size_t recursion_level, int *rel_lookup_flag)
{
const char *p = begin;
int rc, rc2;
tokenbuf_t result;
int start, step, stop, open_end;
int i;
int output_backup;
var_wrapper_t wcon;
int my_rel_lookup_flag;
int original_rel_lookup_state;
int loop_limit_length;
tokenbuf_init(&result);
if (rel_lookup_flag == NULL) {
rel_lookup_flag = &my_rel_lookup_flag;
*rel_lookup_flag = 0;
}
do {
if (begin != end && var->syntax.index_open && *begin == var->syntax.index_open) {
original_rel_lookup_state = *rel_lookup_flag;
loop_limit_length = -1;
begin++;
start = 0;
step = 1;
stop = 0;
open_end = 1;
rc = 0;
output_backup = 0;
re_loop:
for (i = start;
(open_end && (loop_limit_length < 0 || *rel_lookup_flag > original_rel_lookup_state)) ||
(!open_end && i <= stop);
i += step) {
*rel_lookup_flag = original_rel_lookup_state;
output_backup = output->end - output->begin;
/* activate callback wrapper */
wcon.cb_value_fct = var->cb_value_fct;
wcon.cb_value_ctx = var->cb_value_ctx;
wcon.rel_lookup_flag = rel_lookup_flag;
var->cb_value_fct = lookup_wrapper;
var->cb_value_ctx = &wcon;
rc = parse_input(var, ctx, begin, end,
1, output, i, recursion_level+1, rel_lookup_flag);
/* deactivate callback wrapper */
var->cb_value_fct = wcon.cb_value_fct;
var->cb_value_ctx = wcon.cb_value_ctx;
if (rc < 0)
goto error_return;
if (begin[rc] != var->syntax.index_close) {
rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
goto error_return;
}
if (loop_limit_length < 0) {
rc2 = parse_looplimits(var, ctx, begin + rc + 1, end,
&start, &step,
&stop, &open_end);
if (rc2 < 0)
goto error_return;
else if (rc2 == 0)
loop_limit_length = 0;
else if (rc2 > 0) {
loop_limit_length = rc2;
output->end = output->begin + output_backup;
goto re_loop;
}
}
}
if (open_end)
output->end = output->begin + output_backup;
else
*rel_lookup_flag = original_rel_lookup_state;
begin += rc;
begin++;
begin += loop_limit_length;
continue;
}
rc = parse_text(var, ctx, begin, end);
if (rc > 0) {
if (!tokenbuf_append(output, begin, rc)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
begin += rc;
continue;
} else if (rc < 0)
goto error_return;
rc = parse_variable(var, ctx, begin, end,
force_expand, &result,
index_this, rel_lookup_flag);
if (rc > 0) {
if (!tokenbuf_append(output, result.begin, result.end - result.begin)) {
rc = VAR_ERR_OUT_OF_MEMORY;
goto error_return;
}
begin += rc;
continue;
}
if (rc < 0)
goto error_return;
} while (begin != end && rc > 0);
if (recursion_level == 0 && begin != end) {
rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
goto error_return;
}
return begin - p;
error_return:
tokenbuf_free(output);
tokenbuf_free(&result);
output->begin = p;
output->end = begin;
output->buffer_size = 0;
return rc;
}
/*
**
** ==== APPLICATION PROGRAMMING INTERFACE (API) ====
**
*/
var_rc_t
var_create(
var_t **pvar)
{
var_t *var;
if (pvar == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
memset(var, 0, sizeof(var));
var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
*pvar = var;
return VAR_OK;
}
var_rc_t
var_destroy(
var_t *var)
{
if (var == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
free(var);
return VAR_OK;
}
var_rc_t
var_config(
var_t *var,
var_config_t mode,
...)
{
va_list ap;
var_rc_t rc;
if (var == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
va_start(ap, mode);
switch (mode) {
case VAR_CONFIG_SYNTAX: {
var_syntax_t *s;
s = (var_syntax_t *)va_arg(ap, void *);
if (s == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
var->syntax.escape = s->escape;
var->syntax.delim_init = s->delim_init;
var->syntax.delim_open = s->delim_open;
var->syntax.delim_close = s->delim_close;
var->syntax.index_open = s->index_open;
var->syntax.index_close = s->index_close;
var->syntax.index_mark = s->index_mark;
var->syntax.name_chars = NULL; /* unused internally */
if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
return VAR_RC(rc);
if ( var->syntax_nameclass[(int)var->syntax.delim_init]
|| var->syntax_nameclass[(int)var->syntax.delim_open]
|| var->syntax_nameclass[(int)var->syntax.delim_close]
|| var->syntax_nameclass[(int)var->syntax.escape])
return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
break;
}
case VAR_CONFIG_CB_VALUE: {
var_cb_value_t fct;
void *ctx;
fct = (var_cb_value_t)va_arg(ap, void *);
ctx = (void *)va_arg(ap, void *);
var->cb_value_fct = fct;
var->cb_value_ctx = ctx;
break;
}
default:
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
}
va_end(ap);
return VAR_OK;
}
var_rc_t
var_unescape(
var_t *var,
const char *src, size_t srclen,
char *dst, size_t dstlen,
int all)
{
const char *end;
var_rc_t rc;
if (var == NULL || src == NULL || dst == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
end = src + srclen;
while (src < end) {
if (*src == '\\') {
if (++src == end)
return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
switch (*src) {
case '\\':
if (!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)) != VAR_OK)
return VAR_RC(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((int)src[1])
&& isdigit((int)src[2])) {
if ((rc = expand_octal(&src, &dst, end)) != 0)
return VAR_RC(rc);
break;
}
default:
if (!all) {
*dst++ = '\\';
}
*dst++ = *src;
}
++src;
} else
*dst++ = *src++;
}
*dst = NUL;
return VAR_OK;
}
var_rc_t
var_expand(
var_t *var,
const char *src_ptr, size_t src_len,
char **dst_ptr, size_t *dst_len,
int force_expand)
{
var_parse_t ctx;
tokenbuf_t output;
var_rc_t rc;
/* argument sanity checks */
if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
/* prepare internal expansion context */
ctx.force_expand = force_expand;
/* set the dst_ptr pointer to the src_ptr so that it is correctly
initialized in case we fail with an error later. */
*dst_ptr = (char *)src_ptr;
/* call the parsing */
tokenbuf_init(&output);
rc = parse_input(var, &ctx,
src_ptr, src_ptr + src_len,
ctx.force_expand, &output, 0, 0, NULL);
/* post-process output */
if (rc >= 0) {
/* always NUL-terminate output for convinience reasons */
if (!tokenbuf_append(&output, "\0", 1)) {
tokenbuf_free(&output);
return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
}
output.end--;
/* provide dst_ptrs */
*dst_ptr = (char *)output.begin;
if (dst_len != NULL)
*dst_len = output.end - output.begin;
/* canonify all positive answers */
rc = VAR_OK;
}
else {
/* provide error dst_ptr */
*dst_ptr = (char *)src_ptr;
if (dst_len != NULL)
*dst_len = output.end - output.begin;
}
return VAR_RC(rc);
}
static const char *var_errors[] = {
"everything ok", /* VAR_OK = 0 */
"incomplete named character", /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
"incomplete hexadecimal value", /* VAR_ERR_INCOMPLETE_HEX */
"invalid hexadecimal value", /* VAR_ERR_INVALID_HEX */
"octal value too large", /* VAR_ERR_OCTAL_TOO_LARGE */
"invalid octal value", /* VAR_ERR_INVALID_OCTAL */
"incomplete octal value", /* VAR_ERR_INCOMPLETE_OCTAL */
"incomplete grouped hexadecimal value", /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
"incorrect character class specification", /* VAR_ERR_INCORRECT_CLASS_SPEC */
"invalid expansion configuration", /* VAR_ERR_INVALID_CONFIGURATION */
"out of memory", /* VAR_ERR_OUT_OF_MEMORY */
"incomplete variable specification", /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
"undefined variable", /* VAR_ERR_UNDEFINED_VARIABLE */
"input is neither text nor variable", /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
"unknown command character in variable", /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
"malformatted search and replace operation", /* VAR_ERR_MALFORMATTED_REPLACE */
"unknown flag in search and replace operation", /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
"invalid regex in search and replace operation", /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
"missing parameter in command", /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
"empty search string in search and replace operation", /* VAR_ERR_EMPTY_SEARCH_STRING */
"start offset missing in cut operation", /* VAR_ERR_MISSING_START_OFFSET */
"offsets in cut operation delimited by unknown character", /* VAR_ERR_INVALID_OFFSET_DELIMITER */
"range out of bounds in cut operation", /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
"offset out of bounds in cut operation", /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
"logic error in cut operation", /* VAR_ERR_OFFSET_LOGIC */
"malformatted transpose operation", /* VAR_ERR_MALFORMATTED_TRANSPOSE */
"source and target class mismatch in transpose operation", /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
"empty character class in transpose operation", /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
"incorrect character class in transpose operation", /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
"malformatted padding operation", /* VAR_ERR_MALFORMATTED_PADDING */
"width parameter missing in padding operation", /* VAR_ERR_MISSING_PADDING_WIDTH */
"fill string missing in padding operation", /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
"unknown quoted pair in search and replace operation", /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
"sub-matching reference out of range", /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
"invalid argument", /* VAR_ERR_INVALID_ARGUMENT */
"incomplete quoted pair", /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
"lookup function does not support variable arrays", /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
"index of array variable contains an invalid character", /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
"index of array variable is incomplete", /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
"bracket expression in array variable's index not closed", /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
"division by zero error in index specification", /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
"unterminated loop construct", /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
"invalid character in loop limits" /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
};
var_rc_t var_strerror(var_t *var, var_rc_t rc, char **str)
{
if (str == NULL)
return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
rc = 0 - rc;
if (rc < 0 || rc >= sizeof(var_errors) / sizeof(char *))
*str = "unknown error";
else
*str = (char *)var_errors[rc];
return VAR_OK;
}