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