NetBSD/gnu/dist/postfix/util/mac_expand.c

318 lines
8.1 KiB
C

/*++
/* NAME
/* mac_expand 3
/* SUMMARY
/* attribute expansion
/* SYNOPSIS
/* #include <mac_expand.h>
/*
/* int mac_expand(result, pattern, flags, filter, lookup, context)
/* VSTRING *result;
/* const char *pattern;
/* int flags;
/* const char *filter;
/* const char *lookup(const char *key, int mode, char *context)
/* char *context;
/* DESCRIPTION
/* This module implements parameter-less macro expansions, both
/* conditional and unconditional, and both recursive and non-recursive.
/*
/* In this text, an attribute is considered "undefined" when its value
/* is a null pointer. Otherwise, the attribute is considered "defined"
/* and is expected to have as value a null-terminated string.
/*
/* The following expansions are implemented:
/* .IP "$name, ${name}, $(name)"
/* Unconditional expansion. If the named attribute is defined, the
/* expansion is the value of the named attribute, optionally subjected
/* to further $name expansions. Otherwise, the expansion is empty.
/* .IP "${name?text}, $(name?text)"
/* Conditional expansion. If the named attribute is defined, the
/* expansion is the given text, subjected to another iteration of
/* $name expansion. Otherwise, the expansion is empty.
/* .IP "${name:text}, $(name:text)"
/* Conditional expansion. If the named attribute is undefined, the
/* the expansion is the given text, subjected to another iteration
/* of $name expansion. Otherwise, the expansion is empty.
/* .PP
/* Arguments:
/* .IP result
/* Storage for the result of expansion. The result is truncated
/* upon entry.
/* .IP pattern
/* The string to be expanded.
/* .IP flags
/* Bit-wise OR of zero or more of the following:
/* .RS
/* .IP MAC_EXP_FLAG_RECURSE
/* Expand $name recursively.
/* .PP
/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
/* .RE
/* .IP filter
/* A null pointer, or a null-terminated array of characters that
/* are allowed to appear in an expansion. Illegal characters are
/* replaced by underscores.
/* .IP lookup
/* The attribute lookup routine. Arguments are: the attribute name,
/* MAC_EXP_MODE_TEST to test the existence of the named attribute
/* or MAC_EXP_MODE_USE to use the value of the named attribute,
/* and the caller context that was given to mac_expand(). A null
/* result means that the requested attribute was not defined.
/* .IP context
/* Caller context that is passed on to the attribute lookup routine.
/* DIAGNOSTICS
/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
/* macro nesting.
/*
/* The result value is the binary OR of zero or more of the following:
/* .IP MAC_PARSE_ERROR
/* A syntax error was found in \fBpattern\fR, or some macro had
/* an unreasonable nesting depth.
/* .IP MAC_PARSE_UNDEF
/* A macro was expanded but not defined.
/* SEE ALSO
/* mac_parse(3) locate macro references in string.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <ctype.h>
#include <string.h>
/* Utility library. */
#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>
#include <mac_parse.h>
#include <mac_expand.h>
/*
* Little helper structure.
*/
typedef struct {
VSTRING *result; /* result buffer */
int flags; /* features */
const char *filter; /* character filter */
MAC_EXP_LOOKUP_FN lookup; /* lookup routine */
char *context; /* caller context */
int status; /* findings */
int level; /* nesting level */
} MAC_EXP;
/* mac_expand_callback - callback for mac_parse */
static int mac_expand_callback(int type, VSTRING *buf, char *ptr)
{
const char *myname = "mac_expand_callback";
MAC_EXP *mc = (MAC_EXP *) ptr;
int lookup_mode;
const char *text;
char *cp;
int ch;
int len;
/*
* Sanity check.
*/
if (mc->level++ > 100) {
msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
mc->status |= MAC_PARSE_ERROR;
}
if (mc->status & MAC_PARSE_ERROR)
return (mc->status);
/*
* $Name etc. reference.
*/
if (type == MAC_PARSE_VARNAME) {
/*
* Look for the ? or : delimiter. In case of a syntax error, return
* without doing damage, and issue a warning instead.
*/
for (cp = vstring_str(buf); /* void */ ; cp++) {
if ((ch = *cp) == 0) {
lookup_mode = MAC_EXP_MODE_USE;
break;
}
if (ch == '?' || ch == ':') {
*cp++ = 0;
lookup_mode = MAC_EXP_MODE_TEST;
break;
}
if (!ISALNUM(ch) && ch != '_') {
msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
mc->status |= MAC_PARSE_ERROR;
return (mc->status);
}
}
/*
* Look up the named parameter.
*/
text = mc->lookup(vstring_str(buf), lookup_mode, mc->context);
/*
* Perform the requested substitution.
*/
switch (ch) {
case '?':
if (text != 0)
mac_parse(cp, mac_expand_callback, (char *) mc);
break;
case ':':
if (text == 0)
mac_parse(cp, mac_expand_callback, (char *) mc);
break;
default:
if (text == 0) {
mc->status |= MAC_PARSE_UNDEF;
} else if (*text == 0) {
/* void */ ;
} else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
mac_parse(text, mac_expand_callback, (char *) mc);
} else {
len = VSTRING_LEN(mc->result);
vstring_strcat(mc->result, text);
if (mc->filter) {
cp = vstring_str(mc->result) + len;
while (*(cp += strspn(cp, mc->filter)))
*cp++ = '_';
}
}
break;
}
}
/*
* Literal text.
*/
else {
text = vstring_str(buf);
vstring_strcat(mc->result, text);
}
/*
* Give the poor tester a clue of what is going on.
*/
if (msg_verbose)
msg_info("%s: %s = %s", myname, vstring_str(buf),
text ? text : "(undef)");
mc->level--;
return (mc->status);
}
/* mac_expand - expand $name instances */
int mac_expand(VSTRING *result, const char *pattern, int flags,
const char *filter,
MAC_EXP_LOOKUP_FN lookup, char *context)
{
MAC_EXP mc;
int status;
/*
* Bundle up the request and do the substitutions.
*/
mc.result = result;
mc.flags = flags;
mc.filter = filter;
mc.lookup = lookup;
mc.context = context;
mc.status = 0;
mc.level = 0;
VSTRING_RESET(result);
status = mac_parse(pattern, mac_expand_callback, (char *) &mc);
VSTRING_TERMINATE(result);
return (status);
}
#ifdef TEST
/*
* This code certainly deserves a stand-alone test program.
*/
#include <stringops.h>
#include <htable.h>
#include <vstream.h>
#include <vstring_vstream.h>
static const char *lookup(const char *name, int unused_mode, char *context)
{
HTABLE *table = (HTABLE *) context;
return (htable_find(table, name));
}
int main(int unused_argc, char **unused_argv)
{
VSTRING *buf = vstring_alloc(100);
VSTRING *result = vstring_alloc(100);
char *cp;
char *name;
char *value;
HTABLE *table;
int stat;
while (!vstream_feof(VSTREAM_IN)) {
table = htable_create(0);
/*
* Read a block of definitions, terminated with an empty line.
*/
while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
vstream_printf("<< %s\n", vstring_str(buf));
vstream_fflush(VSTREAM_OUT);
if (VSTRING_LEN(buf) == 0)
break;
cp = vstring_str(buf);
name = mystrtok(&cp, " \t\r\n=");
value = mystrtok(&cp, " \t\r\n=");
htable_enter(table, name, value ? mystrdup(value) : 0);
}
/*
* Read a block of patterns, terminated with an empty line or EOF.
*/
while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
vstream_printf("<< %s\n", vstring_str(buf));
vstream_fflush(VSTREAM_OUT);
if (VSTRING_LEN(buf) == 0)
break;
cp = vstring_str(buf);
VSTRING_RESET(result);
stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
(char *) 0, lookup, (char *) table);
vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
vstream_fflush(VSTREAM_OUT);
}
htable_free(table, myfree);
vstream_printf("\n");
}
/*
* Clean up.
*/
vstring_free(buf);
vstring_free(result);
exit(0);
}
#endif