1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #ifndef HAVE_SNPRINTF
6 
7 #include <ctype.h>
8 #include <sys/types.h>
9 
10 /* Define this as a fall through, HAVE_STDARG_H is probably already set */
11 
12 #ifndef HAVE_VARARGS_H
13 # define HAVE_VARARGS_H
14 #endif
15 
16 /**************************************************************
17  * Original:
18  * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
19  * A bombproof version of doprnt (dopr) included.
20  * Sigh.  This sort of thing is always nasty do deal with.  Note that
21  * the version here does not include floating point...
22  *
23  * snprintf() is used instead of sprintf() as it does limit checks
24  * for string length.  This covers a nasty loophole.
25  *
26  * The other functions are there to prevent NULL pointers from
27  * causing nast effects.
28  *
29  * More Recently:
30  *  Brandon Long (blong@fiction.net) 9/15/96 for mutt 0.43
31  *  This was ugly.  It is still ugly.  I opted out of floating point
32  *  numbers, but the formatter understands just about everything
33  *  from the normal C string format, at least as far as I can tell from
34  *  the Solaris 2.5 printf(3S) man page.
35  *
36  *  Brandon Long (blong@fiction.net) 10/22/97 for mutt 0.87.1
37  *    Ok, added some minimal floating point support, which means this
38  *    probably requires libm on most operating systems.  Don't yet
39  *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
40  *    was pretty badly broken, it just wasn't being exercised in ways
41  *    which showed it, so that's been fixed.  Also, formated the code
42  *    to mutt conventions, and removed dead code left over from the
43  *    original.  Also, there is now a builtin-test, just compile with:
44  *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
45  *    and run snprintf for results.
46  *
47  **************************************************************/
48 
49 
50 /* varargs declarations: */
51 
52 #if defined(HAVE_STDARG_H)
53 # include <stdarg.h>
54 # define HAVE_STDARGS    /* let's hope that works everywhere (mj) */
55 # define VA_LOCAL_DECL   va_list ap
56 # define VA_START(f)     va_start(ap, f)
57 # define VA_SHIFT(v,t)  ;   /* no-op for ANSI */
58 # define VA_END          va_end(ap)
59 #else
60 # if defined(HAVE_VARARGS_H)
61 #  include <varargs.h>
62 #  undef HAVE_STDARGS
63 #  define VA_LOCAL_DECL   va_list ap
64 #  define VA_START(f)     va_start(ap)      /* f is ignored! */
65 #  define VA_SHIFT(v,t) v = va_arg(ap,t)
66 #  define VA_END        va_end(ap)
67 # else
68 /*XX ** NO VARARGS ** XX*/
69 # endif
70 #endif
71 
72 int snprintf (char *str, size_t count, const char *fmt, ...);
73 int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
74 
75 static void dopr (char *buffer, size_t maxlen, const char *format,
76                   va_list args);
77 static void fmtstr (char *buffer, size_t *currlen, size_t maxlen,
78 		    char *value, int flags, int min, int max);
79 static void fmtint (char *buffer, size_t *currlen, size_t maxlen,
80 		    long value, int base, int min, int max, int flags);
81 static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
82 		   long double fvalue, int min, int max, int flags);
83 static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
84 
vsnprintf(char * str,size_t count,const char * fmt,va_list args)85 int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
86 {
87   str[0] = 0;
88   dopr(str, count, fmt, args);
89   return(strlen(str));
90 }
91 
92 /* VARARGS3 */
93 #ifdef HAVE_STDARGS
snprintf(char * str,size_t count,const char * fmt,...)94 int snprintf (char *str,size_t count,const char *fmt,...)
95 #else
96 int snprintf (va_alist) va_dcl
97 #endif
98 {
99 #ifndef HAVE_STDARGS
100   char *str;
101   size_t count;
102   char *fmt;
103 #endif
104   VA_LOCAL_DECL;
105 
106   VA_START (fmt);
107   VA_SHIFT (str, char *);
108   VA_SHIFT (count, size_t );
109   VA_SHIFT (fmt, char *);
110   (void) vsnprintf(str, count, fmt, ap);
111   VA_END;
112   return(strlen(str));
113 }
114 
115 /*
116  * dopr(): poor man's version of doprintf
117  */
118 
119 /* format read states */
120 #define DP_S_DEFAULT 0
121 #define DP_S_FLAGS   1
122 #define DP_S_MIN     2
123 #define DP_S_DOT     3
124 #define DP_S_MAX     4
125 #define DP_S_MOD     5
126 #define DP_S_CONV    6
127 #define DP_S_DONE    7
128 
129 /* format flags - Bits */
130 #define DP_F_MINUS 1
131 #define DP_F_PLUS  2
132 #define DP_F_SPACE 4
133 #define DP_F_NUM   8
134 #define DP_F_ZERO  16
135 #define DP_F_UP    32
136 
137 /* Conversion Flags */
138 #define DP_C_SHORT   1
139 #define DP_C_LONG    2
140 #define DP_C_LDOUBLE 3
141 
142 #define char_to_int(p) (p - '0')
143 #define MAX(p,q) ((p >= q) ? p : q)
144 
dopr(char * buffer,size_t maxlen,const char * format,va_list args)145 static void dopr (char *buffer, size_t maxlen, const char *format, va_list args)
146 {
147   char ch;
148   long value;
149   long double fvalue;
150   char *strvalue;
151   int min;
152   int max;
153   int state;
154   int flags;
155   int cflags;
156   size_t currlen;
157 
158   state = DP_S_DEFAULT;
159   currlen = flags = cflags = min = 0;
160   max = -1;
161   ch = *format++;
162 
163   while (state != DP_S_DONE)
164   {
165     if ((ch == '\0') || (currlen >= maxlen))
166       state = DP_S_DONE;
167 
168     switch(state)
169     {
170     case DP_S_DEFAULT:
171       if (ch == '%')
172 	state = DP_S_FLAGS;
173       else
174 	dopr_outch (buffer, &currlen, maxlen, ch);
175       ch = *format++;
176       break;
177     case DP_S_FLAGS:
178       switch (ch)
179       {
180       case '-':
181 	flags |= DP_F_MINUS;
182         ch = *format++;
183 	break;
184       case '+':
185 	flags |= DP_F_PLUS;
186         ch = *format++;
187 	break;
188       case ' ':
189 	flags |= DP_F_SPACE;
190         ch = *format++;
191 	break;
192       case '#':
193 	flags |= DP_F_NUM;
194         ch = *format++;
195 	break;
196       case '0':
197 	flags |= DP_F_ZERO;
198         ch = *format++;
199 	break;
200       default:
201 	state = DP_S_MIN;
202 	break;
203       }
204       break;
205     case DP_S_MIN:
206       if (isdigit(ch))
207       {
208 	min = 10*min + char_to_int (ch);
209 	ch = *format++;
210       }
211       else if (ch == '*')
212       {
213 	min = va_arg (args, int);
214 	ch = *format++;
215 	state = DP_S_DOT;
216       }
217       else
218 	state = DP_S_DOT;
219       break;
220     case DP_S_DOT:
221       if (ch == '.')
222       {
223 	state = DP_S_MAX;
224 	ch = *format++;
225       }
226       else
227 	state = DP_S_MOD;
228       break;
229     case DP_S_MAX:
230       if (isdigit(ch))
231       {
232 	if (max < 0)
233 	  max = 0;
234 	max = 10*max + char_to_int (ch);
235 	ch = *format++;
236       }
237       else if (ch == '*')
238       {
239 	max = va_arg (args, int);
240 	ch = *format++;
241 	state = DP_S_MOD;
242       }
243       else
244 	state = DP_S_MOD;
245       break;
246     case DP_S_MOD:
247       /* Currently, we don't support Long Long, bummer */
248       switch (ch)
249       {
250       case 'h':
251 	cflags = DP_C_SHORT;
252 	ch = *format++;
253 	break;
254       case 'l':
255 	cflags = DP_C_LONG;
256 	ch = *format++;
257 	break;
258       case 'L':
259 	cflags = DP_C_LDOUBLE;
260 	ch = *format++;
261 	break;
262       default:
263 	break;
264       }
265       state = DP_S_CONV;
266       break;
267     case DP_S_CONV:
268       switch (ch)
269       {
270       case 'd':
271       case 'i':
272 	if (cflags == DP_C_SHORT)
273 	  value = va_arg (args, short int);
274 	else if (cflags == DP_C_LONG)
275 	  value = va_arg (args, long int);
276 	else
277 	  value = va_arg (args, int);
278 	fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
279 	break;
280       case 'o':
281 	flags &= ~DP_F_PLUS;
282 	if (cflags == DP_C_SHORT)
283 	  value = va_arg (args, unsigned short int);
284 	else if (cflags == DP_C_LONG)
285 	  value = va_arg (args, unsigned long int);
286 	else
287 	  value = va_arg (args, unsigned int);
288 	fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
289 	break;
290       case 'u':
291 	flags &= ~DP_F_PLUS;
292 	if (cflags == DP_C_SHORT)
293 	  value = va_arg (args, unsigned short int);
294 	else if (cflags == DP_C_LONG)
295 	  value = va_arg (args, unsigned long int);
296 	else
297 	  value = va_arg (args, unsigned int);
298 	fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
299 	break;
300       case 'X':
301 	flags |= DP_F_UP;
302       case 'x':
303 	flags &= ~DP_F_PLUS;
304 	if (cflags == DP_C_SHORT)
305 	  value = va_arg (args, unsigned short int);
306 	else if (cflags == DP_C_LONG)
307 	  value = va_arg (args, unsigned long int);
308 	else
309 	  value = va_arg (args, unsigned int);
310 	fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
311 	break;
312       case 'f':
313 	if (cflags == DP_C_LDOUBLE)
314 	  fvalue = va_arg (args, long double);
315 	else
316 	  fvalue = va_arg (args, double);
317 	/* um, floating point? */
318 	fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
319 	break;
320       case 'E':
321 	flags |= DP_F_UP;
322       case 'e':
323 	if (cflags == DP_C_LDOUBLE)
324 	  fvalue = va_arg (args, long double);
325 	else
326 	  fvalue = va_arg (args, double);
327 	break;
328       case 'G':
329 	flags |= DP_F_UP;
330       case 'g':
331 	if (cflags == DP_C_LDOUBLE)
332 	  fvalue = va_arg (args, long double);
333 	else
334 	  fvalue = va_arg (args, double);
335 	break;
336       case 'c':
337 	dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
338 	break;
339       case 's':
340 	strvalue = va_arg (args, char *);
341 	if (max < 0)
342 	  max = maxlen; /* ie, no max */
343 	fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
344 	break;
345       case 'p':
346 	strvalue = va_arg (args, void *);
347 	fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags);
348 	break;
349       case 'n':
350 	if (cflags == DP_C_SHORT)
351 	{
352 	  short int *num;
353 	  num = va_arg (args, short int *);
354 	  *num = currlen;
355         }
356 	else if (cflags == DP_C_LONG)
357 	{
358 	  long int *num;
359 	  num = va_arg (args, long int *);
360 	  *num = currlen;
361         }
362 	else
363 	{
364 	  int *num;
365 	  num = va_arg (args, int *);
366 	  *num = currlen;
367         }
368 	break;
369       case '%':
370 	dopr_outch (buffer, &currlen, maxlen, ch);
371 	break;
372       case 'w':
373 	/* not supported yet, treat as next char */
374 	ch = *format++;
375 	break;
376       default:
377 	/* Unknown, skip */
378 	break;
379       }
380       ch = *format++;
381       state = DP_S_DEFAULT;
382       flags = cflags = min = 0;
383       max = -1;
384       break;
385     case DP_S_DONE:
386       break;
387     default:
388       /* hmm? */
389       break; /* some picky compilers need this */
390     }
391   }
392   if (currlen < maxlen - 1)
393     buffer[currlen] = '\0';
394   else
395     buffer[maxlen - 1] = '\0';
396 }
397 
fmtstr(char * buffer,size_t * currlen,size_t maxlen,char * value,int flags,int min,int max)398 static void fmtstr (char *buffer, size_t *currlen, size_t maxlen,
399 		    char *value, int flags, int min, int max)
400 {
401   int padlen, strln;     /* amount to pad */
402   int cnt = 0;
403 
404   if (value == 0)
405   {
406     value = "<NULL>";
407   }
408 
409   for (strln = 0; value[strln]; ++strln); /* strlen */
410   padlen = min - strln;
411   if (padlen < 0)
412     padlen = 0;
413   if (flags & DP_F_MINUS)
414     padlen = -padlen; /* Left Justify */
415 
416   while ((padlen > 0) && (cnt < max))
417   {
418     dopr_outch (buffer, currlen, maxlen, ' ');
419     --padlen;
420     ++cnt;
421   }
422   while (*value && (cnt < max))
423   {
424     dopr_outch (buffer, currlen, maxlen, *value++);
425     ++cnt;
426   }
427   while ((padlen < 0) && (cnt < max))
428   {
429     dopr_outch (buffer, currlen, maxlen, ' ');
430     ++padlen;
431     ++cnt;
432   }
433 }
434 
435 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
436 
fmtint(char * buffer,size_t * currlen,size_t maxlen,long value,int base,int min,int max,int flags)437 static void fmtint (char *buffer, size_t *currlen, size_t maxlen,
438 		    long value, int base, int min, int max, int flags)
439 {
440   int signvalue = 0;
441   unsigned long uvalue;
442   char convert[20];
443   int place = 0;
444   int spadlen = 0; /* amount to space pad */
445   int zpadlen = 0; /* amount to zero pad */
446   int caps = 0;
447 
448   if (max < 0)
449     max = 0;
450 
451   uvalue = value;
452   if( value < 0 ) {
453     signvalue = '-';
454     uvalue = -value;
455   }
456   else
457     if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
458       signvalue = '+';
459     else
460       if (flags & DP_F_SPACE)
461 	signvalue = ' ';
462 
463   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
464 
465   do {
466     convert[place++] =
467       (caps? "0123456789ABCDEF":"0123456789abcdef")
468       [uvalue % (unsigned)base  ];
469     uvalue = (uvalue / (unsigned)base );
470   } while(uvalue && (place < 20));
471   if (place == 20) place--;
472   convert[place] = 0;
473 
474   zpadlen = max - place;
475   spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
476   if (zpadlen < 0) zpadlen = 0;
477   if (spadlen < 0) spadlen = 0;
478   if (flags & DP_F_ZERO)
479   {
480     zpadlen = MAX(zpadlen, spadlen);
481     spadlen = 0;
482   }
483   if (flags & DP_F_MINUS)
484     spadlen = -spadlen; /* Left Justifty */
485 
486 #ifdef DEBUG_SNPRINTF
487   dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
488       zpadlen, spadlen, min, max, place));
489 #endif
490 
491   /* Spaces */
492   while (spadlen > 0)
493   {
494     dopr_outch (buffer, currlen, maxlen, ' ');
495     --spadlen;
496   }
497 
498   /* Sign */
499   if (signvalue)
500     dopr_outch (buffer, currlen, maxlen, signvalue);
501 
502   /* Zeros */
503   if (zpadlen > 0)
504   {
505     while (zpadlen > 0)
506     {
507       dopr_outch (buffer, currlen, maxlen, '0');
508       --zpadlen;
509     }
510   }
511 
512   /* Digits */
513   while (place > 0)
514     dopr_outch (buffer, currlen, maxlen, convert[--place]);
515 
516   /* Left Justified spaces */
517   while (spadlen < 0) {
518     dopr_outch (buffer, currlen, maxlen, ' ');
519     ++spadlen;
520   }
521 }
522 
abs_val(long double value)523 static long double abs_val (long double value)
524 {
525   long double result = value;
526 
527   if (value < 0)
528     result = -value;
529 
530   return result;
531 }
532 
pow10(int exp)533 static long double pow10 (int exp)
534 {
535   long double result = 1;
536 
537   while (exp)
538   {
539     result *= 10;
540     exp--;
541   }
542 
543   return result;
544 }
545 
round(long double value)546 static long round (long double value)
547 {
548   long intpart;
549 
550   intpart = value;
551   value = value - intpart;
552   if (value >= 0.5)
553     intpart++;
554 
555   return intpart;
556 }
557 
fmtfp(char * buffer,size_t * currlen,size_t maxlen,long double fvalue,int min,int max,int flags)558 static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
559 		   long double fvalue, int min, int max, int flags)
560 {
561   int signvalue = 0;
562   long double ufvalue;
563   char iconvert[20];
564   char fconvert[20];
565   int iplace = 0;
566   int fplace = 0;
567   int padlen = 0; /* amount to pad */
568   int zpadlen = 0;
569   int caps = 0;
570   long intpart;
571   long fracpart;
572 
573   /*
574    * AIX manpage says the default is 0, but Solaris says the default
575    * is 6, and sprintf on AIX defaults to 6
576    */
577   if (max < 0)
578     max = 6;
579 
580   ufvalue = abs_val (fvalue);
581 
582   if (fvalue < 0)
583     signvalue = '-';
584   else
585     if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
586       signvalue = '+';
587     else
588       if (flags & DP_F_SPACE)
589 	signvalue = ' ';
590 
591 #if 0
592   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
593 #endif
594 
595   intpart = ufvalue;
596 
597   /*
598    * Sorry, we only support 9 digits past the decimal because of our
599    * conversion method
600    */
601   if (max > 9)
602     max = 9;
603 
604   /* We "cheat" by converting the fractional part to integer by
605    * multiplying by a factor of 10
606    */
607   fracpart = round ((pow10 (max)) * (ufvalue - intpart));
608 
609   if (fracpart >= pow10 (max))
610   {
611     intpart++;
612     fracpart -= pow10 (max);
613   }
614 
615 #ifdef DEBUG_SNPRINTF
616   dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart));
617 #endif
618 
619   /* Convert integer part */
620   do {
621     iconvert[iplace++] =
622       (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10];
623     intpart = (intpart / 10);
624   } while(intpart && (iplace < 20));
625   if (iplace == 20) iplace--;
626   iconvert[iplace] = 0;
627 
628   /* Convert fractional part */
629   do {
630     fconvert[fplace++] =
631       (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10];
632     fracpart = (fracpart / 10);
633   } while(fracpart && (fplace < 20));
634   if (fplace == 20) fplace--;
635   fconvert[fplace] = 0;
636 
637   /* -1 for decimal point, another -1 if we are printing a sign */
638   padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
639   zpadlen = max - fplace;
640   if (zpadlen < 0)
641     zpadlen = 0;
642   if (padlen < 0)
643     padlen = 0;
644   if (flags & DP_F_MINUS)
645     padlen = -padlen; /* Left Justifty */
646 
647   if ((flags & DP_F_ZERO) && (padlen > 0))
648   {
649     if (signvalue)
650     {
651       dopr_outch (buffer, currlen, maxlen, signvalue);
652       --padlen;
653       signvalue = 0;
654     }
655     while (padlen > 0)
656     {
657       dopr_outch (buffer, currlen, maxlen, '0');
658       --padlen;
659     }
660   }
661   while (padlen > 0)
662   {
663     dopr_outch (buffer, currlen, maxlen, ' ');
664     --padlen;
665   }
666   if (signvalue)
667     dopr_outch (buffer, currlen, maxlen, signvalue);
668 
669   while (iplace > 0)
670     dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
671 
672   /*
673    * Decimal point.  This should probably use locale to find the correct
674    * char to print out.
675    */
676   dopr_outch (buffer, currlen, maxlen, '.');
677 
678   while (fplace > 0)
679     dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
680 
681   while (zpadlen > 0)
682   {
683     dopr_outch (buffer, currlen, maxlen, '0');
684     --zpadlen;
685   }
686 
687   while (padlen < 0)
688   {
689     dopr_outch (buffer, currlen, maxlen, ' ');
690     ++padlen;
691   }
692 }
693 
dopr_outch(char * buffer,size_t * currlen,size_t maxlen,char c)694 static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
695 {
696   if (*currlen < maxlen)
697     buffer[(*currlen)++] = c;
698 }
699 
700 #ifdef TEST_SNPRINTF
701 #ifndef LONG_STRING
702 #define LONG_STRING 1024
703 #endif
main(void)704 int main (void)
705 {
706   char buf1[LONG_STRING];
707   char buf2[LONG_STRING];
708   char *fp_fmt[] = {
709     "%-1.5f",
710     "%1.5f",
711     "%123.9f",
712     "%10.5f",
713     "% 10.5f",
714     "%+22.9f",
715     "%+4.9f",
716     "%01.3f",
717     "%4f",
718     "%3.1f",
719     "%3.2f",
720     NULL
721   };
722   double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996,
723     0.9996, 1.996, 4.136, 0};
724   char *int_fmt[] = {
725     "%-1.5d",
726     "%1.5d",
727     "%123.9d",
728     "%5.5d",
729     "%10.5d",
730     "% 10.5d",
731     "%+22.33d",
732     "%01.3d",
733     "%4d",
734     NULL
735   };
736   long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
737   int x, y;
738   int fail = 0;
739   int num = 0;
740 
741   printf ("Testing snprintf format codes against system sprintf...\n");
742 
743   for (x = 0; fp_fmt[x] != NULL ; x++)
744     for (y = 0; fp_nums[y] != 0 ; y++)
745     {
746       snprintf (buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]);
747       sprintf (buf2, fp_fmt[x], fp_nums[y]);
748       if (strcmp (buf1, buf2))
749       {
750 	printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf  = %s\n",
751 	    fp_fmt[x], buf1, buf2);
752 	fail++;
753       }
754       num++;
755     }
756 
757   for (x = 0; int_fmt[x] != NULL ; x++)
758     for (y = 0; int_nums[y] != 0 ; y++)
759     {
760       snprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]);
761       sprintf (buf2, int_fmt[x], int_nums[y]);
762       if (strcmp (buf1, buf2))
763       {
764 	printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf  = %s\n",
765 	    int_fmt[x], buf1, buf2);
766 	fail++;
767       }
768       num++;
769     }
770   printf ("%d tests failed out of %d.\n", fail, num);
771 }
772 #endif /* SNPRINTF_TEST */
773 
774 #endif /* !HAVE_SNPRINTF */
775