summaryrefslogtreecommitdiffstats
path: root/src/util/mac_parse.c
blob: 45b717a18f887035a8160705574f8861a81dcb8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*++
/* NAME
/*	mac_parse 3
/* SUMMARY
/*	locate macro references in string
/* SYNOPSIS
/*	#include <mac_parse.h>
/*
/*	int	mac_parse(string, action, context)
/*	const char *string;
/*	int	(*action)(int type, VSTRING *buf, void *context);
/* DESCRIPTION
/*	This module recognizes macro expressions in null-terminated
/*	strings.  Macro expressions have the form $name, $(text) or
/*	${text}. A macro name consists of alphanumerics and/or
/*	underscore. Text other than macro expressions is treated
/*	as literal text.
/*
/*	mac_parse() breaks up its string argument into macro references
/*	and other text, and invokes the \fIaction\fR routine for each item
/*	found.  With each action routine call, the \fItype\fR argument
/*	indicates what was found, \fIbuf\fR contains a copy of the text
/*	found, and \fIcontext\fR is passed on unmodified from the caller.
/*	The application is at liberty to clobber \fIbuf\fR.
/* .IP MAC_PARSE_LITERAL
/*	The content of \fIbuf\fR is literal text.
/* .IP MAC_PARSE_EXPR
/*	The content of \fIbuf\fR is a macro expression: either a
/*	bare macro name without the preceding "$", or all the text
/*	inside $() or ${}.
/* .PP
/*	The action routine result value is the bit-wise OR of zero or more
/*	of the following:
/* .IP	MAC_PARSE_ERROR
/*	A parsing error was detected.
/* .IP	MAC_PARSE_UNDEF
/*	A macro was expanded but not defined.
/* .PP
/*	Use the constant MAC_PARSE_OK when no error was detected.
/* SEE ALSO
/*	dict(3) dictionary interface.
/* DIAGNOSTICS
/*	Fatal errors: out of memory. malformed macro name.
/*
/*	The result value is the bit-wise OR of zero or more of the
/*	following:
/* .IP	MAC_PARSE_ERROR
/*	A parsing error was detected.
/* .IP	MAC_PARSE_UNDEF
/*	A macro was expanded but not defined.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <mac_parse.h>

 /*
  * Helper macro for consistency. Null-terminate the temporary buffer,
  * execute the action, and reset the temporary buffer for re-use.
  */
#define MAC_PARSE_ACTION(status, type, buf, context) \
	do { \
	    VSTRING_TERMINATE(buf); \
	    status |= action((type), (buf), (context)); \
	    VSTRING_RESET(buf); \
	} while(0)

/* mac_parse - split string into literal text and macro references */

int     mac_parse(const char *value, MAC_PARSE_FN action, void *context)
{
    const char *myname = "mac_parse";
    VSTRING *buf = vstring_alloc(1);	/* result buffer */
    const char *vp;			/* value pointer */
    const char *pp;			/* open_paren pointer */
    const char *ep;			/* string end pointer */
    static char open_paren[] = "({";
    static char close_paren[] = ")}";
    int     level;
    int     status = 0;

#define SKIP(start, var, cond) do { \
        for (var = start; *var && (cond); var++) \
	    /* void */; \
    } while (0)

    if (msg_verbose > 1)
	msg_info("%s: %s", myname, value);

    for (vp = value; *vp;) {
	if (*vp != '$') {			/* ordinary character */
	    VSTRING_ADDCH(buf, *vp);
	    vp += 1;
	} else if (vp[1] == '$') {		/* $$ becomes $ */
	    VSTRING_ADDCH(buf, *vp);
	    vp += 2;
	} else {				/* found bare $ */
	    if (VSTRING_LEN(buf) > 0)
		MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
	    vp += 1;
	    pp = open_paren;
	    if (*vp == *pp || *vp == *++pp) {	/* ${x} or $(x) */
		level = 1;
		vp += 1;
		for (ep = vp; level > 0; ep++) {
		    if (*ep == 0) {
			msg_warn("truncated macro reference: \"%s\"", value);
			status |= MAC_PARSE_ERROR;
			break;
		    }
		    if (*ep == *pp)
			level++;
		    if (*ep == close_paren[pp - open_paren])
			level--;
		}
		if (status & MAC_PARSE_ERROR)
		    break;
		vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1);
		vp = ep;
	    } else {				/* plain $x */
		SKIP(vp, ep, ISALNUM(*ep) || *ep == '_');
		vstring_strncat(buf, vp, ep - vp);
		vp = ep;
	    }
	    if (VSTRING_LEN(buf) == 0) {
		status |= MAC_PARSE_ERROR;
		msg_warn("empty macro name: \"%s\"", value);
		break;
	    }
	    MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context);
	}
    }
    if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0)
	MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);

    /*
     * Cleanup.
     */
    vstring_free(buf);

    return (status);
}

#ifdef TEST

 /*
  * Proof-of-concept test program. Read strings from stdin, print parsed
  * result to stdout.
  */
#include <vstring_vstream.h>

/* mac_parse_print - print parse tree */

static int mac_parse_print(int type, VSTRING *buf, void *unused_context)
{
    char   *type_name;

    switch (type) {
    case MAC_PARSE_EXPR:
	type_name = "MAC_PARSE_EXPR";
	break;
    case MAC_PARSE_LITERAL:
	type_name = "MAC_PARSE_LITERAL";
	break;
    default:
	msg_panic("unknown token type %d", type);
    }
    vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf));
    return (0);
}

int     main(int unused_argc, char **unused_argv)
{
    VSTRING *buf = vstring_alloc(1);

    while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
	mac_parse(vstring_str(buf), mac_parse_print, (void *) 0);
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(buf);
    return (0);
}

#endif