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