1 /*++
2 /* NAME
3 /*	mac_parse 3
4 /* SUMMARY
5 /*	locate macro references in string
6 /* SYNOPSIS
7 /*	#include <mac_parse.h>
8 /*
9 /*	int	mac_parse(string, action, context)
10 /*	const char *string;
11 /*	int	(*action)(int type, VSTRING *buf, void *context);
12 /* DESCRIPTION
13 /*	This module recognizes macro expressions in null-terminated
14 /*	strings.  Macro expressions have the form $name, $(text) or
15 /*	${text}. A macro name consists of alphanumerics and/or
16 /*	underscore. Text other than macro expressions is treated
17 /*	as literal text.
18 /*
19 /*	mac_parse() breaks up its string argument into macro references
20 /*	and other text, and invokes the \fIaction\fR routine for each item
21 /*	found.  With each action routine call, the \fItype\fR argument
22 /*	indicates what was found, \fIbuf\fR contains a copy of the text
23 /*	found, and \fIcontext\fR is passed on unmodified from the caller.
24 /*	The application is at liberty to clobber \fIbuf\fR.
25 /* .IP MAC_PARSE_LITERAL
26 /*	The content of \fIbuf\fR is literal text.
27 /* .IP MAC_PARSE_EXPR
28 /*	The content of \fIbuf\fR is a macro expression: either a
29 /*	bare macro name without the preceding "$", or all the text
30 /*	inside $() or ${}.
31 /* .PP
32 /*	The action routine result value is the bit-wise OR of zero or more
33 /*	of the following:
34 /* .IP	MAC_PARSE_ERROR
35 /*	A parsing error was detected.
36 /* .IP	MAC_PARSE_UNDEF
37 /*	A macro was expanded but not defined.
38 /* .PP
39 /*	Use the constant MAC_PARSE_OK when no error was detected.
40 /* SEE ALSO
41 /*	dict(3) dictionary interface.
42 /* DIAGNOSTICS
43 /*	Fatal errors: out of memory. malformed macro name.
44 /*
45 /*	The result value is the bit-wise OR of zero or more of the
46 /*	following:
47 /* .IP	MAC_PARSE_ERROR
48 /*	A parsing error was detected.
49 /* .IP	MAC_PARSE_UNDEF
50 /*	A macro was expanded but not defined.
51 /* LICENSE
52 /* .ad
53 /* .fi
54 /*	The Secure Mailer license must be distributed with this software.
55 /* AUTHOR(S)
56 /*	Wietse Venema
57 /*	IBM T.J. Watson Research
58 /*	P.O. Box 704
59 /*	Yorktown Heights, NY 10598, USA
60 /*--*/
61 
62 /* System library. */
63 
64 #include <sys_defs.h>
65 #include <ctype.h>
66 
67 /* Utility library. */
68 
69 #include <msg.h>
70 #include <mac_parse.h>
71 
72  /*
73   * Helper macro for consistency. Null-terminate the temporary buffer,
74   * execute the action, and reset the temporary buffer for re-use.
75   */
76 #define MAC_PARSE_ACTION(status, type, buf, context) \
77 	do { \
78 	    VSTRING_TERMINATE(buf); \
79 	    status |= action((type), (buf), (context)); \
80 	    VSTRING_RESET(buf); \
81 	} while(0)
82 
83 /* mac_parse - split string into literal text and macro references */
84 
mac_parse(const char * value,MAC_PARSE_FN action,void * context)85 int     mac_parse(const char *value, MAC_PARSE_FN action, void *context)
86 {
87     const char *myname = "mac_parse";
88     VSTRING *buf = vstring_alloc(1);	/* result buffer */
89     const char *vp;			/* value pointer */
90     const char *pp;			/* open_paren pointer */
91     const char *ep;			/* string end pointer */
92     static char open_paren[] = "({";
93     static char close_paren[] = ")}";
94     int     level;
95     int     status = 0;
96 
97 #define SKIP(start, var, cond) do { \
98         for (var = start; *var && (cond); var++) \
99 	    /* void */; \
100     } while (0)
101 
102     if (msg_verbose > 1)
103 	msg_info("%s: %s", myname, value);
104 
105     for (vp = value; *vp;) {
106 	if (*vp != '$') {			/* ordinary character */
107 	    VSTRING_ADDCH(buf, *vp);
108 	    vp += 1;
109 	} else if (vp[1] == '$') {		/* $$ becomes $ */
110 	    VSTRING_ADDCH(buf, *vp);
111 	    vp += 2;
112 	} else {				/* found bare $ */
113 	    if (VSTRING_LEN(buf) > 0)
114 		MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
115 	    vp += 1;
116 	    pp = open_paren;
117 	    if (*vp == *pp || *vp == *++pp) {	/* ${x} or $(x) */
118 		level = 1;
119 		vp += 1;
120 		for (ep = vp; level > 0; ep++) {
121 		    if (*ep == 0) {
122 			msg_warn("truncated macro reference: \"%s\"", value);
123 			status |= MAC_PARSE_ERROR;
124 			break;
125 		    }
126 		    if (*ep == *pp)
127 			level++;
128 		    if (*ep == close_paren[pp - open_paren])
129 			level--;
130 		}
131 		if (status & MAC_PARSE_ERROR)
132 		    break;
133 		vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1);
134 		vp = ep;
135 	    } else {				/* plain $x */
136 		SKIP(vp, ep, ISALNUM(*ep) || *ep == '_');
137 		vstring_strncat(buf, vp, ep - vp);
138 		vp = ep;
139 	    }
140 	    if (VSTRING_LEN(buf) == 0) {
141 		status |= MAC_PARSE_ERROR;
142 		msg_warn("empty macro name: \"%s\"", value);
143 		break;
144 	    }
145 	    MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context);
146 	}
147     }
148     if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0)
149 	MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
150 
151     /*
152      * Cleanup.
153      */
154     vstring_free(buf);
155 
156     return (status);
157 }
158 
159 #ifdef TEST
160 
161  /*
162   * Proof-of-concept test program. Read strings from stdin, print parsed
163   * result to stdout.
164   */
165 #include <vstring_vstream.h>
166 
167 /* mac_parse_print - print parse tree */
168 
mac_parse_print(int type,VSTRING * buf,void * unused_context)169 static int mac_parse_print(int type, VSTRING *buf, void *unused_context)
170 {
171     char   *type_name;
172 
173     switch (type) {
174     case MAC_PARSE_EXPR:
175 	type_name = "MAC_PARSE_EXPR";
176 	break;
177     case MAC_PARSE_LITERAL:
178 	type_name = "MAC_PARSE_LITERAL";
179 	break;
180     default:
181 	msg_panic("unknown token type %d", type);
182     }
183     vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf));
184     return (0);
185 }
186 
main(int unused_argc,char ** unused_argv)187 int     main(int unused_argc, char **unused_argv)
188 {
189     VSTRING *buf = vstring_alloc(1);
190 
191     while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
192 	mac_parse(vstring_str(buf), mac_parse_print, (void *) 0);
193 	vstream_fflush(VSTREAM_OUT);
194     }
195     vstring_free(buf);
196     return (0);
197 }
198 
199 #endif
200