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