1 /*	$NetBSD: mac_expand.c,v 1.1.1.1 2009/06/23 10:09:00 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	mac_expand 3
6 /* SUMMARY
7 /*	attribute expansion
8 /* SYNOPSIS
9 /*	#include <mac_expand.h>
10 /*
11 /*	int	mac_expand(result, pattern, flags, filter, lookup, context)
12 /*	VSTRING *result;
13 /*	const char *pattern;
14 /*	int	flags;
15 /*	const char *filter;
16 /*	const char *lookup(const char *key, int mode, char *context)
17 /*	char	*context;
18 /* DESCRIPTION
19 /*	This module implements parameter-less macro expansions, both
20 /*	conditional and unconditional, and both recursive and non-recursive.
21 /*
22 /*	In this text, an attribute is considered "undefined" when its value
23 /*	is a null pointer.  Otherwise, the attribute is considered "defined"
24 /*	and is expected to have as value a null-terminated string.
25 /*
26 /*	The following expansions are implemented:
27 /* .IP "$name, ${name}, $(name)"
28 /*	Unconditional expansion. If the named attribute value is non-empty, the
29 /*	expansion is the value of the named attribute,  optionally subjected
30 /*	to further $name expansions.  Otherwise, the expansion is empty.
31 /* .IP "${name?text}, $(name?text)"
32 /*	Conditional expansion. If the named attribute value is non-empty, the
33 /*	expansion is the given text, subjected to another iteration of
34 /*	$name expansion.  Otherwise, the expansion is empty.
35 /* .IP "${name:text}, $(name:text)"
36 /*	Conditional expansion. If the attribute value is empty or undefined,
37 /*	the expansion is the given text, subjected to another iteration
38 /*	of $name expansion.  Otherwise, the expansion is empty.
39 /* .PP
40 /*	Arguments:
41 /* .IP result
42 /*	Storage for the result of expansion. The result is truncated
43 /*	upon entry.
44 /* .IP pattern
45 /*	The string to be expanded.
46 /* .IP flags
47 /*	Bit-wise OR of zero or more of the following:
48 /* .RS
49 /* .IP MAC_EXP_FLAG_RECURSE
50 /*	Expand macros in lookup results. This should never be done with
51 /*	data whose origin is untrusted.
52 /* .PP
53 /*	The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
54 /* .RE
55 /* .IP filter
56 /*	A null pointer, or a null-terminated array of characters that
57 /*	are allowed to appear in an expansion. Illegal characters are
58 /*	replaced by underscores.
59 /* .IP lookup
60 /*	The attribute lookup routine. Arguments are: the attribute name,
61 /*	MAC_EXP_MODE_TEST to test the existence of the named attribute
62 /*	or MAC_EXP_MODE_USE to use the value of the named attribute,
63 /*	and the caller context that was given to mac_expand(). A null
64 /*	result value means that the requested attribute was not defined.
65 /* .IP context
66 /*	Caller context that is passed on to the attribute lookup routine.
67 /* DIAGNOSTICS
68 /*	Fatal errors: out of memory.  Warnings: syntax errors, unreasonable
69 /*	macro nesting.
70 /*
71 /*	The result value is the binary OR of zero or more of the following:
72 /* .IP MAC_PARSE_ERROR
73 /*	A syntax error was found in \fBpattern\fR, or some macro had
74 /*	an unreasonable nesting depth.
75 /* .IP MAC_PARSE_UNDEF
76 /*	A macro was expanded but its value not defined.
77 /* SEE ALSO
78 /*	mac_parse(3) locate macro references in string.
79 /* LICENSE
80 /* .ad
81 /* .fi
82 /*	The Secure Mailer license must be distributed with this software.
83 /* AUTHOR(S)
84 /*	Wietse Venema
85 /*	IBM T.J. Watson Research
86 /*	P.O. Box 704
87 /*	Yorktown Heights, NY 10598, USA
88 /*--*/
89 
90 /* System library. */
91 
92 #include <sys_defs.h>
93 #include <ctype.h>
94 #include <string.h>
95 
96 /* Utility library. */
97 
98 #include <msg.h>
99 #include <vstring.h>
100 #include <mymalloc.h>
101 #include <mac_parse.h>
102 #include <mac_expand.h>
103 
104  /*
105   * Little helper structure.
106   */
107 typedef struct {
108     VSTRING *result;			/* result buffer */
109     int     flags;			/* features */
110     const char *filter;			/* character filter */
111     MAC_EXP_LOOKUP_FN lookup;		/* lookup routine */
112     char   *context;			/* caller context */
113     int     status;			/* findings */
114     int     level;			/* nesting level */
115 } MAC_EXP;
116 
117 /* mac_expand_callback - callback for mac_parse */
118 
119 static int mac_expand_callback(int type, VSTRING *buf, char *ptr)
120 {
121     MAC_EXP *mc = (MAC_EXP *) ptr;
122     int     lookup_mode;
123     const char *text;
124     char   *cp;
125     int     ch;
126     ssize_t len;
127 
128     /*
129      * Sanity check.
130      */
131     if (mc->level++ > 100) {
132 	msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf));
133 	mc->status |= MAC_PARSE_ERROR;
134     }
135     if (mc->status & MAC_PARSE_ERROR)
136 	return (mc->status);
137 
138     /*
139      * $Name etc. reference.
140      *
141      * In order to support expansion of lookup results, we must save the lookup
142      * result. We use the input buffer since it will not be needed anymore.
143      */
144     if (type == MAC_PARSE_EXPR) {
145 
146 	/*
147 	 * Look for the ? or : delimiter. In case of a syntax error, return
148 	 * without doing damage, and issue a warning instead.
149 	 */
150 	for (cp = vstring_str(buf); /* void */ ; cp++) {
151 	    if ((ch = *cp) == 0) {
152 		lookup_mode = MAC_EXP_MODE_USE;
153 		break;
154 	    }
155 	    if (ch == '?' || ch == ':') {
156 		*cp++ = 0;
157 		lookup_mode = MAC_EXP_MODE_TEST;
158 		break;
159 	    }
160 	    if (!ISALNUM(ch) && ch != '_') {
161 		msg_warn("macro name syntax error: \"%s\"", vstring_str(buf));
162 		mc->status |= MAC_PARSE_ERROR;
163 		return (mc->status);
164 	    }
165 	}
166 
167 	/*
168 	 * Look up the named parameter.
169 	 */
170 	text = mc->lookup(vstring_str(buf), lookup_mode, mc->context);
171 
172 	/*
173 	 * Perform the requested substitution.
174 	 */
175 	switch (ch) {
176 	case '?':
177 	    if (text != 0 && *text != 0)
178 		mac_parse(cp, mac_expand_callback, (char *) mc);
179 	    break;
180 	case ':':
181 	    if (text == 0 || *text == 0)
182 		mac_parse(cp, mac_expand_callback, (char *) mc);
183 	    break;
184 	default:
185 	    if (text == 0) {
186 		mc->status |= MAC_PARSE_UNDEF;
187 	    } else if (*text == 0) {
188 		 /* void */ ;
189 	    } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
190 		vstring_strcpy(buf, text);
191 		mac_parse(vstring_str(buf), mac_expand_callback, (char *) mc);
192 	    } else {
193 		len = VSTRING_LEN(mc->result);
194 		vstring_strcat(mc->result, text);
195 		if (mc->filter) {
196 		    cp = vstring_str(mc->result) + len;
197 		    while (*(cp += strspn(cp, mc->filter)))
198 			*cp++ = '_';
199 		}
200 	    }
201 	    break;
202 	}
203     }
204 
205     /*
206      * Literal text.
207      */
208     else {
209 	vstring_strcat(mc->result, vstring_str(buf));
210     }
211 
212     mc->level--;
213 
214     return (mc->status);
215 }
216 
217 /* mac_expand - expand $name instances */
218 
219 int     mac_expand(VSTRING *result, const char *pattern, int flags,
220 		           const char *filter,
221 		           MAC_EXP_LOOKUP_FN lookup, char *context)
222 {
223     MAC_EXP mc;
224     int     status;
225 
226     /*
227      * Bundle up the request and do the substitutions.
228      */
229     mc.result = result;
230     mc.flags = flags;
231     mc.filter = filter;
232     mc.lookup = lookup;
233     mc.context = context;
234     mc.status = 0;
235     mc.level = 0;
236     VSTRING_RESET(result);
237     status = mac_parse(pattern, mac_expand_callback, (char *) &mc);
238     VSTRING_TERMINATE(result);
239 
240     return (status);
241 }
242 
243 #ifdef TEST
244 
245  /*
246   * This code certainly deserves a stand-alone test program.
247   */
248 #include <stdlib.h>
249 #include <stringops.h>
250 #include <htable.h>
251 #include <vstream.h>
252 #include <vstring_vstream.h>
253 
254 static const char *lookup(const char *name, int unused_mode, char *context)
255 {
256     HTABLE *table = (HTABLE *) context;
257 
258     return (htable_find(table, name));
259 }
260 
261 int     main(int unused_argc, char **unused_argv)
262 {
263     VSTRING *buf = vstring_alloc(100);
264     VSTRING *result = vstring_alloc(100);
265     char   *cp;
266     char   *name;
267     char   *value;
268     HTABLE *table;
269     int     stat;
270 
271     while (!vstream_feof(VSTREAM_IN)) {
272 
273 	table = htable_create(0);
274 
275 	/*
276 	 * Read a block of definitions, terminated with an empty line.
277 	 */
278 	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
279 	    vstream_printf("<< %s\n", vstring_str(buf));
280 	    vstream_fflush(VSTREAM_OUT);
281 	    if (VSTRING_LEN(buf) == 0)
282 		break;
283 	    cp = vstring_str(buf);
284 	    name = mystrtok(&cp, " \t\r\n=");
285 	    value = mystrtok(&cp, " \t\r\n=");
286 	    htable_enter(table, name, value ? mystrdup(value) : 0);
287 	}
288 
289 	/*
290 	 * Read a block of patterns, terminated with an empty line or EOF.
291 	 */
292 	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
293 	    vstream_printf("<< %s\n", vstring_str(buf));
294 	    vstream_fflush(VSTREAM_OUT);
295 	    if (VSTRING_LEN(buf) == 0)
296 		break;
297 	    cp = vstring_str(buf);
298 	    VSTRING_RESET(result);
299 	    stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
300 			      (char *) 0, lookup, (char *) table);
301 	    vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
302 	    vstream_fflush(VSTREAM_OUT);
303 	}
304 	htable_free(table, myfree);
305 	vstream_printf("\n");
306     }
307 
308     /*
309      * Clean up.
310      */
311     vstring_free(buf);
312     vstring_free(result);
313     exit(0);
314 }
315 
316 #endif
317