1 /* $NetBSD: vbuf_print.c,v 1.5 2025/02/25 19:15:52 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 "stringops.h"
73 #include "vbuf_print.h"
74
75 /*
76 * What we need here is a *sprintf() routine that can ask for more room (as
77 * in 4.4 BSD). However, that functionality is not widely available, and I
78 * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
79 *
80 * Postfix vbuf_print() was implemented when many mainstream systems had no
81 * usable snprintf() implementation (usable means: return the length,
82 * excluding terminator, that the output would have if the buffer were large
83 * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
84 * distinguish between formatting error and buffer size error, while SUN had
85 * no snprintf() implementation before Solaris 2.6 (1997).
86 *
87 * For the above reasons, vbuf_print() was implemented with sprintf() and a
88 * generously-sized output buffer. Current vbuf_print() implementations use
89 * snprintf(), and report an error if the output does not fit (in that case,
90 * the old sprintf()-based implementation would have had a buffer overflow
91 * vulnerability). The old implementation is still available for building
92 * Postfix on ancient systems.
93 *
94 * Guessing the output size of a string (%s) conversion is not hard. The
95 * problem is with numerical results. Instead of making an accurate guess we
96 * take a wide margin when reserving space. The INT_SPACE margin should be
97 * large enough to hold the result from any (octal, hex, decimal) integer
98 * conversion that has no explicit width or precision specifiers. With
99 * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
100 * just to be sure.
101 */
102 #define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2)
103 #define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
104 #define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2)
105
106 /*
107 * Helper macros... Note that there is no need to check the result from
108 * VSTRING_SPACE() because that always succeeds or never returns.
109 */
110 #ifndef NO_SNPRINTF
111 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
112 ssize_t _ret; \
113 if (VBUF_SPACE((bp), (sz)) != 0) \
114 return (bp); \
115 _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
116 if (_ret < 0) \
117 msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
118 if (_ret >= (bp)->cnt) \
119 msg_panic("%s: output for '%s' exceeds space %ld", \
120 myname, mystrdup(fmt), (long) (bp)->cnt); \
121 VBUF_SKIP(bp); \
122 } while (0)
123 #else
124 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
125 if (VBUF_SPACE((bp), (sz)) != 0) \
126 return (bp); \
127 sprintf((char *) (bp)->ptr, (fmt), (arg)); \
128 VBUF_SKIP(bp); \
129 } while (0)
130 #endif
131
132 #define VBUF_SKIP(bp) do { \
133 while ((bp)->cnt > 0 && *(bp)->ptr) \
134 (bp)->ptr++, (bp)->cnt--; \
135 } while (0)
136
137 #define VSTRING_ADDNUM(vp, n) do { \
138 VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
139 } while (0)
140
141 #define VBUF_STRCAT(bp, s) do { \
142 unsigned char *_cp = (unsigned char *) (s); \
143 int _ch; \
144 while ((_ch = *_cp++) != 0) \
145 VBUF_PUT((bp), _ch); \
146 } while (0)
147
148 /* vbuf_print - format string, vsprintf-like interface */
149
vbuf_print(VBUF * bp,const char * format,va_list ap)150 VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap)
151 {
152 const char *myname = "vbuf_print";
153 static VSTRING *fmt; /* format specifier */
154 unsigned char *cp;
155 int width; /* width and numerical precision */
156 int prec; /* are signed for overflow defense */
157 unsigned long_flag; /* long or plain integer */
158 int ch;
159 char *s;
160 int saved_errno = errno; /* VBUF_SPACE() may clobber it */
161
162 /*
163 * Assume that format strings are short.
164 */
165 if (fmt == 0)
166 fmt = vstring_alloc(INT_SPACE);
167
168 /*
169 * Iterate over characters in the format string, picking up arguments
170 * when format specifiers are found.
171 */
172 for (cp = (unsigned char *) format; *cp; cp++) {
173 if (*cp != '%') {
174 VBUF_PUT(bp, *cp); /* ordinary character */
175 } else if (cp[1] == '%') {
176 VBUF_PUT(bp, *cp++); /* %% becomes % */
177 } else {
178
179 /*
180 * Handle format specifiers one at a time, since we can only deal
181 * with arguments one at a time. Try to determine the end of the
182 * format specifier. We do not attempt to fully parse format
183 * strings, since we are ging to let sprintf() do the hard work.
184 * In regular expression notation, we recognize:
185 *
186 * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
187 *
188 * which includes some combinations that do not make sense. Garbage
189 * in, garbage out.
190 */
191 VSTRING_RESET(fmt); /* clear format string */
192 VSTRING_ADDCH(fmt, *cp++);
193 if (*cp == '-') /* left-adjusted field? */
194 VSTRING_ADDCH(fmt, *cp++);
195 if (*cp == '+') /* signed field? */
196 VSTRING_ADDCH(fmt, *cp++);
197 if (*cp == '0') /* zero-padded field? */
198 VSTRING_ADDCH(fmt, *cp++);
199 if (*cp == '*') { /* dynamic field width */
200 width = va_arg(ap, int);
201 if (width < 0) {
202 msg_warn("%s: bad width %d in %.50s",
203 myname, width, format);
204 width = 0;
205 } else
206 VSTRING_ADDNUM(fmt, width);
207 cp++;
208 } else { /* hard-coded field width */
209 for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
210 int digit = ch - '0';
211
212 if (width > INT_MAX / 10
213 || (width *= 10) > INT_MAX - digit)
214 msg_panic("%s: bad width %d... in %.50s",
215 myname, width, format);
216 width += digit;
217 VSTRING_ADDCH(fmt, ch);
218 }
219 }
220 if (*cp == '.') { /* width/precision separator */
221 VSTRING_ADDCH(fmt, *cp++);
222 if (*cp == '*') { /* dynamic precision */
223 prec = va_arg(ap, int);
224 if (prec < 0) {
225 msg_warn("%s: bad precision %d in %.50s",
226 myname, prec, format);
227 prec = -1;
228 } else
229 VSTRING_ADDNUM(fmt, prec);
230 cp++;
231 } else { /* hard-coded precision */
232 for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
233 int digit = ch - '0';
234
235 if (prec > INT_MAX / 10
236 || (prec *= 10) > INT_MAX - digit)
237 msg_panic("%s: bad precision %d... in %.50s",
238 myname, prec, format);
239 prec += digit;
240 VSTRING_ADDCH(fmt, ch);
241 }
242 }
243 } else {
244 prec = -1;
245 }
246 if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
247 VSTRING_ADDCH(fmt, *cp++);
248 if (*cp == 0) /* premature end, punt */
249 break;
250 VSTRING_ADDCH(fmt, *cp); /* type (checked below) */
251 VSTRING_TERMINATE(fmt); /* null terminate */
252
253 /*
254 * Execute the format string - let sprintf() do the hard work for
255 * non-trivial cases only. For simple string conversions and for
256 * long string conversions, do a direct copy to the output
257 * buffer.
258 */
259 switch (*cp) {
260 case 's': /* string-valued argument */
261 if (long_flag)
262 msg_panic("%s: %%l%c is not supported", myname, *cp);
263 s = va_arg(ap, char *);
264 if (prec >= 0 || (width > 0 && width > strlen(s))) {
265 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
266 vstring_str(fmt), s);
267 } else {
268 VBUF_STRCAT(bp, s);
269 }
270 break;
271 case 'c': /* integral-valued argument */
272 if (long_flag)
273 msg_panic("%s: %%l%c is not supported", myname, *cp);
274 /* FALLTHROUGH */
275 case 'd':
276 case 'u':
277 case 'o':
278 case 'x':
279 case 'X':
280 if (long_flag)
281 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
282 vstring_str(fmt), va_arg(ap, long));
283 else
284 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
285 vstring_str(fmt), va_arg(ap, int));
286 break;
287 case 'e': /* float-valued argument */
288 case 'f':
289 case 'g':
290 /* C99 *printf ignore the 'l' modifier. */
291 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
292 vstring_str(fmt), va_arg(ap, double));
293 break;
294 case 'm':
295 /* Ignore the 'l' modifier, width and precision. */
296 VBUF_STRCAT(bp, mystrerror(saved_errno));
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