1/*++
2/* NAME
3/*	vbuf_print 3
4/* SUMMARY
5/*	formatted print to generic buffer
6/* SYNOPSIS
7/*	#include <stdarg.h>
8/*	#include <vbuf_print.h>
9/*
10/*	VBUF	*vbuf_print(bp, format, ap)
11/*	VBUF	*bp;
12/*	const char *format;
13/*	va_list	ap;
14/* DESCRIPTION
15/*	vbuf_print() appends data to the named buffer according to its
16/*	\fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e,
17/*	f and g format types, the l modifier, field width and precision,
18/*	sign, and padding with zeros or spaces.
19/*
20/*	In addition, vbuf_print() recognizes the %m format specifier
21/*	and expands it to the error message corresponding to the current
22/*	value of the global \fIerrno\fR variable.
23/* REENTRANCY
24/* .ad
25/* .fi
26/*	vbuf_print() allocates a static buffer. After completion
27/*	of the first vbuf_print() call, this buffer is safe for
28/*	reentrant vbuf_print() calls by (asynchronous) terminating
29/*	signal handlers or by (synchronous) terminating error
30/*	handlers. vbuf_print() initialization typically happens
31/*	upon the first formatted output to a VSTRING or VSTREAM.
32/*
33/*	However, it is up to the caller to ensure that the destination
34/*	VSTREAM or VSTRING buffer is protected against reentrant usage.
35/* LICENSE
36/* .ad
37/* .fi
38/*	The Secure Mailer license must be distributed with this software.
39/* AUTHOR(S)
40/*	Wietse Venema
41/*	IBM T.J. Watson Research
42/*	P.O. Box 704
43/*	Yorktown Heights, NY 10598, USA
44/*--*/
45
46/* System library. */
47
48#include "sys_defs.h"
49#include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
50#include <stdarg.h>
51#include <string.h>
52#include <ctype.h>
53#include <stdlib.h>			/* 44bsd stdarg.h uses abort() */
54#include <stdio.h>			/* sprintf() prototype */
55#include <float.h>			/* range of doubles */
56#include <errno.h>
57#include <limits.h>			/* CHAR_BIT */
58
59/* Application-specific. */
60
61#include "msg.h"
62#include "vbuf.h"
63#include "vstring.h"
64#include "vbuf_print.h"
65
66 /*
67  * What we need here is a *sprintf() routine that can ask for more room (as
68  * in 4.4 BSD). However, that functionality is not widely available, and I
69  * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
70  *
71  * This means we're stuck with plain old ugly sprintf() for all non-trivial
72  * conversions. We cannot use snprintf() even if it is available, because
73  * that routine truncates output, and we want everything. Therefore, it is
74  * up to us to ensure that sprintf() output always stays within bounds.
75  *
76  * Due to the complexity of *printf() format strings we cannot easily predict
77  * how long results will be without actually doing the conversions. A trick
78  * used by some people is to print to a temporary file and to read the
79  * result back. In programs that do a lot of formatting, that might be too
80  * expensive.
81  *
82  * Guessing the output size of a string (%s) conversion is not hard. The
83  * problem is with numerical results. Instead of making an accurate guess we
84  * take a wide margin when reserving space.  The INT_SPACE margin should be
85  * large enough to hold the result from any (octal, hex, decimal) integer
86  * conversion that has no explicit width or precision specifiers. With
87  * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
88  * just to be sure.
89  */
90#define INT_SPACE	((CHAR_BIT * sizeof(long)) / 2)
91#define DBL_SPACE	((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
92#define PTR_SPACE	((CHAR_BIT * sizeof(char *)) / 2)
93
94 /*
95  * Helper macros... Note that there is no need to check the result from
96  * VSTRING_SPACE() because that always succeeds or never returns.
97  */
98#define VBUF_SKIP(bp)	{ \
99	while ((bp)->cnt > 0 && *(bp)->ptr) \
100	    (bp)->ptr++, (bp)->cnt--; \
101    }
102
103#define VSTRING_ADDNUM(vp, n) { \
104	VSTRING_SPACE(vp, INT_SPACE); \
105	sprintf(vstring_end(vp), "%d", n); \
106	VBUF_SKIP(&vp->vbuf); \
107    }
108
109#define VBUF_STRCAT(bp, s) { \
110	unsigned char *_cp = (unsigned char *) (s); \
111	int _ch; \
112	while ((_ch = *_cp++) != 0) \
113	    VBUF_PUT((bp), _ch); \
114    }
115
116/* vbuf_print - format string, vsprintf-like interface */
117
118VBUF   *vbuf_print(VBUF *bp, const char *format, va_list ap)
119{
120    const char *myname = "vbuf_print";
121    static VSTRING *fmt;		/* format specifier */
122    unsigned char *cp;
123    int     width;			/* width and numerical precision */
124    int     prec;			/* are signed for overflow defense */
125    unsigned long_flag;			/* long or plain integer */
126    int     ch;
127    char   *s;
128    int     saved_errno = errno;	/* VBUF_SPACE() may clobber it */
129
130    /*
131     * Assume that format strings are short.
132     */
133    if (fmt == 0)
134	fmt = vstring_alloc(INT_SPACE);
135
136    /*
137     * Iterate over characters in the format string, picking up arguments
138     * when format specifiers are found.
139     */
140    for (cp = (unsigned char *) format; *cp; cp++) {
141	if (*cp != '%') {
142	    VBUF_PUT(bp, *cp);			/* ordinary character */
143	} else if (cp[1] == '%') {
144	    VBUF_PUT(bp, *cp++);		/* %% becomes % */
145	} else {
146
147	    /*
148	     * Handle format specifiers one at a time, since we can only deal
149	     * with arguments one at a time. Try to determine the end of the
150	     * format specifier. We do not attempt to fully parse format
151	     * strings, since we are ging to let sprintf() do the hard work.
152	     * In regular expression notation, we recognize:
153	     *
154	     * %-?0?([0-9]+|\*)?\.?([0-9]+|\*)?l?[a-zA-Z]
155	     *
156	     * which includes some combinations that do not make sense. Garbage
157	     * in, garbage out.
158	     */
159	    VSTRING_RESET(fmt);			/* clear format string */
160	    VSTRING_ADDCH(fmt, *cp++);
161	    if (*cp == '-')			/* left-adjusted field? */
162		VSTRING_ADDCH(fmt, *cp++);
163	    if (*cp == '+')			/* signed field? */
164		VSTRING_ADDCH(fmt, *cp++);
165	    if (*cp == '0')			/* zero-padded field? */
166		VSTRING_ADDCH(fmt, *cp++);
167	    if (*cp == '*') {			/* dynamic field width */
168		width = va_arg(ap, int);
169		VSTRING_ADDNUM(fmt, width);
170		cp++;
171	    } else {				/* hard-coded field width */
172		for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
173		    width = width * 10 + ch - '0';
174		    VSTRING_ADDCH(fmt, ch);
175		}
176	    }
177	    if (width < 0) {
178		msg_warn("%s: bad width %d in %.50s", myname, width, format);
179		width = 0;
180	    }
181	    if (*cp == '.')			/* width/precision separator */
182		VSTRING_ADDCH(fmt, *cp++);
183	    if (*cp == '*') {			/* dynamic precision */
184		prec = va_arg(ap, int);
185		VSTRING_ADDNUM(fmt, prec);
186		cp++;
187	    } else {				/* hard-coded precision */
188		for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
189		    prec = prec * 10 + ch - '0';
190		    VSTRING_ADDCH(fmt, ch);
191		}
192	    }
193	    if (prec < 0) {
194		msg_warn("%s: bad precision %d in %.50s", myname, prec, format);
195		prec = 0;
196	    }
197	    if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
198		VSTRING_ADDCH(fmt, *cp++);
199	    if (*cp == 0)			/* premature end, punt */
200		break;
201	    VSTRING_ADDCH(fmt, *cp);		/* type (checked below) */
202	    VSTRING_TERMINATE(fmt);		/* null terminate */
203
204	    /*
205	     * Execute the format string - let sprintf() do the hard work for
206	     * non-trivial cases only. For simple string conversions and for
207	     * long string conversions, do a direct copy to the output
208	     * buffer.
209	     */
210	    switch (*cp) {
211	    case 's':				/* string-valued argument */
212		s = va_arg(ap, char *);
213		if (prec > 0 || (width > 0 && width > strlen(s))) {
214		    if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE))
215			return (bp);
216		    sprintf((char *) bp->ptr, vstring_str(fmt), s);
217		    VBUF_SKIP(bp);
218		} else {
219		    VBUF_STRCAT(bp, s);
220		}
221		break;
222	    case 'c':				/* integral-valued argument */
223	    case 'd':
224	    case 'u':
225	    case 'o':
226	    case 'x':
227	    case 'X':
228		if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE))
229		    return (bp);
230		if (long_flag)
231		    sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, long));
232		else
233		    sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, int));
234		VBUF_SKIP(bp);
235		break;
236	    case 'e':				/* float-valued argument */
237	    case 'f':
238	    case 'g':
239		if (VBUF_SPACE(bp, (width > prec ? width : prec) + DBL_SPACE))
240		    return (bp);
241		sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, double));
242		VBUF_SKIP(bp);
243		break;
244	    case 'm':
245		VBUF_STRCAT(bp, strerror(saved_errno));
246		break;
247	    case 'p':
248		if (VBUF_SPACE(bp, (width > prec ? width : prec) + PTR_SPACE))
249		    return (bp);
250		sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, char *));
251		VBUF_SKIP(bp);
252		break;
253	    default:				/* anything else is bad */
254		msg_panic("vbuf_print: unknown format type: %c", *cp);
255		/* NOTREACHED */
256		break;
257	    }
258	}
259    }
260    return (bp);
261}
262