/************************************************************** * Original: * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 * A bombproof version of doprnt (dopr) included. * Sigh. This sort of thing is always nasty do deal with. Note that * the version here does not include floating point... * * snprintf() is used instead of sprintf() as it does limit checks * for string length. This covers a nasty loophole. * * The other functions are there to prevent NULL pointers from * causing nast effects. * * More Recently: * Brandon Long 9/15/96 for mutt 0.43 * This was ugly. It is still ugly. I opted out of floating point * numbers, but the formatter understands just about everything * from the normal C string format, at least as far as I can tell from * the Solaris 2.5 printf(3S) man page. * * Brandon Long 10/22/97 for mutt 0.87.1 * Ok, added some minimal floating point support, which means this * probably requires libm on most operating systems. Don't yet * support the exponent (e,E) and sigfig (g,G). Also, fmtint() * was pretty badly broken, it just wasn't being exercised in ways * which showed it, so that's been fixed. Also, formated the code * to mutt conventions, and removed dead code left over from the * original. Also, there is now a builtin-test, just compile with: * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm * and run snprintf for results. * * Thomas Roessler 01/27/98 for mutt 0.89i * The PGP code was using unsigned hexadecimal formats. * Unfortunately, unsigned formats simply didn't work. * * Michael Elkins 03/05/98 for mutt 0.90.8 * The original code assumed that both snprintf() and vsnprintf() were * missing. Some systems only have snprintf() but not vsnprintf(), so * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. * **************************************************************/ #include "config.h" #include #include #include #include #ifdef _WIN32 #include #include #include #else #include #endif /* varargs declarations: */ #if defined(HAVE_STDARG_H) # include # define HAVE_STDARGS /* let's hope that works everywhere (mj) */ # define VA_LOCAL_DECL va_list ap # define VA_START(f) va_start(ap, f) # define VA_SHIFT(v,t) ; /* no-op for ANSI */ # define VA_END va_end(ap) #else # if defined(HAVE_VARARGS_H) # include # undef HAVE_STDARGS # define VA_LOCAL_DECL va_list ap # define VA_START(f) va_start(ap) /* f is ignored! */ # define VA_SHIFT(v,t) v = va_arg(ap,t) # define VA_END va_end(ap) # else /*XX ** NO VARARGS ** XX*/ # error no either stdarg or varargs needed # endif #endif static void dopr (char *buffer, size_t maxlen, const char *format, va_list args); static void fmtstr (char *buffer, size_t *currlen, size_t maxlen, char *value, int flags, int min, int max); static void fmtint (char *buffer, size_t *currlen, size_t maxlen, long value, int base, int min, int max, int flags); static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, long double fvalue, int min, int max, int flags); static void fmtefp (char *buffer, size_t *currlen, size_t maxlen, long double fvalue, int min, int max, int flags); static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); /* * myisdigit(): signalhandlerparanoia isdigit version independend of C-Library */ static int myisdigit(int c) { switch(c) { case '0': return 1; case '1': return 1; case '2': return 1; case '3': return 1; case '4': return 1; case '5': return 1; case '6': return 1; case '7': return 1; case '8': return 1; case '9': return 1; default: return 0; } } /* * mystrlen(): signalhandlerparanoia strlen version independend of C-Library */ size_t mystrlen (const char *s) { int strln; for (strln = 0; s[strln]; ++strln); return strln; } /* * mystrcpy(): signalhandlerparanoia strcpy version independend of C-Library */ char *mystrcpy(char *s1, const char *s2) { int strln; for (strln = 0; s2[strln]; ++strln) s1[strln]=s2[strln]; s1[strln]=0; return s1; } /* * mystrcat(): signalhandlerparanoia strcpy version independend of C-Library */ char *mystrcat(char *s1, const char *s2) { int strln; int length=mystrlen(s1); for (strln = 0; s2[strln]; ++strln) s1[length+strln]=s2[strln]; s1[length+strln]=0; return s1; } /* * dopr(): poor man's version of doprintf */ /* format read states */ #define DP_S_DEFAULT 0 #define DP_S_FLAGS 1 #define DP_S_MIN 2 #define DP_S_DOT 3 #define DP_S_MAX 4 #define DP_S_MOD 5 #define DP_S_CONV 6 #define DP_S_DONE 7 /* format flags - Bits */ #define DP_F_MINUS (1 << 0) #define DP_F_PLUS (1 << 1) #define DP_F_SPACE (1 << 2) #define DP_F_NUM (1 << 3) #define DP_F_ZERO (1 << 4) #define DP_F_UP (1 << 5) #define DP_F_UNSIGNED (1 << 6) /* Conversion Flags */ #define DP_C_SHORT 1 #define DP_C_LONG 2 #define DP_C_LDOUBLE 3 #define char_to_int(p) (p - '0') #ifndef MAX # define MAX(p,q) ((p >= q) ? p : q) #endif static void dopr (char *buffer, size_t maxlen, const char *format, va_list args) { char ch; long value; long double fvalue; char *strvalue; int min; int max; int state; int flags; int cflags; size_t currlen; state = DP_S_DEFAULT; currlen = flags = cflags = min = 0; max = -1; ch = *format++; while (state != DP_S_DONE) { if ((ch == '\0') || (currlen >= maxlen)) state = DP_S_DONE; switch(state) { case DP_S_DEFAULT: if (ch == '%') state = DP_S_FLAGS; else dopr_outch (buffer, &currlen, maxlen, ch); ch = *format++; break; case DP_S_FLAGS: switch (ch) { case '-': flags |= DP_F_MINUS; ch = *format++; break; case '+': flags |= DP_F_PLUS; ch = *format++; break; case ' ': flags |= DP_F_SPACE; ch = *format++; break; case '#': flags |= DP_F_NUM; ch = *format++; break; case '0': flags |= DP_F_ZERO; ch = *format++; break; default: state = DP_S_MIN; break; } break; case DP_S_MIN: if (myisdigit((unsigned char)ch)) { min = 10*min + char_to_int (ch); ch = *format++; } else if (ch == '*') { min = va_arg (args, int); ch = *format++; state = DP_S_DOT; } else state = DP_S_DOT; break; case DP_S_DOT: if (ch == '.') { state = DP_S_MAX; ch = *format++; } else state = DP_S_MOD; break; case DP_S_MAX: if (myisdigit((unsigned char)ch)) { if (max < 0) max = 0; max = 10*max + char_to_int (ch); ch = *format++; } else if (ch == '*') { max = va_arg (args, int); ch = *format++; state = DP_S_MOD; } else state = DP_S_MOD; break; case DP_S_MOD: /* Currently, we don't support Long Long, bummer */ switch (ch) { case 'h': cflags = DP_C_SHORT; ch = *format++; break; case 'l': cflags = DP_C_LONG; ch = *format++; break; case 'L': cflags = DP_C_LDOUBLE; ch = *format++; break; default: break; } state = DP_S_CONV; break; case DP_S_CONV: switch (ch) { case 'd': case 'i': if (cflags == DP_C_SHORT) value =(short int) va_arg (args, int); else if (cflags == DP_C_LONG) value = va_arg (args, long int); else value = va_arg (args, int); fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); break; case 'o': flags |= DP_F_UNSIGNED; if (cflags == DP_C_SHORT) value = (unsigned short int) va_arg (args, int); else if (cflags == DP_C_LONG) value = va_arg (args, unsigned long int); else value = va_arg (args, unsigned int); fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); break; case 'u': flags |= DP_F_UNSIGNED; if (cflags == DP_C_SHORT) value = (unsigned short int) va_arg (args, int); else if (cflags == DP_C_LONG) value = va_arg (args, unsigned long int); else value = va_arg (args, unsigned int); fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); break; case 'X': flags |= DP_F_UP; case 'x': flags |= DP_F_UNSIGNED; if (cflags == DP_C_SHORT) value = (unsigned short int) va_arg (args, int); else if (cflags == DP_C_LONG) value = va_arg (args, unsigned long int); else value = va_arg (args, unsigned int); fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); break; case 'f': if (cflags == DP_C_LDOUBLE) fvalue = va_arg (args, long double); else fvalue = va_arg (args, double); /* um, floating point? */ fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); break; case 'E': flags |= DP_F_UP; case 'e': if (cflags == DP_C_LDOUBLE) fvalue = va_arg (args, long double); else fvalue = va_arg (args, double); /* um, floating point? */ fmtefp (buffer, &currlen, maxlen, fvalue, min, max, flags); break; case 'G': flags |= DP_F_UP; case 'g': if (cflags == DP_C_LDOUBLE) fvalue = va_arg (args, long double); else fvalue = va_arg (args, double); /* um, floating point? */ fmtefp (buffer, &currlen, maxlen, fvalue, min, max, flags); break; case 'c': dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); break; case 's': strvalue = va_arg (args, char *); if (max < 0) max = maxlen; /* ie, no max */ fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); break; case 'p': strvalue = va_arg (args, void *); fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags); break; case 'n': if (cflags == DP_C_SHORT) { short int *num; num = va_arg (args, short int *); *num = currlen; } else if (cflags == DP_C_LONG) { long int *num; num = va_arg (args, long int *); *num = currlen; } else { int *num; num = va_arg (args, int *); *num = currlen; } break; case '%': dopr_outch (buffer, &currlen, maxlen, ch); break; case 'w': /* not supported yet, treat as next char */ ch = *format++; break; default: /* Unknown, skip */ break; } ch = *format++; state = DP_S_DEFAULT; flags = cflags = min = 0; max = -1; break; case DP_S_DONE: break; default: /* hmm? */ break; /* some picky compilers need this */ } } if (currlen < maxlen - 1) buffer[currlen] = '\0'; else buffer[maxlen - 1] = '\0'; } static void fmtstr (char *buffer, size_t *currlen, size_t maxlen, char *value, int flags, int min, int max) { int padlen, strln; /* amount to pad */ int cnt = 0; if (value == 0) { value = ""; } for (strln = 0; value[strln]; ++strln); /* strlen */ padlen = min - strln; if (padlen < 0) padlen = 0; if (flags & DP_F_MINUS) padlen = -padlen; /* Left Justify */ while ((padlen > 0) && (cnt < max)) { dopr_outch (buffer, currlen, maxlen, ' '); --padlen; ++cnt; } while (*value && (cnt < max)) { dopr_outch (buffer, currlen, maxlen, *value++); ++cnt; } while ((padlen < 0) && (cnt < max)) { dopr_outch (buffer, currlen, maxlen, ' '); ++padlen; ++cnt; } } /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ static void fmtint (char *buffer, size_t *currlen, size_t maxlen, long value, int base, int min, int max, int flags) { int signvalue = 0; unsigned long uvalue; char convert[20]; int place = 0; int spadlen = 0; /* amount to space pad */ int zpadlen = 0; /* amount to zero pad */ int caps = 0; if (max < 0) max = 0; uvalue = value; if(!(flags & DP_F_UNSIGNED)) { if( value < 0 ) { signvalue = '-'; uvalue = -value; } else if (flags & DP_F_PLUS) /* Do a sign (+/i) */ signvalue = '+'; else if (flags & DP_F_SPACE) signvalue = ' '; } if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ do { convert[place++] = (caps? "0123456789ABCDEF":"0123456789abcdef") [uvalue % (unsigned)base ]; uvalue = (uvalue / (unsigned)base ); } while(uvalue && (place < 20)); if (place == 20) place--; convert[place] = 0; zpadlen = max - place; spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); if (zpadlen < 0) zpadlen = 0; if (spadlen < 0) spadlen = 0; if (flags & DP_F_ZERO) { zpadlen = MAX(zpadlen, spadlen); spadlen = 0; } if (flags & DP_F_MINUS) spadlen = -spadlen; /* Left Justifty */ #ifdef DEBUG_SNPRINTF dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", zpadlen, spadlen, min, max, place)); #endif /* Spaces */ while (spadlen > 0) { dopr_outch (buffer, currlen, maxlen, ' '); --spadlen; } /* Sign */ if (signvalue) dopr_outch (buffer, currlen, maxlen, signvalue); /* Zeros */ if (zpadlen > 0) { while (zpadlen > 0) { dopr_outch (buffer, currlen, maxlen, '0'); --zpadlen; } } /* Digits */ while (place > 0) dopr_outch (buffer, currlen, maxlen, convert[--place]); /* Left Justified spaces */ while (spadlen < 0) { dopr_outch (buffer, currlen, maxlen, ' '); ++spadlen; } } static long double abs_val (long double value) { long double result = value; if (value < 0) result = -value; return result; } static long double mypow10 (int exp) { long double result = 1; while (exp) { result *= 10; exp--; } return result; } #ifdef WIN32 static long round (long double value) { long intpart; intpart = value; value = value - intpart; if (value >= 0.5) intpart++; return intpart; } #endif static int default_max = 6; void set_number_of_digits(int digits) { default_max = digits - 1; } static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, long double fvalue, int min, int max, int flags) { int signvalue = 0; long double ufvalue; char iconvert[20]; char fconvert[20]; int iplace = 0; int fplace = 0; int padlen = 0; /* amount to pad */ int zpadlen = 0; int caps = 0; long intpart; long fracpart; long old_fracpart; int zeros; int i; /* * AIX manpage says the default is 0, but Solaris says the default * is 6, and sprintf on AIX defaults to 6 */ if (max < 0) max = default_max; ufvalue = abs_val (fvalue); if (fvalue < 0) signvalue = '-'; else if (flags & DP_F_PLUS) /* Do a sign (+/i) */ signvalue = '+'; else if (flags & DP_F_SPACE) signvalue = ' '; #if 0 if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ #endif intpart = ufvalue; /* * Sorry, we only support 9 digits past the decimal because of our * conversion method */ if (max > 9) max = 9; /* We "cheat" by converting the fractional part to integer by * multiplying by a factor of 10 */ fracpart = (mypow10 (max)) * (ufvalue - intpart); if (fracpart>0) { zeros=fabs(floor(log(ufvalue - intpart)/log(10))+1); if (zeros>=20) zeros=20; } else { fracpart=0; zeros=0; } old_fracpart=fracpart; if (fracpart >= mypow10 (max)) { intpart++; fracpart -= mypow10 (max); } #ifdef DEBUG_SNPRINTF dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); #endif /* Convert integer part */ do { iconvert[iplace++] = (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; intpart = (intpart / 10); } while(intpart && (iplace < 20)); if (iplace == 20) iplace--; iconvert[iplace] = 0; /* Convert fractional part */ do { fconvert[fplace++] = (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; fracpart = (fracpart / 10); } while(fracpart && (fplace-zeros < 20)); for (i=0;(i 0)) { if (signvalue) { dopr_outch (buffer, currlen, maxlen, signvalue); --padlen; signvalue = 0; } while (padlen > 0) { dopr_outch (buffer, currlen, maxlen, '0'); --padlen; } } while (padlen > 0) { dopr_outch (buffer, currlen, maxlen, ' '); --padlen; } if (signvalue) dopr_outch (buffer, currlen, maxlen, signvalue); while (iplace > 0) dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); if (old_fracpart!=0) { /* * Decimal point. This should probably use locale to find the correct * char to print out. */ dopr_outch (buffer, currlen, maxlen, '.'); while (fplace > 0) dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); while (zpadlen > 0) { dopr_outch (buffer, currlen, maxlen, '0'); --zpadlen; } while (padlen < 0) { dopr_outch (buffer, currlen, maxlen, ' '); ++padlen; } } } static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) { if (*currlen < maxlen) buffer[(*currlen)++] = c; } int myvsnprintf (char *str, size_t count, const char *fmt, va_list args) { str[0] = 0; dopr(str, count, fmt, args); return(mystrlen(str)); } /* VARARGS3 */ #ifdef HAVE_STDARGS int mysnprintf (char *str,size_t count,const char *fmt,...) #else int mysnprintf (va_alist) va_dcl #endif { #ifndef HAVE_STDARGS char *str; size_t count; char *fmt; #endif VA_LOCAL_DECL; VA_START (fmt); VA_SHIFT (str, char *); VA_SHIFT (count, size_t ); VA_SHIFT (fmt, char *); (void) myvsnprintf(str, count, fmt, ap); VA_END; return(mystrlen(str)); } #ifdef WIN32 /* VARARGS3 */ #ifdef HAVE_STDARGS int __cdecl msgboxprintf (FILE * f,const char *fmt,...) #else int __cdecl msgboxprintf (va_alist) va_dcl #endif { #ifndef HAVE_STDARGS FILE* f; char *fmt; #endif char text[1024]; VA_LOCAL_DECL; VA_START (fmt); VA_SHIFT (text, char *); VA_SHIFT (1024, size_t ); VA_SHIFT (fmt, char *); (void) myvsnprintf(text, 1024, fmt, ap); VA_END; MessageBox(NULL,text,"white_dune",MB_ICONINFORMATION); return(mystrlen(text)); } #endif static void fmtefp (char *buffer, size_t *currlen, size_t maxlen, long double evalue, int min, int max, int flags) { long double fvalue; int exp; if (evalue==0) dopr_outch(buffer, currlen, maxlen, '0'); else { exp=floor(log(fabs(evalue))/log(10.0)); if ((exp>2) || (exp<-1)) { fvalue=evalue*pow(10.0,-(double)exp); fmtfp (buffer, currlen, maxlen, fvalue, min, max, flags); dopr_outch(buffer, currlen, maxlen, 'e'); fmtint (buffer,currlen, maxlen,exp,10,min,max,flags); } else fmtfp (buffer, currlen, maxlen, evalue, min, max, flags); } } int mywritestr(int filedes,const char *str) { int position_written; int restlength; restlength=mystrlen(str); position_written=0; while (restlength>0) { int writereturn; if ( (writereturn = write(filedes,str+position_written,restlength)) < 0 ) return(-1); restlength=restlength-writereturn; position_written=position_written+writereturn; } return(0); } /* VARARGS3 */ #ifdef HAVE_STDARGS int mywritef (int filedes,const char *fmt,...) #else int mywritef (va_alist) va_dcl #endif { char str[8000]; #ifndef HAVE_STDARGS int filedes; char *fmt; #endif VA_LOCAL_DECL; VA_START (fmt); VA_SHIFT (filedes, int); VA_SHIFT (fmt, char *); (void) myvsnprintf(str,8000, fmt, ap); VA_END; /* error, myvsnprintf possibly truncated data */ if (mystrlen(str)>=7999) return(-2); return mywritestr(filedes,str); } /* * strncpy that copies n bytes and can write zero to n+1-th byte * use this when you want to copy exact n bytes (or less) */ char* mystrncpy_danger(char* str1,const char *str2,int n) { char* ret; ret = strncpy(str1,str2,n); ret[n] = 0; return ret; } /* * strncpy that copies n bytes and then write zero to n-th byte * use this when you want to copy less than n bytes */ char* mystrncpy_secure(char* str1,const char *str2,int n) { char* ret; ret = strncpy(str1,str2,n); ret[n-1] = 0; return ret; } #ifdef TEST_SNPRINTF #ifndef LONG_STRING #define LONG_STRING 1024 #endif #include int main (void) { char buf1[LONG_STRING]; char buf2[LONG_STRING]; char *fp_fmt[] = { "%-1.5f", "%g", "%f", "%-1.5f", "%1.5f", "%123.9f", "%10.5f", "% 10.5f", "%+22.9f", "%+4.9f", "%01.3f", "%4f", "%3.1f", "%3.2f", NULL }; float test_fp_nums[] = { -1, 1, 0.5, 0102.1, 1.0032, 203.9,-1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996, 0.9996, 1.996, 1.003, 4.136, 0}; char *int_fmt[] = { "%-1.5d", "%1.5d", "%123.9d", "%5.5d", "%10.5d", "% 10.5d", "%+22.33d", "%01.3d", "%4d", NULL }; long int_nums[] = { -1, 134, 91340, 341, 0203, 0}; int x, y; int fail = 0; int num = 0; printf ("Testing snprintf format codes against system sprintf...\n"); for (x = 0; fp_fmt[x] != NULL ; x++) { for (y = 0; test_fp_nums[y] != 0 ; y++) { mysnprintf (buf1, sizeof (buf1), fp_fmt[x], test_fp_nums[y]); sprintf (buf2, fp_fmt[x], test_fp_nums[y]); if (strcmp (buf1, buf2)) { printf("snprintf %lf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n", test_fp_nums[y],fp_fmt[x], buf1, buf2); fail++; } } mysnprintf (buf1, sizeof (buf1), fp_fmt[x],0); sprintf (buf2, fp_fmt[x], 0); if (strcmp (buf1, buf2)) { printf("snprintf %lf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n", test_fp_nums[y],fp_fmt[x], buf1, buf2); fail++; } num++; } for (x = 0; int_fmt[x] != NULL ; x++) for (y = 0; int_nums[y] != 0 ; y++) { mysnprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]); sprintf (buf2, int_fmt[x], int_nums[y]); if (strcmp (buf1, buf2)) { printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n", int_fmt[x], buf1, buf2); fail++; } num++; } printf ("%d tests failed out of %d.\n", fail, num); } #endif /* SNPRINTF_TEST */