/src/postfix/postfix/src/util/mac_expand.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*++ |
2 | | /* NAME |
3 | | /* mac_expand 3 |
4 | | /* SUMMARY |
5 | | /* attribute expansion |
6 | | /* SYNOPSIS |
7 | | /* #include <mac_expand.h> |
8 | | /* |
9 | | /* int mac_expand(result, pattern, flags, filter, lookup, context) |
10 | | /* VSTRING *result; |
11 | | /* const char *pattern; |
12 | | /* int flags; |
13 | | /* const char *filter; |
14 | | /* const char *lookup(const char *key, int mode, void *context) |
15 | | /* void *context; |
16 | | /* AUXILIARY FUNCTIONS |
17 | | /* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) ( |
18 | | /* const char *left, |
19 | | /* int tok_val, |
20 | | /* const char *rite) |
21 | | /* |
22 | | /* void mac_expand_add_relop( |
23 | | /* int *tok_list, |
24 | | /* const char *suffix, |
25 | | /* MAC_EXPAND_RELOP_FN relop_eval) |
26 | | /* |
27 | | /* MAC_EXP_OP_RES mac_exp_op_res_bool[2]; |
28 | | /* DESCRIPTION |
29 | | /* This module implements parameter-less named attribute |
30 | | /* expansions, both conditional and unconditional. As of Postfix |
31 | | /* 3.0 this code supports relational expression evaluation. |
32 | | /* |
33 | | /* In this text, an attribute is considered "undefined" when its value |
34 | | /* is a null pointer. Otherwise, the attribute is considered "defined" |
35 | | /* and is expected to have as value a null-terminated string. |
36 | | /* |
37 | | /* In the text below, the legacy form $(...) is equivalent to |
38 | | /* ${...}. The legacy form $(...) may eventually disappear |
39 | | /* from documentation. In the text below, the name in $name |
40 | | /* and ${name...} must contain only characters from the set |
41 | | /* [a-zA-Z0-9_]. |
42 | | /* |
43 | | /* The following substitutions are supported: |
44 | | /* .IP "$name, ${name}" |
45 | | /* Unconditional attribute-based substitution. The result is the |
46 | | /* named attribute value (empty if the attribute is not defined) |
47 | | /* after optional further named attribute substitution. |
48 | | /* .IP "${name?text}, ${name?{text}}" |
49 | | /* Conditional attribute-based substitution. If the named attribute |
50 | | /* value is non-empty, the result is the given text, after |
51 | | /* named attribute expansion and relational expression evaluation. |
52 | | /* Otherwise, the result is empty. Whitespace before or after |
53 | | /* {text} is ignored. |
54 | | /* .IP "${name:text}, ${name:{text}}" |
55 | | /* Conditional attribute-based substitution. If the attribute |
56 | | /* value is empty or undefined, the expansion is the given |
57 | | /* text, after named attribute expansion and relational expression |
58 | | /* evaluation. Otherwise, the result is empty. Whitespace |
59 | | /* before or after {text} is ignored. |
60 | | /* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}" |
61 | | /* Conditional attribute-based substitution. If the named attribute |
62 | | /* value is non-empty, the result is text1. Otherwise, the |
63 | | /* result is text2. In both cases the result is subject to |
64 | | /* named attribute expansion and relational expression evaluation. |
65 | | /* Whitespace before or after {text1} or {text2} is ignored. |
66 | | /* .IP "${{text1} == ${text2} ? {text3} : {text4}}" |
67 | | /* Relational expression-based substitution. First, the content |
68 | | /* of {text1} and ${text2} is subjected to named attribute and |
69 | | /* relational expression-based substitution. Next, the relational |
70 | | /* expression is evaluated. If it evaluates to "true", the |
71 | | /* result is the content of {text3}, otherwise it is the content |
72 | | /* of {text4}, after named attribute and relational expression-based |
73 | | /* substitution. In addition to ==, this supports !=, <, <=, |
74 | | /* >=, and >. Comparisons are numerical when both operands are |
75 | | /* all digits, otherwise the comparisons are lexicographical. |
76 | | /* |
77 | | /* Arguments: |
78 | | /* .IP result |
79 | | /* Storage for the result of expansion. By default, the result |
80 | | /* is truncated upon entry. |
81 | | /* .IP pattern |
82 | | /* The string to be expanded. |
83 | | /* .IP flags |
84 | | /* Bit-wise OR of zero or more of the following: |
85 | | /* .RS |
86 | | /* .IP MAC_EXP_FLAG_RECURSE |
87 | | /* Expand attributes in lookup results. This should never be |
88 | | /* done with data whose origin is untrusted. |
89 | | /* .IP MAC_EXP_FLAG_APPEND |
90 | | /* Append text to the result buffer without truncating it. |
91 | | /* .IP MAC_EXP_FLAG_SCAN |
92 | | /* Scan the input for named attributes, including named |
93 | | /* attributes in all conditional result values. Do not expand |
94 | | /* named attributes, and do not truncate or write to the result |
95 | | /* argument. |
96 | | /* .IP MAC_EXP_FLAG_PRINTABLE |
97 | | /* Use the printable() function instead of \fIfilter\fR. |
98 | | /* .PP |
99 | | /* The constant MAC_EXP_FLAG_NONE specifies a manifest null value. |
100 | | /* .RE |
101 | | /* .IP filter |
102 | | /* A null pointer, or a null-terminated array of characters that |
103 | | /* are allowed to appear in an expansion. Illegal characters are |
104 | | /* replaced by underscores. |
105 | | /* .IP lookup |
106 | | /* The attribute lookup routine. Arguments are: the attribute name, |
107 | | /* MAC_EXP_MODE_TEST to test the existence of the named attribute |
108 | | /* or MAC_EXP_MODE_USE to use the value of the named attribute, |
109 | | /* and the caller context that was given to mac_expand(). A null |
110 | | /* result value means that the requested attribute was not defined. |
111 | | /* .IP context |
112 | | /* Caller context that is passed on to the attribute lookup routine. |
113 | | /* .PP |
114 | | /* mac_expand_add_relop() registers a function that implements |
115 | | /* support for custom relational operators. Custom operator names |
116 | | /* such as "==xxx" have two parts: a prefix that is identical to |
117 | | /* a built-in operator such as "==", and an application-specified |
118 | | /* suffix such as "xxx". |
119 | | /* |
120 | | /* Arguments: |
121 | | /* .IP tok_list |
122 | | /* A null-terminated list of MAC_EXP_OP_TOK_* values that support |
123 | | /* the custom operator suffix. |
124 | | /* .IP suffix |
125 | | /* A null-terminated alphanumeric string that specifies the custom |
126 | | /* operator suffix. |
127 | | /* .IP relop_eval |
128 | | /* A function that compares two strings according to the |
129 | | /* MAC_EXP_OP_TOK_* value specified with the tok_val argument, |
130 | | /* and that returns non-zero if the custom operator evaluates to |
131 | | /* true, zero otherwise. |
132 | | /* |
133 | | /* mac_exp_op_res_bool provides an array that converts a boolean |
134 | | /* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or |
135 | | /* MAX_EXP_OP_RES_FALSE value. |
136 | | /* DIAGNOSTICS |
137 | | /* Fatal errors: out of memory. Warnings: syntax errors, unreasonable |
138 | | /* recursion depth. |
139 | | /* |
140 | | /* The result value is the binary OR of zero or more of the following: |
141 | | /* .IP MAC_PARSE_ERROR |
142 | | /* A syntax error was found in \fBpattern\fR, or some attribute had |
143 | | /* an unreasonable nesting depth. |
144 | | /* .IP MAC_PARSE_UNDEF |
145 | | /* An attribute was expanded but its value was not defined. |
146 | | /* SEE ALSO |
147 | | /* mac_parse(3) locate macro references in string. |
148 | | /* LICENSE |
149 | | /* .ad |
150 | | /* .fi |
151 | | /* The Secure Mailer license must be distributed with this software. |
152 | | /* AUTHOR(S) |
153 | | /* Wietse Venema |
154 | | /* IBM T.J. Watson Research |
155 | | /* P.O. Box 704 |
156 | | /* Yorktown Heights, NY 10598, USA |
157 | | /* |
158 | | /* Wietse Venema |
159 | | /* Google, Inc. |
160 | | /* 111 8th Avenue |
161 | | /* New York, NY 10011, USA |
162 | | /*--*/ |
163 | | |
164 | | /* System library. */ |
165 | | |
166 | | #include <sys_defs.h> |
167 | | #include <ctype.h> |
168 | | #include <errno.h> |
169 | | #include <string.h> |
170 | | #include <stdlib.h> |
171 | | |
172 | | /* Utility library. */ |
173 | | |
174 | | #include <msg.h> |
175 | | #include <htable.h> |
176 | | #include <vstring.h> |
177 | | #include <mymalloc.h> |
178 | | #include <stringops.h> |
179 | | #include <name_code.h> |
180 | | #include <sane_strtol.h> |
181 | | #include <mac_parse.h> |
182 | | #include <mac_expand.h> |
183 | | |
184 | | /* |
185 | | * Simplifies the return of common relational operator results. |
186 | | */ |
187 | | MAC_EXP_OP_RES mac_exp_op_res_bool[2] = { |
188 | | MAC_EXP_OP_RES_FALSE, |
189 | | MAC_EXP_OP_RES_TRUE |
190 | | }; |
191 | | |
192 | | /* |
193 | | * Little helper structure. |
194 | | */ |
195 | | typedef struct { |
196 | | VSTRING *result; /* result buffer */ |
197 | | int flags; /* features */ |
198 | | const char *filter; /* character filter */ |
199 | | MAC_EXP_LOOKUP_FN lookup; /* lookup routine */ |
200 | | void *context; /* caller context */ |
201 | | int status; /* findings */ |
202 | | int level; /* nesting level */ |
203 | | } MAC_EXP_CONTEXT; |
204 | | |
205 | | /* |
206 | | * Support for relational expressions. |
207 | | * |
208 | | * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the |
209 | | * result respectively when the parameter value is non-empty, or when the |
210 | | * parameter value is undefined or empty; support for the ternary ?: |
211 | | * operator was anticipated, but not implemented for 10 years. |
212 | | * |
213 | | * To make ${relational-expr?result} and ${relational-expr:result} work as |
214 | | * expected without breaking the way that ? and : work, relational |
215 | | * expressions evaluate to a non-empty or empty value. It does not matter |
216 | | * what non-empty value we use for TRUE. However we must not use the |
217 | | * undefined (null pointer) value for FALSE - that would raise the |
218 | | * MAC_PARSE_UNDEF flag. |
219 | | * |
220 | | * The value of a relational expression can be exposed with ${relational-expr}, |
221 | | * i.e. a relational expression that is not followed by ? or : conditional |
222 | | * expansion. |
223 | | */ |
224 | 0 | #define MAC_EXP_BVAL_TRUE "true" |
225 | 0 | #define MAC_EXP_BVAL_FALSE "" |
226 | | |
227 | | /* |
228 | | * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header |
229 | | * file. |
230 | | */ |
231 | | #define MAC_EXP_OP_STR_EQ "==" |
232 | | #define MAC_EXP_OP_STR_NE "!=" |
233 | | #define MAC_EXP_OP_STR_LT "<" |
234 | | #define MAC_EXP_OP_STR_LE "<=" |
235 | | #define MAC_EXP_OP_STR_GE ">=" |
236 | | #define MAC_EXP_OP_STR_GT ">" |
237 | | #define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \ |
238 | | "\" or \"" MAC_EXP_OP_STR_NE "\"" \ |
239 | | "\" or \"" MAC_EXP_OP_STR_LT "\"" \ |
240 | | "\" or \"" MAC_EXP_OP_STR_LE "\"" \ |
241 | | "\" or \"" MAC_EXP_OP_STR_GE "\"" \ |
242 | | "\" or \"" MAC_EXP_OP_STR_GT "\"" |
243 | | |
244 | | static const NAME_CODE mac_exp_op_table[] = |
245 | | { |
246 | | MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ, |
247 | | MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE, |
248 | | MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT, |
249 | | MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE, |
250 | | MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE, |
251 | | MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT, |
252 | | 0, MAC_EXP_OP_TOK_NONE, |
253 | | }; |
254 | | |
255 | | /* |
256 | | * The whitespace separator set. |
257 | | */ |
258 | 0 | #define MAC_EXP_WHITESPACE CHARS_SPACE |
259 | | |
260 | | /* |
261 | | * Support for operator extensions. |
262 | | */ |
263 | | static HTABLE *mac_exp_ext_table; |
264 | | static VSTRING *mac_exp_ext_key; |
265 | | |
266 | | /* |
267 | | * SLMs. |
268 | | */ |
269 | 0 | #define STR(x) vstring_str(x) |
270 | | |
271 | | /* atol_or_die - convert or die */ |
272 | | |
273 | | static long atol_or_die(const char *strval) |
274 | 0 | { |
275 | 0 | long result; |
276 | 0 | char *remainder; |
277 | |
|
278 | 0 | result = sane_strtol(strval, &remainder, 10); |
279 | 0 | if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE) |
280 | 0 | msg_fatal("mac_exp_eval: bad conversion: %s", strval); |
281 | 0 | return (result); |
282 | 0 | } |
283 | | |
284 | | /* mac_exp_eval - evaluate binary expression */ |
285 | | |
286 | | static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val, |
287 | | const char *rite) |
288 | 0 | { |
289 | 0 | static const char myname[] = "mac_exp_eval"; |
290 | 0 | long delta; |
291 | | |
292 | | /* |
293 | | * Numerical or string comparison. |
294 | | */ |
295 | 0 | if (alldig(left) && alldig(rite)) { |
296 | 0 | delta = atol_or_die(left) - atol_or_die(rite); |
297 | 0 | } else { |
298 | 0 | delta = strcmp(left, rite); |
299 | 0 | } |
300 | 0 | switch (tok_val) { |
301 | 0 | case MAC_EXP_OP_TOK_EQ: |
302 | 0 | return (mac_exp_op_res_bool[delta == 0]); |
303 | 0 | case MAC_EXP_OP_TOK_NE: |
304 | 0 | return (mac_exp_op_res_bool[delta != 0]); |
305 | 0 | case MAC_EXP_OP_TOK_LT: |
306 | 0 | return (mac_exp_op_res_bool[delta < 0]); |
307 | 0 | case MAC_EXP_OP_TOK_LE: |
308 | 0 | return (mac_exp_op_res_bool[delta <= 0]); |
309 | 0 | case MAC_EXP_OP_TOK_GE: |
310 | 0 | return (mac_exp_op_res_bool[delta >= 0]); |
311 | 0 | case MAC_EXP_OP_TOK_GT: |
312 | 0 | return (mac_exp_op_res_bool[delta > 0]); |
313 | 0 | default: |
314 | 0 | msg_panic("%s: unknown operator: %d", |
315 | 0 | myname, tok_val); |
316 | 0 | } |
317 | 0 | } |
318 | | |
319 | | /* mac_exp_parse_error - report parse error, set error flag, return status */ |
320 | | |
321 | | static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc, |
322 | | const char *fmt,...) |
323 | 0 | { |
324 | 0 | va_list ap; |
325 | |
|
326 | 0 | va_start(ap, fmt); |
327 | 0 | vmsg_warn(fmt, ap); |
328 | 0 | va_end(ap); |
329 | 0 | return (mc->status |= MAC_PARSE_ERROR); |
330 | 0 | }; |
331 | | |
332 | | /* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */ |
333 | | |
334 | 0 | #define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \ |
335 | 0 | return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \ |
336 | 0 | } while (0) |
337 | | |
338 | | /* |
339 | | * Postfix 3.0 introduces support for {text} operands. Only with these do we |
340 | | * support the ternary ?: operator and relational operators. |
341 | | * |
342 | | * We cannot support operators in random text, because that would break Postfix |
343 | | * 2.11 compatibility. For example, with the expression "${name?value}", the |
344 | | * value is random text that may contain ':', '?', '{' and '}' characters. |
345 | | * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates |
346 | | * to "?foo:{b}ar" or empty. There are explicit tests in this directory and |
347 | | * the postconf directory to ensure that Postfix 2.11 compatibility is |
348 | | * maintained. |
349 | | * |
350 | | * Ideally, future Postfix configurations enclose random text operands inside |
351 | | * {} braces. These allow whitespace around operands, which improves |
352 | | * readability. |
353 | | */ |
354 | | |
355 | | /* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */ |
356 | | |
357 | | #define MAC_EXP_FIND_LEFT_CURLY(len, cp) \ |
358 | 0 | ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \ |
359 | 0 | (cp += len) : 0) |
360 | | |
361 | | /* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */ |
362 | | |
363 | | static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp) |
364 | 0 | { |
365 | 0 | char *payload; |
366 | 0 | char *cp; |
367 | 0 | int level; |
368 | 0 | int ch; |
369 | | |
370 | | /* |
371 | | * Extract the payload and balance the {}. The caller is expected to skip |
372 | | * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY(). |
373 | | */ |
374 | 0 | for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) { |
375 | 0 | if ((ch = *cp) == 0) { |
376 | 0 | mac_exp_parse_error(mc, "unbalanced {} in attribute expression: " |
377 | 0 | "\"%s\"", |
378 | 0 | *bp); |
379 | 0 | return (0); |
380 | 0 | } else if (ch == '{') { |
381 | 0 | level++; |
382 | 0 | } else if (ch == '}') { |
383 | 0 | if (--level <= 0) |
384 | 0 | break; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | *cp++ = 0; |
388 | | |
389 | | /* |
390 | | * Skip trailing whitespace after }. |
391 | | */ |
392 | 0 | *bp = cp + strspn(cp, MAC_EXP_WHITESPACE); |
393 | 0 | return (payload); |
394 | 0 | } |
395 | | |
396 | | /* mac_exp_parse_relational - parse relational expression, advance read ptr */ |
397 | | |
398 | | static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup, |
399 | | char **bp) |
400 | 0 | { |
401 | 0 | char *cp = *bp; |
402 | 0 | VSTRING *left_op_buf; |
403 | 0 | VSTRING *rite_op_buf; |
404 | 0 | const char *left_op_strval; |
405 | 0 | const char *rite_op_strval; |
406 | 0 | char *op_pos; |
407 | 0 | char *op_strval; |
408 | 0 | size_t op_len; |
409 | 0 | int op_tokval; |
410 | 0 | int op_result; |
411 | 0 | size_t tmp_len; |
412 | 0 | char *type_pos; |
413 | 0 | size_t type_len; |
414 | 0 | MAC_EXPAND_RELOP_FN relop_eval; |
415 | | |
416 | | /* |
417 | | * Left operand. The caller is expected to skip leading whitespace before |
418 | | * the {. See MAC_EXP_FIND_LEFT_CURLY(). |
419 | | */ |
420 | 0 | if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) |
421 | 0 | return (mc->status); |
422 | | |
423 | | /* |
424 | | * Operator. Todo: regexp operator. |
425 | | */ |
426 | 0 | op_pos = cp; |
427 | 0 | op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */ |
428 | 0 | op_strval = mystrndup(cp, op_len); |
429 | 0 | op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval); |
430 | 0 | myfree(op_strval); |
431 | 0 | if (op_tokval == MAC_EXP_OP_TOK_NONE) |
432 | 0 | MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"", |
433 | 0 | MAC_EXP_OP_STR_ANY, left_op_strval, cp); |
434 | 0 | cp += op_len; |
435 | | |
436 | | /* |
437 | | * Custom operator suffix. |
438 | | */ |
439 | 0 | if (mac_exp_ext_table && ISALNUM(*cp)) { |
440 | 0 | type_pos = cp; |
441 | 0 | for (type_len = 1; ISALNUM(cp[type_len]); type_len++) |
442 | 0 | /* void */ ; |
443 | 0 | cp += type_len; |
444 | 0 | vstring_sprintf(mac_exp_ext_key, "%.*s", |
445 | 0 | (int) (op_len + type_len), op_pos); |
446 | 0 | if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table, |
447 | 0 | STR(mac_exp_ext_key))) == 0) |
448 | 0 | MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"", |
449 | 0 | (int) op_len, op_pos, (int) type_len, type_pos); |
450 | 0 | } else { |
451 | 0 | relop_eval = mac_exp_eval; |
452 | 0 | } |
453 | | |
454 | | /* |
455 | | * Right operand. Todo: syntax may depend on operator. |
456 | | */ |
457 | 0 | if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0) |
458 | 0 | MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: " |
459 | 0 | "\"...{%s} %.*s>>>%.20s\"", |
460 | 0 | left_op_strval, (int) op_len, op_pos, cp); |
461 | 0 | if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) |
462 | 0 | return (mc->status); |
463 | | |
464 | | /* |
465 | | * Evaluate the relational expression. Todo: regexp support. |
466 | | */ |
467 | 0 | mc->status |= |
468 | 0 | mac_expand(left_op_buf = vstring_alloc(100), left_op_strval, |
469 | 0 | mc->flags, mc->filter, mc->lookup, mc->context); |
470 | 0 | mc->status |= |
471 | 0 | mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval, |
472 | 0 | mc->flags, mc->filter, mc->lookup, mc->context); |
473 | 0 | if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0 |
474 | 0 | && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval, |
475 | 0 | vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR) |
476 | 0 | mc->status |= MAC_PARSE_ERROR; |
477 | 0 | vstring_free(left_op_buf); |
478 | 0 | vstring_free(rite_op_buf); |
479 | 0 | if (mc->status & MAC_PARSE_ERROR) |
480 | 0 | return (mc->status); |
481 | | |
482 | | /* |
483 | | * Here, we fake up a non-empty or empty parameter value lookup result, |
484 | | * for compatibility with the historical code that looks named parameter |
485 | | * values. |
486 | | */ |
487 | 0 | if (mc->flags & MAC_EXP_FLAG_SCAN) { |
488 | 0 | *lookup = 0; |
489 | 0 | } else { |
490 | 0 | switch (op_result) { |
491 | 0 | case MAC_EXP_OP_RES_TRUE: |
492 | 0 | *lookup = MAC_EXP_BVAL_TRUE; |
493 | 0 | break; |
494 | 0 | case MAC_EXP_OP_RES_FALSE: |
495 | 0 | *lookup = MAC_EXP_BVAL_FALSE; |
496 | 0 | break; |
497 | 0 | default: |
498 | 0 | msg_panic("mac_expand: unexpected operator result: %d", op_result); |
499 | 0 | } |
500 | 0 | } |
501 | 0 | *bp = cp; |
502 | 0 | return (0); |
503 | 0 | } |
504 | | |
505 | | /* mac_expand_add_relop - register operator extensions */ |
506 | | |
507 | | void mac_expand_add_relop(int *tok_list, const char *suffix, |
508 | | MAC_EXPAND_RELOP_FN relop_eval) |
509 | 0 | { |
510 | 0 | const char myname[] = "mac_expand_add_relop"; |
511 | 0 | const char *tok_name; |
512 | 0 | int *tp; |
513 | | |
514 | | /* |
515 | | * Sanity checks. |
516 | | */ |
517 | 0 | if (!allalnum(suffix)) |
518 | 0 | msg_panic("%s: bad operator suffix: %s", myname, suffix); |
519 | | |
520 | | /* |
521 | | * One-time initialization. |
522 | | */ |
523 | 0 | if (mac_exp_ext_table == 0) { |
524 | 0 | mac_exp_ext_table = htable_create(10); |
525 | 0 | mac_exp_ext_key = vstring_alloc(10); |
526 | 0 | } |
527 | 0 | for (tp = tok_list; *tp; tp++) { |
528 | 0 | if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0) |
529 | 0 | msg_panic("%s: unknown token code: %d", myname, *tp); |
530 | 0 | vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix); |
531 | 0 | if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0) |
532 | 0 | msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key)); |
533 | 0 | (void) htable_enter(mac_exp_ext_table, |
534 | 0 | STR(mac_exp_ext_key), (void *) relop_eval); |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | | /* mac_expand_callback - callback for mac_parse */ |
539 | | |
540 | | static int mac_expand_callback(int type, VSTRING *buf, void *ptr) |
541 | 0 | { |
542 | 0 | static const char myname[] = "mac_expand_callback"; |
543 | 0 | MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr; |
544 | 0 | int lookup_mode; |
545 | 0 | const char *lookup; |
546 | 0 | char *cp; |
547 | 0 | int ch; |
548 | 0 | ssize_t res_len; |
549 | 0 | ssize_t tmp_len; |
550 | 0 | const char *res_iftrue; |
551 | 0 | const char *res_iffalse; |
552 | | |
553 | | /* |
554 | | * Sanity check. |
555 | | */ |
556 | 0 | if (mc->level++ > 100) |
557 | 0 | mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"", |
558 | 0 | vstring_str(buf)); |
559 | 0 | if (mc->status & MAC_PARSE_ERROR) |
560 | 0 | return (mc->status); |
561 | | |
562 | | /* |
563 | | * Named parameter or relational expression. In case of a syntax error, |
564 | | * return without doing damage, and issue a warning instead. |
565 | | */ |
566 | 0 | if (type == MAC_PARSE_EXPR) { |
567 | |
|
568 | 0 | cp = vstring_str(buf); |
569 | | |
570 | | /* |
571 | | * Relational expression. If recursion is disabled, perform only one |
572 | | * level of $name expansion. |
573 | | */ |
574 | 0 | if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { |
575 | 0 | if (mac_exp_parse_relational(mc, &lookup, &cp) != 0) |
576 | 0 | return (mc->status); |
577 | | |
578 | | /* |
579 | | * Look for the ? or : operator. |
580 | | */ |
581 | 0 | if ((ch = *cp) != 0) { |
582 | 0 | if (ch != '?' && ch != ':') |
583 | 0 | MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: " |
584 | 0 | "\"...}>>>%.20s\"", cp); |
585 | 0 | cp++; |
586 | 0 | } |
587 | 0 | } |
588 | | |
589 | | /* |
590 | | * Named parameter. |
591 | | */ |
592 | 0 | else { |
593 | 0 | char *start; |
594 | | |
595 | | /* |
596 | | * Look for the ? or : operator. In case of a syntax error, |
597 | | * return without doing damage, and issue a warning instead. |
598 | | */ |
599 | 0 | start = (cp += strspn(cp, MAC_EXP_WHITESPACE)); |
600 | 0 | for ( /* void */ ; /* void */ ; cp++) { |
601 | 0 | if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) { |
602 | 0 | *cp = 0; |
603 | 0 | lookup_mode = MAC_EXP_MODE_USE; |
604 | 0 | break; |
605 | 0 | } |
606 | 0 | if (ch == '?' || ch == ':') { |
607 | 0 | *cp++ = 0; |
608 | 0 | cp += tmp_len; |
609 | 0 | lookup_mode = MAC_EXP_MODE_TEST; |
610 | 0 | break; |
611 | 0 | } |
612 | 0 | ch = *cp; |
613 | 0 | if (!ISALNUM(ch) && ch != '_') { |
614 | 0 | MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: " |
615 | 0 | "\"...%.*s>>>%.20s\"", |
616 | 0 | (int) (cp - vstring_str(buf)), |
617 | 0 | vstring_str(buf), cp); |
618 | 0 | } |
619 | 0 | } |
620 | | |
621 | | /* |
622 | | * Look up the named parameter. Todo: allow the lookup function |
623 | | * to specify if the result is safe for $name expansion. |
624 | | */ |
625 | 0 | lookup = mc->lookup(start, lookup_mode, mc->context); |
626 | 0 | } |
627 | | |
628 | | /* |
629 | | * Return the requested result. After parsing the result operand |
630 | | * following ?, we fall through to parse the result operand following |
631 | | * :. This is necessary with the ternary ?: operator: first, with |
632 | | * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(), |
633 | | * and second, to find garbage after any result operand. Without |
634 | | * MAC_EXP_FLAG_SCAN the content of only one of the ?: result |
635 | | * operands will be parsed with mac_parse(); syntax errors in the |
636 | | * other operand will be missed. |
637 | | */ |
638 | 0 | switch (ch) { |
639 | 0 | case '?': |
640 | 0 | if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { |
641 | 0 | if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0) |
642 | 0 | return (mc->status); |
643 | 0 | } else { |
644 | 0 | res_iftrue = cp; |
645 | 0 | cp = ""; /* no left-over text */ |
646 | 0 | } |
647 | 0 | if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) |
648 | 0 | mc->status |= mac_parse(res_iftrue, mac_expand_callback, |
649 | 0 | (void *) mc); |
650 | 0 | if (*cp == 0) /* end of input, OK */ |
651 | 0 | break; |
652 | 0 | if (*cp != ':') /* garbage */ |
653 | 0 | MAC_EXP_ERR_RETURN(mc, "\":\" expected at: " |
654 | 0 | "\"...%s}>>>%.20s\"", res_iftrue, cp); |
655 | 0 | cp += 1; |
656 | | /* FALLTHROUGH: do not remove, see comment above. */ |
657 | 0 | case ':': |
658 | 0 | if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { |
659 | 0 | if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0) |
660 | 0 | return (mc->status); |
661 | 0 | } else { |
662 | 0 | res_iffalse = cp; |
663 | 0 | cp = ""; /* no left-over text */ |
664 | 0 | } |
665 | 0 | if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) |
666 | 0 | mc->status |= mac_parse(res_iffalse, mac_expand_callback, |
667 | 0 | (void *) mc); |
668 | 0 | if (*cp != 0) /* garbage */ |
669 | 0 | MAC_EXP_ERR_RETURN(mc, "unexpected input at: " |
670 | 0 | "\"...%s}>>>%.20s\"", res_iffalse, cp); |
671 | 0 | break; |
672 | 0 | case 0: |
673 | 0 | if (lookup == 0) { |
674 | 0 | mc->status |= MAC_PARSE_UNDEF; |
675 | 0 | } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { |
676 | 0 | /* void */ ; |
677 | 0 | } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { |
678 | 0 | vstring_strcpy(buf, lookup); |
679 | 0 | mc->status |= mac_parse(vstring_str(buf), mac_expand_callback, |
680 | 0 | (void *) mc); |
681 | 0 | } else { |
682 | 0 | res_len = VSTRING_LEN(mc->result); |
683 | 0 | vstring_strcat(mc->result, lookup); |
684 | 0 | if (mc->flags & MAC_EXP_FLAG_PRINTABLE) { |
685 | 0 | printable(vstring_str(mc->result) + res_len, '_'); |
686 | 0 | } else if (mc->filter) { |
687 | 0 | cp = vstring_str(mc->result) + res_len; |
688 | 0 | while (*(cp += strspn(cp, mc->filter))) |
689 | 0 | *cp++ = '_'; |
690 | 0 | } |
691 | 0 | } |
692 | 0 | break; |
693 | 0 | default: |
694 | 0 | msg_panic("%s: unknown operator code %d", myname, ch); |
695 | 0 | } |
696 | 0 | } |
697 | | |
698 | | /* |
699 | | * Literal text. |
700 | | */ |
701 | 0 | else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { |
702 | 0 | vstring_strcat(mc->result, vstring_str(buf)); |
703 | 0 | } |
704 | 0 | mc->level--; |
705 | |
|
706 | 0 | return (mc->status); |
707 | 0 | } |
708 | | |
709 | | /* mac_expand - expand $name instances */ |
710 | | |
711 | | int mac_expand(VSTRING *result, const char *pattern, int flags, |
712 | | const char *filter, |
713 | | MAC_EXP_LOOKUP_FN lookup, void *context) |
714 | 0 | { |
715 | 0 | MAC_EXP_CONTEXT mc; |
716 | 0 | int status; |
717 | | |
718 | | /* |
719 | | * Bundle up the request and do the substitutions. |
720 | | */ |
721 | 0 | mc.result = result; |
722 | 0 | mc.flags = flags; |
723 | 0 | mc.filter = filter; |
724 | 0 | mc.lookup = lookup; |
725 | 0 | mc.context = context; |
726 | 0 | mc.status = 0; |
727 | 0 | mc.level = 0; |
728 | 0 | if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0) |
729 | 0 | VSTRING_RESET(result); |
730 | 0 | status = mac_parse(pattern, mac_expand_callback, (void *) &mc); |
731 | 0 | if ((flags & MAC_EXP_FLAG_SCAN) == 0) |
732 | 0 | VSTRING_TERMINATE(result); |
733 | |
|
734 | 0 | return (status); |
735 | 0 | } |
736 | | |
737 | | #ifdef TEST |
738 | | |
739 | | /* |
740 | | * This code certainly deserves a stand-alone test program. |
741 | | */ |
742 | | #include <stringops.h> |
743 | | #include <htable.h> |
744 | | #include <vstream.h> |
745 | | #include <vstring_vstream.h> |
746 | | |
747 | | static const char *lookup(const char *name, int unused_mode, void *context) |
748 | | { |
749 | | HTABLE *table = (HTABLE *) context; |
750 | | |
751 | | return (htable_find(table, name)); |
752 | | } |
753 | | |
754 | | static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop, |
755 | | const char *rite) |
756 | | { |
757 | | const char myname[] = "length_relop_eval"; |
758 | | ssize_t delta = strlen(left) - strlen(rite); |
759 | | |
760 | | switch (relop) { |
761 | | case MAC_EXP_OP_TOK_EQ: |
762 | | return (mac_exp_op_res_bool[delta == 0]); |
763 | | case MAC_EXP_OP_TOK_NE: |
764 | | return (mac_exp_op_res_bool[delta != 0]); |
765 | | case MAC_EXP_OP_TOK_LT: |
766 | | return (mac_exp_op_res_bool[delta < 0]); |
767 | | case MAC_EXP_OP_TOK_LE: |
768 | | return (mac_exp_op_res_bool[delta <= 0]); |
769 | | case MAC_EXP_OP_TOK_GE: |
770 | | return (mac_exp_op_res_bool[delta >= 0]); |
771 | | case MAC_EXP_OP_TOK_GT: |
772 | | return (mac_exp_op_res_bool[delta > 0]); |
773 | | default: |
774 | | msg_panic("%s: unknown operator: %d", |
775 | | myname, relop); |
776 | | } |
777 | | } |
778 | | |
779 | | int main(int unused_argc, char **argv) |
780 | | { |
781 | | VSTRING *buf = vstring_alloc(100); |
782 | | VSTRING *result = vstring_alloc(100); |
783 | | char *cp; |
784 | | char *name; |
785 | | char *value; |
786 | | HTABLE *table; |
787 | | int stat; |
788 | | int length_relops[] = { |
789 | | MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE, |
790 | | MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE, |
791 | | MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE, |
792 | | 0, |
793 | | }; |
794 | | |
795 | | /* |
796 | | * Add relops that compare string lengths instead of content. |
797 | | */ |
798 | | mac_expand_add_relop(length_relops, "length", length_relop_eval); |
799 | | |
800 | | /* |
801 | | * Loop over the inputs. |
802 | | */ |
803 | | while (!vstream_feof(VSTREAM_IN)) { |
804 | | |
805 | | table = htable_create(0); |
806 | | |
807 | | /* |
808 | | * Read a block of definitions, terminated with an empty line. |
809 | | */ |
810 | | while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { |
811 | | vstream_printf("<< %s\n", vstring_str(buf)); |
812 | | vstream_fflush(VSTREAM_OUT); |
813 | | if (VSTRING_LEN(buf) == 0) |
814 | | break; |
815 | | cp = vstring_str(buf); |
816 | | name = mystrtok(&cp, CHARS_SPACE "="); |
817 | | value = mystrtok(&cp, CHARS_SPACE "="); |
818 | | htable_enter(table, name, value ? mystrdup(value) : 0); |
819 | | } |
820 | | |
821 | | /* |
822 | | * Read a block of patterns, terminated with an empty line or EOF. |
823 | | */ |
824 | | while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { |
825 | | vstream_printf("<< %s\n", vstring_str(buf)); |
826 | | vstream_fflush(VSTREAM_OUT); |
827 | | if (VSTRING_LEN(buf) == 0) |
828 | | break; |
829 | | cp = vstring_str(buf); |
830 | | VSTRING_RESET(result); |
831 | | stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, |
832 | | (char *) 0, lookup, (void *) table); |
833 | | vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); |
834 | | vstream_fflush(VSTREAM_OUT); |
835 | | } |
836 | | htable_free(table, myfree); |
837 | | vstream_printf("\n"); |
838 | | } |
839 | | |
840 | | /* |
841 | | * Clean up. |
842 | | */ |
843 | | vstring_free(buf); |
844 | | vstring_free(result); |
845 | | exit(0); |
846 | | } |
847 | | |
848 | | #endif |