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