1 /* $NetBSD: vbuf_print.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* vbuf_print 3
6 /* SUMMARY
7 /* formatted print to generic buffer
8 /* SYNOPSIS
9 /* #include <stdarg.h>
10 /* #include <vbuf_print.h>
11 /*
12 /* VBUF *vbuf_print(bp, format, ap)
13 /* VBUF *bp;
14 /* const char *format;
15 /* va_list ap;
16 /* DESCRIPTION
17 /* vbuf_print() appends data to the named buffer according to its
18 /* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e,
19 /* f and g format types, the l modifier, field width and precision,
20 /* sign, and padding with zeros or spaces.
21 /*
22 /* In addition, vbuf_print() recognizes the %m format specifier
23 /* and expands it to the error message corresponding to the current
24 /* value of the global \fIerrno\fR variable.
25 /* REENTRANCY
26 /* .ad
27 /* .fi
28 /* vbuf_print() allocates a static buffer. After completion
29 /* of the first vbuf_print() call, this buffer is safe for
30 /* reentrant vbuf_print() calls by (asynchronous) terminating
31 /* signal handlers or by (synchronous) terminating error
32 /* handlers. vbuf_print() initialization typically happens
33 /* upon the first formatted output to a VSTRING or VSTREAM.
34 /*
35 /* However, it is up to the caller to ensure that the destination
36 /* VSTREAM or VSTRING buffer is protected against reentrant usage.
37 /* LICENSE
38 /* .ad
39 /* .fi
40 /* The Secure Mailer license must be distributed with this software.
41 /* AUTHOR(S)
42 /* Wietse Venema
43 /* IBM T.J. Watson Research
44 /* P.O. Box 704
45 /* Yorktown Heights, NY 10598, USA
46 /*
47 /* Wietse Venema
48 /* Google, Inc.
49 /* 111 8th Avenue
50 /* New York, NY 10011, USA
51 /*--*/
52
53 /* System library. */
54
55 #include "sys_defs.h"
56 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
57 #include <stdarg.h>
58 #include <string.h>
59 #include <ctype.h>
60 #include <stdlib.h> /* 44bsd stdarg.h uses abort() */
61 #include <stdio.h> /* sprintf() prototype */
62 #include <float.h> /* range of doubles */
63 #include <errno.h>
64 #include <limits.h> /* CHAR_BIT, INT_MAX */
65
66 /* Application-specific. */
67
68 #include "msg.h"
69 #include "mymalloc.h"
70 #include "vbuf.h"
71 #include "vstring.h"
72 #include "vbuf_print.h"
73
74 /*
75 * What we need here is a *sprintf() routine that can ask for more room (as
76 * in 4.4 BSD). However, that functionality is not widely available, and I
77 * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
78 *
79 * Postfix vbuf_print() was implemented when many mainstream systems had no
80 * usable snprintf() implementation (usable means: return the length,
81 * excluding terminator, that the output would have if the buffer were large
82 * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
83 * distinguish between formatting error and buffer size error, while SUN had
84 * no snprintf() implementation before Solaris 2.6 (1997).
85 *
86 * For the above reasons, vbuf_print() was implemented with sprintf() and a
87 * generously-sized output buffer. Current vbuf_print() implementations use
88 * snprintf(), and report an error if the output does not fit (in that case,
89 * the old sprintf()-based implementation would have had a buffer overflow
90 * vulnerability). The old implementation is still available for building
91 * Postfix on ancient systems.
92 *
93 * Guessing the output size of a string (%s) conversion is not hard. The
94 * problem is with numerical results. Instead of making an accurate guess we
95 * take a wide margin when reserving space. The INT_SPACE margin should be
96 * large enough to hold the result from any (octal, hex, decimal) integer
97 * conversion that has no explicit width or precision specifiers. With
98 * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
99 * just to be sure.
100 */
101 #define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2)
102 #define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
103 #define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2)
104
105 /*
106 * Helper macros... Note that there is no need to check the result from
107 * VSTRING_SPACE() because that always succeeds or never returns.
108 */
109 #ifndef NO_SNPRINTF
110 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
111 ssize_t _ret; \
112 if (VBUF_SPACE((bp), (sz)) != 0) \
113 return (bp); \
114 _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
115 if (_ret < 0) \
116 msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
117 if (_ret >= (bp)->cnt) \
118 msg_panic("%s: output for '%s' exceeds space %ld", \
119 myname, mystrdup(fmt), (long) (bp)->cnt); \
120 VBUF_SKIP(bp); \
121 } while (0)
122 #else
123 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
124 if (VBUF_SPACE((bp), (sz)) != 0) \
125 return (bp); \
126 sprintf((char *) (bp)->ptr, (fmt), (arg)); \
127 VBUF_SKIP(bp); \
128 } while (0)
129 #endif
130
131 #define VBUF_SKIP(bp) do { \
132 while ((bp)->cnt > 0 && *(bp)->ptr) \
133 (bp)->ptr++, (bp)->cnt--; \
134 } while (0)
135
136 #define VSTRING_ADDNUM(vp, n) do { \
137 VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
138 } while (0)
139
140 #define VBUF_STRCAT(bp, s) do { \
141 unsigned char *_cp = (unsigned char *) (s); \
142 int _ch; \
143 while ((_ch = *_cp++) != 0) \
144 VBUF_PUT((bp), _ch); \
145 } while (0)
146
147 /* vbuf_print - format string, vsprintf-like interface */
148
vbuf_print(VBUF * bp,const char * format,va_list ap)149 VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap)
150 {
151 const char *myname = "vbuf_print";
152 static VSTRING *fmt; /* format specifier */
153 unsigned char *cp;
154 int width; /* width and numerical precision */
155 int prec; /* are signed for overflow defense */
156 unsigned long_flag; /* long or plain integer */
157 int ch;
158 char *s;
159 int saved_errno = errno; /* VBUF_SPACE() may clobber it */
160
161 /*
162 * Assume that format strings are short.
163 */
164 if (fmt == 0)
165 fmt = vstring_alloc(INT_SPACE);
166
167 /*
168 * Iterate over characters in the format string, picking up arguments
169 * when format specifiers are found.
170 */
171 for (cp = (unsigned char *) format; *cp; cp++) {
172 if (*cp != '%') {
173 VBUF_PUT(bp, *cp); /* ordinary character */
174 } else if (cp[1] == '%') {
175 VBUF_PUT(bp, *cp++); /* %% becomes % */
176 } else {
177
178 /*
179 * Handle format specifiers one at a time, since we can only deal
180 * with arguments one at a time. Try to determine the end of the
181 * format specifier. We do not attempt to fully parse format
182 * strings, since we are ging to let sprintf() do the hard work.
183 * In regular expression notation, we recognize:
184 *
185 * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
186 *
187 * which includes some combinations that do not make sense. Garbage
188 * in, garbage out.
189 */
190 VSTRING_RESET(fmt); /* clear format string */
191 VSTRING_ADDCH(fmt, *cp++);
192 if (*cp == '-') /* left-adjusted field? */
193 VSTRING_ADDCH(fmt, *cp++);
194 if (*cp == '+') /* signed field? */
195 VSTRING_ADDCH(fmt, *cp++);
196 if (*cp == '0') /* zero-padded field? */
197 VSTRING_ADDCH(fmt, *cp++);
198 if (*cp == '*') { /* dynamic field width */
199 width = va_arg(ap, int);
200 if (width < 0) {
201 msg_warn("%s: bad width %d in %.50s",
202 myname, width, format);
203 width = 0;
204 } else
205 VSTRING_ADDNUM(fmt, width);
206 cp++;
207 } else { /* hard-coded field width */
208 for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
209 int digit = ch - '0';
210
211 if (width > INT_MAX / 10
212 || (width *= 10) > INT_MAX - digit)
213 msg_panic("%s: bad width %d... in %.50s",
214 myname, width, format);
215 width += digit;
216 VSTRING_ADDCH(fmt, ch);
217 }
218 }
219 if (*cp == '.') { /* width/precision separator */
220 VSTRING_ADDCH(fmt, *cp++);
221 if (*cp == '*') { /* dynamic precision */
222 prec = va_arg(ap, int);
223 if (prec < 0) {
224 msg_warn("%s: bad precision %d in %.50s",
225 myname, prec, format);
226 prec = -1;
227 } else
228 VSTRING_ADDNUM(fmt, prec);
229 cp++;
230 } else { /* hard-coded precision */
231 for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
232 int digit = ch - '0';
233
234 if (prec > INT_MAX / 10
235 || (prec *= 10) > INT_MAX - digit)
236 msg_panic("%s: bad precision %d... in %.50s",
237 myname, prec, format);
238 prec += digit;
239 VSTRING_ADDCH(fmt, ch);
240 }
241 }
242 } else {
243 prec = -1;
244 }
245 if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
246 VSTRING_ADDCH(fmt, *cp++);
247 if (*cp == 0) /* premature end, punt */
248 break;
249 VSTRING_ADDCH(fmt, *cp); /* type (checked below) */
250 VSTRING_TERMINATE(fmt); /* null terminate */
251
252 /*
253 * Execute the format string - let sprintf() do the hard work for
254 * non-trivial cases only. For simple string conversions and for
255 * long string conversions, do a direct copy to the output
256 * buffer.
257 */
258 switch (*cp) {
259 case 's': /* string-valued argument */
260 if (long_flag)
261 msg_panic("%s: %%l%c is not supported", myname, *cp);
262 s = va_arg(ap, char *);
263 if (prec >= 0 || (width > 0 && width > strlen(s))) {
264 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
265 vstring_str(fmt), s);
266 } else {
267 VBUF_STRCAT(bp, s);
268 }
269 break;
270 case 'c': /* integral-valued argument */
271 if (long_flag)
272 msg_panic("%s: %%l%c is not supported", myname, *cp);
273 /* FALLTHROUGH */
274 case 'd':
275 case 'u':
276 case 'o':
277 case 'x':
278 case 'X':
279 if (long_flag)
280 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
281 vstring_str(fmt), va_arg(ap, long));
282 else
283 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
284 vstring_str(fmt), va_arg(ap, int));
285 break;
286 case 'e': /* float-valued argument */
287 case 'f':
288 case 'g':
289 /* C99 *printf ignore the 'l' modifier. */
290 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
291 vstring_str(fmt), va_arg(ap, double));
292 break;
293 case 'm':
294 /* Ignore the 'l' modifier, width and precision. */
295 VBUF_STRCAT(bp, saved_errno ?
296 strerror(saved_errno) : "Application error");
297 break;
298 case 'p':
299 if (long_flag)
300 msg_panic("%s: %%l%c is not supported", myname, *cp);
301 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
302 vstring_str(fmt), va_arg(ap, char *));
303 break;
304 default: /* anything else is bad */
305 msg_panic("vbuf_print: unknown format type: %c", *cp);
306 /* NOTREACHED */
307 break;
308 }
309 }
310 }
311 return (bp);
312 }
313
314 #ifdef TEST
315 #include <argv.h>
316 #include <msg_vstream.h>
317 #include <vstring.h>
318 #include <vstring_vstream.h>
319
main(int argc,char ** argv)320 int main(int argc, char **argv)
321 {
322 VSTRING *ibuf = vstring_alloc(100);
323
324 msg_vstream_init(argv[0], VSTREAM_ERR);
325
326 while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) {
327 ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE);
328 char *cp;
329
330 if (args->argc == 0 || *(cp = args->argv[0]) == '#') {
331 /* void */ ;
332 } else if (args->argc != 2 || *cp != '%') {
333 msg_warn("usage: format number");
334 } else {
335 char *fmt = cp++;
336 int lflag;
337
338 /* Determine the vstring_sprintf() argument type. */
339 cp += strspn(cp, "+-*0123456789.");
340 if ((lflag = (*cp == 'l')) != 0)
341 cp++;
342 if (cp[1] != 0) {
343 msg_warn("bad format: \"%s\"", fmt);
344 } else {
345 VSTRING *obuf = vstring_alloc(1);
346 char *val = args->argv[1];
347
348 /* Test the worst-case memory allocation. */
349 #ifdef CA_VSTRING_CTL_EXACT
350 vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END);
351 #endif
352 switch (*cp) {
353 case 'c':
354 case 'd':
355 case 'o':
356 case 'u':
357 case 'x':
358 case 'X':
359 if (lflag)
360 vstring_sprintf(obuf, fmt, atol(val));
361 else
362 vstring_sprintf(obuf, fmt, atoi(val));
363 msg_info("\"%s\"", vstring_str(obuf));
364 break;
365 case 's':
366 vstring_sprintf(obuf, fmt, val);
367 msg_info("\"%s\"", vstring_str(obuf));
368 break;
369 case 'f':
370 case 'g':
371 vstring_sprintf(obuf, fmt, atof(val));
372 msg_info("\"%s\"", vstring_str(obuf));
373 break;
374 default:
375 msg_warn("bad format: \"%s\"", fmt);
376 break;
377 }
378 vstring_free(obuf);
379 }
380 }
381 argv_free(args);
382 }
383 vstring_free(ibuf);
384 return (0);
385 }
386
387 #endif
388