/* #ident "@(#)smail/src:RELEASE-3_2_0_121:string.c,v 1.80 2005/11/18 03:08:58 woods Exp" */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * Copyright (C) 1992 Ronald S. Karr * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * string.c: * miscellaneous string operations * * external functions: strcmpic, strncmpic, strip, strcolon, * is_string_in_list, strerror, strsysexit, * str_c_quote, str_printf, str_printf_va, xprintf, * dprintf, c_dequote, c_atol, base62, read_line, * str_cat, str_ncat, ivaltol, ltoival, copy, rcopy, * make_lib_fn */ #include "defs.h" #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef __STDC__ # include #else # include #endif #if defined(HAVE_UNISTD_H) # include #endif #include "smail.h" #include "alloc.h" #include "list.h" #include "smailarch.h" #include "main.h" #include "parse.h" #include "addr.h" #include "log.h" #include "smailstring.h" #include "dys.h" #include "extern.h" #include "exitcodes.h" #include "debug.h" #include "smailport.h" /* functions local to this file */ static char *bltoa __P((unsigned int, unsigned long)); /* * str2lower - convert a string to lower case.... */ char * str2lower(s) register char *s; { register char *sp; for (sp = s; *sp; sp++) { *sp = tolower((int) *sp); } return s; } /* * strcmpic - case independent strcmp function * * XXX rename and make optional to *BSD strcasecmp(3) */ int strcmpic(s1, s2) register char *s1, *s2; /* strings to be compared */ { register unsigned int c1, c2; /* temp */ /* Make sure that strings exist */ if ((s1 == NULL) || (s2 == NULL)) { return ((s1 == s2) ? 0 : ((s2 == NULL) ? 1 : -1)); } while (*s1 && *s2) { if ((c1 = tolower((int) *s1++)) != (c2 = tolower((int) *s2++))) { return (c1 - c2); } } /* * one or both chars must be `\0'. If only one is `\0', then * the other string is longer. */ return ((int) ((*s1) - (*s2))); } /* * strncmpic - case independent strcmp function * * XXX rename and make optional to *BSD strncasecmp(3) */ int strncmpic(s1, s2, n) register char *s1, *s2; /* strings to compare */ size_t n; /* compare up to this many chars */ { register unsigned int c1, c2; /* temp */ register size_t cnt = n; /* count of chars so far compared */ while (*s1 && *s2 && cnt > 0) { if ((c1 = tolower((int) *s1++)) != (c2 = tolower((int) *s2++))) { return (c1 - c2); } cnt--; /* count this character */ } /* * If we ran out of chars, then the string segments are equal, otherwise * one or both strings must have ended. In this case the subtraction * will show which one is shorter, if any. */ return (cnt ? ((int) ((*s1) - (*s2))) : 0); } /* * strip - destructively strip quotes and backslash characters from an address * * i.e. sed -E -e 's|[\. ]*| |g' -e 's|["\\]||g' * * Effectively equivalent to rfc822_unqoute_quoted_string() if we assume all * the FWS was already removed long ago (as it normally is). * * Returns TRUE if any change was made to storage pointed to by addr. */ int strip(addr) register char *addr; /* strip this address */ { int was_stripped = FALSE; /* TRUE if any stripping was done */ register char *p = addr; /* write pointer to addr */ register int c; /* read char in addr */ while ((c = *addr++)) { if (c == '\\') { /* skip to char after \ */ *p++ = *addr++; was_stripped = TRUE; } else if (c == '"') { /* don't copy quote char */ was_stripped = TRUE; } else { *p++ = c; } } *p++ = '\0'; /* end of string */ return was_stripped; } /* * chop -- (destructively) chop whitespace off the end of a string (by * replacing it with NULs) and return a pointer to the first non-whitespace * character at the beginning of the string. */ char * chop(s) char *s; { char *p; size_t len; if (!s || !*s) { return s; } while (isspace((int) *s)) { s++; } len = strlen(s); if (len) { p = s + len - 1; while (isspace((int) *p)) { *p-- = '\0'; } } return s; } /* * strcolon - step through string parts separated by colons * * when called with a string, return a copy of the first part of * the string up to, but excluding the first `:'. When called with * NULL return a copy of the next part of the previously passed string, * with each part separated by a colon `:'. * * return NULL if no more parts are left. * * strcolon is typically used in a loop on ':' separated names such as: * * for (p = strcolon(names); p; p = strcolon((char *)NULL)) { * ... do something with the name p ... * } * * the storage returned is reused by the next call, so if you wish to keep a * string around, you will need to copy it. */ char * strcolon(s) register char *s; /* string or NULL */ { static char *next = NULL; /* pointer to next ':' */ static char *region = NULL; /* region used to store result */ static size_t alloc = 0; /* alloc size of region */ if (!s) { s = next; if (s == NULL) { return NULL; } } next = strchr(s, ':'); if (next) { size_t len = (next - s); if (len >= alloc) { if (region == NULL) { region = xmalloc(len + 1); } else { region = xrealloc(region, len + 1); } alloc = len + 1; } if (len) { (void) memcpy(region, s, len); } region[len] = '\0'; next++; return region; } return s; } /* * is_string_in_list - return TRUE if string is in colon-separated list * * Case is not significant in comparisons. * * XXX might be nice to add ';' subfield handling ala match_ip() * (in which case this might best be moved to match.c) */ int is_string_in_list(string, list) register char *string; /* string to look for */ char *list; /* list of strings */ { register char *s; if (!string || !list) { return FALSE; } for (s = strcolon(list); s; s = strcolon((char *) NULL)) { s = chop(s); if (EQIC(string, s)) { return TRUE; } } return FALSE; } /* * is_number_in_list - return TRUE if number is in colon-separated list of * numbers, which may include ranges of numbers * * XXX should this function support negated items? Some form of wildcard? * * XXX might be nice to add ';' subfield handling ala match_ip() * (in which case this might best be moved to match.c) */ int is_number_in_list(number, list) unsigned int number; /* number to look for */ char *list; /* list of numbers and number ranges */ { register char *s; if (!list) { return FALSE; } for (s = strcolon(list); s; s = strcolon((char *) NULL)) { char *range_sep; s = chop(s); range_sep = strchr(s, '-'); /* * Note: the way the check for valid ranges is done should avoid having * to deal with atoi() returning a negative value.... */ if (range_sep && (range_sep == s || !range_sep[1])) { /* XXX broken range -- should return/log an error message! */ return FALSE; } else if (range_sep && range_sep != s && range_sep[1]) { /* check if within range */ if ((unsigned int) atoi(s) <= number && number <= (unsigned int) atoi(range_sep + 1)) { return TRUE; } else { return FALSE; } } else if ((unsigned int) atoi(s) == number) { return TRUE; } } return FALSE; } #if 0 /* not needed yet */ /* * is_user_in_list - return TRUE if username is in colon-separated list * * given a username value (which may be a C-style integer) and a colon * separated list of usernames, which may include C-style numbers, ranges of * numbers, and usernames, return TRUE if the given username is in the list, * else FALSE. */ int is_user_in_list(user, list) register char *user; /* user to look for */ char *list; /* list of users */ { register char *s; if (!user || !list) { return FALSE; } /* XXX convert user to user-ID */ for (s = strcolon(list); s; s = strcolon((char *) NULL)) { s = chop(s); /* XXX convert s to an ID and compare integer IDs */ if (EQIC(user, s)) { return TRUE; } } return FALSE; } #endif /* 0 */ #if 0 /* not needed yet */ /* * is_group_in_list - return TRUE if groupname is in colon-separated list * * given a groupname value (which may be a C-style integer) and a colon * separated list of groupnames, which may include C-style numbers, ranges of * numbers, and groupnames, return TRUE if the given groupname is in the list, * else FALSE. */ int is_group_in_list(group, list) register char *group; /* group to look for */ char *list; /* list of groups */ { register char *s; if (!group || !list) { return FALSE; } /* XXX convert group to group-ID */ for (s = strcolon(list); s; s = strcolon((char *) NULL)) { s = chop(s); /* XXX convert s to an ID and compare integer IDs */ if (EQIC(group, s)) { return TRUE; } } return FALSE; } #endif /* 0 */ /* * quote - quote the string pointed to by value, IFF required. * * If string requires quoting, then quote it using str_c_quote(), else return * a copy of the original string. * * A string "requires quoting" if it starts with a non-alphanumeric, or * contains any characters other than alphanumerics, periods, hyphens, * underscores, slashes, or "@" chars. * * If a string is already quoted then it is assumed all the right escape * sequences have been inserted as well. * * The storage pointed to by the return value is reused on subsequent calls. * * XXX this syntax handling is bogus -- we should have various syntax options * and perhaps factor out the "needs quote" and "is quoted" checks so that we * can safely quote identifiers (i.e. with all whitespace escaped), variable * values (as lists or not, etc.), as well as addresses. * * e.g. we could use rfc821_is_dot_string() to check if a local part needs * quoting. * * ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "." * | "," | ";" | ":" | "@" """ | the control * characters (ASCII codes 0 through 31 inclusive and * 127) */ char * quote(value, dospaces, is_list) register char *value; int dospaces; int is_list; { static struct str wkstr; static int initialised = FALSE; int quote_needed; register char *ptr; register int chr; if (!value) { return NULL; /* callers expect NULL == NULL */ } if (!initialised) { STR_INIT(&wkstr); initialised = TRUE; } else { STR_CLEAR(&wkstr); STR_CHECK(&wkstr); } /* empty strings are just empty quotes... */ if (!*value) { STR_CAT(&wkstr, "\"\""); return STR(&wkstr); } /* * Check if quoting is necessary */ /* is the first char alphanumeric or a dash or a slash? */ if (isalnum((int) *value) || *value == '-' || *value == '/') { quote_needed = FALSE; /* assume for the best... */ } else { quote_needed = TRUE; } /* * are the rest of the characters in range? * * Note we have to find the end of the string anyway and always doing this * whole check is not much more expensive than trying to be smarter but * burning more code space with two different loops. */ for (ptr = value+1; (chr = *ptr); ptr++) { if (!((isalnum((int) chr)) || (chr == '_') || (chr == '-') || (chr == '.') || (chr == '/') || (chr == '@'))) { quote_needed = TRUE; } } /* * Is it already quoted? * * Use the pointer to the end found with the loop above. */ if (*value == '"' && *(ptr - 1) == '"' && *(ptr - 2) != '\\') { /* XXX should check that all escaping was done properly */ STR_CAT(&wkstr, value); return STR(&wkstr); } if (quote_needed) { STR_NEXT(&wkstr, '"'); /* Leading quotes */ } str_c_quote(&wkstr, value, dospaces, is_list); if (quote_needed) { STR_NEXT(&wkstr, '"'); /* Trailing quotes */ } STR_NEXT(&wkstr, '\0'); /* Trailing NUL */ return STR(&wkstr); } /* * quote_args - take an args vector and produce a nicely C-style string with * each sub-string quoted, if necessary. * * WARNING: returns a pointer to private storage, so take care to COPY_STRING() * the result (and free up the copies after use) if more than one call must be * made for a function parameter list. */ char * quote_args(argv) char *argv[]; { static struct str wkstr; static int initialised = FALSE; if (!initialised) { STR_INIT(&wkstr); initialised = TRUE; } else { STR_CLEAR(&wkstr); STR_CHECK(&wkstr); } while (*argv) { STR_CAT(&wkstr, quote(*argv++, FALSE, FALSE)); STR_CAT(&wkstr, " "); } STR_PREV(&wkstr); /* nuke trailing space */ STR_NEXT(&wkstr, '\0'); /* add trailing NUL */ return STR(&wkstr); } /* * str_c_quote - return in the already initialized struct string pointed to by * wkstr a copy of value after escaping any backslashes or double quote * characters with a backslash, quoting non-space whitespace with with C-style * backslash character constants, and turning unprintable characters into octal * escapes. * * Also escapes spaces as '\s' if dospaces is TRUE. * * NOTE: This function does not have an orthogonal API to that c_dequote() * despite its name. This function operates on a whole string at a time, * whereas c_dequote() only processes a single escape sequence. */ void str_c_quote(wkstr, value, dospaces, is_list) struct str *wkstr; register char *value; int dospaces; int is_list; { int chr; int last_chr = '\0'; int in_curly = 0; STR_CHECK(wkstr); while ((chr = *value++)) { switch (chr) { case ' ': if (dospaces) { STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 's'); } else { STR_NEXT(wkstr, chr); } break; case '{': in_curly++; STR_NEXT(wkstr, chr); break; case '}': in_curly--; STR_NEXT(wkstr, chr); break; case ':': if (is_list && !in_curly && last_chr != '\\' && (*value != ':')) { STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, '\n'); STR_NEXT(wkstr, '\t'); } STR_NEXT(wkstr, ':'); break; case '\\': case '"': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, chr); break; case BELL: STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'a'); break; case '\b': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'b'); break; case '\f': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'f'); break; case '\n': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'n'); break; case '\r': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'r'); break; case '\t': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 't'); break; case '\v': STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, 'v'); break; default: if (isprint((int) chr) && isascii((int) chr)) { STR_NEXT(wkstr, chr); } else { STR_NEXT(wkstr, '\\'); STR_NEXT(wkstr, (unsigned char) (((unsigned int) (unsigned char) chr >> 6) & 03) + '0'); STR_NEXT(wkstr, (unsigned char) (((unsigned int) (unsigned char) chr >> 3) & 07) + '0'); STR_NEXT(wkstr, (chr & 07) + '0'); } break; } last_chr = chr; } } /* * c_dequote - translate a \escape as C would within a string or char literal * with the addition that '\s' is a space character. * * Returns a pointer to the next character to fetch from p and the actual * character value in the integer storage pointed to by val. * * NOTE: This function is not orthogonal to str_c_quote() despite its name as * it operates only on one character at a time. */ char * c_dequote(p, val) register char *p; /* start point in string */ register int *val; /* put result char here */ { register int c; /* current character */ int cnt; switch (c = *p++) { case '\0': *val = '\0'; return p - 1; case 'a': case 'g': /* non-standard BELL */ *val = BELL; return p; case 'b': *val = '\b'; return p; case 'e': *val = '\033'; return p; case 'f': *val = '\f'; return p; case 'n': *val = '\n'; return p; case 'r': *val = '\r'; return p; case 's': *val = ' '; return p; case 't': *val = '\t'; return p; case 'v': *val = '\v'; return p; case 'x': case 'X': /* * x or X followed by up to three hex digits form a char */ cnt = 3; *val = 0; while (cnt && (isdigit((int) (c = *p++)) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { *val = (*val * 16) + (isdigit((int) c)? c - '0': isupper((int) c)? c - 'A': c - 'a'); } return p - 1; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* handle the normal, octal, case of chars specified numerically */ cnt = 2; /* two more digits, three total */ *val = c - '0'; while (cnt && (c = *p++) >= '0' && c <= '7') { *val = (*val * 8) + c - '0'; } return p - 1; default: *val = c; return p; } } /* * str_printf - highly simplified printf to a dynamic string * str_printf_va - ditto, but taking a va_list parameter * * note that of the standard expansion controls we only support %s, %d, %ld, * %u, %lu, %o, %lo, %x, %lx and %c. * * However we also support: * * %p which prints a hexadecimal representation of a POINTER_TYPE value * * %z which inserts a null byte (and %N for backward compatibility) * * %S is akin to %.*s where a maximum length parameter is given before the * %string, but the length parameter is of type size_t, not int. * * %v and %V which are like %s and %S but also turn unprintable characters into * C-style quotes using str_c_quote(..., dospaces=FALSE, ...). * * %q and %Q are like %v and %V but use str_c_quote(..., dospaces=TRUE, ...). * * Zero padding and field adjustment, etc. are _not_ supported. * * XXX implement "%a" for producing RFC 821 compliant addresses. * * XXX implement "%k" for producing double-quoted strings using quote(). * * XXX consider implementing "%f" for producing a decimal representations of a double */ #ifdef __STDC__ void str_printf(struct str *sp, char *fmt, ...) #else /*VARARGS*/ void str_printf(sp, fmt, va_alist) struct str *sp; /* append to this string */ char *fmt; /* printf-style format string */ va_dcl #endif { va_list ap; /* placeholder for varargs */ VA_START(ap, fmt); str_printf_va(sp, fmt, ap); va_end(ap); } void str_printf_va(sp, fmt, ap) register struct str *sp; /* append to this string */ register char *fmt; /* printf-style format string */ va_list ap; /* placeholder for varargs */ { register int c; /* current char in fmt */ register int islong; /* to handle %ld */ unsigned long int un; /* temp */ long int n; /* temp */ char *s; /* temp */ size_t len; /* for %S */ char *ofmt = fmt; /* original format string */ /* * loop on the format string copying into the string sp */ while ((c = *fmt++)) { if (c != '%') { STR_NEXT(sp, c); } else { if ((islong = (*fmt == 'l'))) { fmt++; } switch (c = *fmt++) { case '\0': STR_NEXT(sp, '%'); --fmt; break; case 'v': case 'q': case 's': if ((s = va_arg(ap, char *))) { if (c == 'v' || c == 'q') { str_c_quote(sp, s, ((c == 'q') ? TRUE : FALSE), FALSE); } else { STR_CAT(sp, s); } } else { STR_CAT(sp, "(null)"); } break; case 'V': case 'Q': case 'S': /* like printf("%.*s", ...), but param is size_t */ len = va_arg(ap, size_t); s = va_arg(ap, char *); if (s) { if (c == 'V' || c == 'Q') { char *cp = strncpy(xmalloc(len + 1), s, len); cp[len] = '\0'; str_c_quote(sp, cp, ((c == 'Q') ? TRUE : FALSE), FALSE); xfree(cp); } else { STR_NCAT(sp, s, len); } } else { STR_CAT(sp, "(empty)"); } break; case 'c': STR_NEXT(sp, va_arg(ap, int)); break; case 'o': un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int); STR_CAT(sp, bltoa(8, un)); break; case 'p': #if 0 assert(sizeof(POINTER_TYPE) == sizeof(unsigned long)); #endif un = va_arg(ap, POINTER_TYPE); STR_CAT(sp, "0x"); STR_CAT(sp, bltoa(16, un)); break; case 'x': un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int); STR_CAT(sp, bltoa(16, un)); break; case 'u': un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int); STR_CAT(sp, bltoa(10, un)); break; case 'd': n = islong ? va_arg(ap, long) : va_arg(ap, int); if (n < 0) { STR_NEXT(sp, '-'); n = -n; } STR_CAT(sp, bltoa(10, (unsigned long) n)); break; case 'N': /* how to insert a nul byte */ case 'z': STR_NEXT(sp, '\0'); break; /* FIXME: ???? */ case '%': STR_NEXT(sp, '%'); break; default: /* This is not terribly helpful */ write_log(WRITE_LOG_PANIC, "str_printf_va(): invalid format specifier: '%%%c' in '%v'", c, ofmt); STR_NEXT(sp, '%'); STR_NEXT(sp, c); /* * too bad we don't know how many bytes to skip on the stack * for this -- this will probably cause a core dump very soon * if there are any more format specifiers, especially any that * will dereference pointers but end up retrieving parmaeters * that are not pointers.... */ break; } } } /* * add a trailing null, but make sure the next character will * overwrite it. */ STR_NEXT(sp, '\0'); STR_PREV(sp); } /* * bltoa - convert long integer to string representation in given base * * standard bug about pointing to static data. */ static char * bltoa(base, n) register unsigned base; /* base for conversion */ register unsigned long n; /* number to convert */ { static char buf[BITS_PER_INT + 1]; /* plenty big for any base */ register char *p = buf + sizeof(buf); /* start at end and move backward */ register int i; *--p = '\0'; /* terminate string */ if (n == 0) { /* special case, 0 is just "0" */ *--p = '0'; return p; } /* get more significant digits until no more are required */ while (n > 0) { /* allow for bases up to 16 */ i = (int) (n % base); n /= base; *--p = "0123456789abcdef"[i]; } return p; } /* * xprintf - str_print to a region, returning pointer to xmalloc()ed region * that should be xfree()ed when finished with.... */ #ifdef __STDC__ char * xprintf(char *fmt, ...) #else /*VARARGS1*/ char * xprintf(fmt, va_alist) char *fmt; /* str_printf-style format string */ va_dcl #endif { struct str str; va_list ap; STR_INIT(&str); VA_START(ap, fmt); str_printf_va(&str, fmt, ap); va_end(ap); STR_NEXT(&str, '\0'); STR_DONE(&str); return STR(&str); } /* * dprintf - debugging printf to a file * * This allows the DEBUGx() macros to explicitly follow the convention that * printing a NULL string pointer displays "(null)". The System V * printf() does not follow this convention, and thus cannot be used. */ #ifdef __STDC__ int dprintf(FILE *file, char *fmt, ...) #else /* VARARGS2 */ /* XXX using two fixed args with va_list is potential trouble.... */ int dprintf(file, fmt, va_alist) FILE *file; char *fmt; va_dcl #endif { static struct str str; static int inited = FALSE; va_list ap; if (! inited) { inited = TRUE; STR_INIT(&str); } else { STR_CHECK(&str); STR_CLEAR(&str); } VA_START(ap, fmt); str_printf_va(&str, fmt, ap); va_end(ap); STR_NEXT(&str, '\0'); return fputs(STR(&str), file); } /* * c_atol - convert an ascii string to a long, C-style. * * Handle the forms [+-]?0[0-7]* [+-]?0x[0-9a-f]* and [+-]?[1-9][0-9]*. * an optional suffix of one of the letters: k, K, or M is allowed, as * a multplier by 1024 or 1048576. If the string given is malformed, * error will be set to an error message. * * WARNING!!! Does not detect overflow! */ long c_atol(input, error) register char *input; /* input string */ char **error; /* return error message here */ { char *input_string = input; long val = 0; int sign = 1; int digval; int base = 10; /* handle a leading sign character */ if (*input == '-') { sign = -1; input++; } else if (*input == '+') { input++; } /* what is the base */ if (*input == '0') { input++; base = 8; if (*input == 'x' || *input == 'X') { base = 16; input++; } } /* decode the number */ while (*input) { if (isdigit((int) *input)) { digval = *input++ - '0'; } else { if (islower((int) *input)) { digval = *input++ - 'a' + 10; } else if (isupper((int) *input)) { digval = *input++ - 'A' + 10; } else { break; } } if (digval >= base) { --input; break; } val = val*base + digval; } /* set the correct sign */ val *= sign; /* is there an optional multiplier? */ if (*input == 'k' || *input == 'K') { input++; val *= 1024; } if (input && *input == 'M') { input++; val *= 1024 * 1024; } /* there should be nothing left of the input string at this point */ if (error && *input) { *error = xprintf("invalid number: %s", input_string); } else if (error) *error = NULL; return val; } /* * base62 - convert a long integer into ASCII base 62 * * uses upper and lowercase letters, plus numbers. * always returns a string of exactly 6 characters, plus a null byte. * * returns a static area which is reused for each call. * * XXX should be merged into bltoa() */ char * base62(val) unsigned long val; { static char base62_chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static char buf[7]; register char *p = buf + sizeof(buf) - 1; *p = '\0'; while (p > buf) { *--p = base62_chars[val % 62]; val /= 62; } return buf; } /* * read_line - read a line from an input stream and return a pointer to it * * returns a storage area which may be reused for subsequent calls. * * returns NULL after EOF has been reached and nothing more can be read. */ char * read_line(f) register FILE *f; { static int inited = FALSE; static struct str line; register int c; if (! inited) { inited = TRUE; STR_INIT(&line); } else { STR_CHECK(&line); STR_CLEAR(&line); } while ((c = getc(f)) != EOF) { STR_NEXT(&line, c); if (c == '\n') { break; } } if (STR_LEN(&line) == 0) { return NULL; } STR_NEXT(&line, '\0'); return STR(&line); } /* * str_cat - concatenate a C string onto the end of a dynamic string (thus * NUL-terminating it), but leave the NUL un-accounted for in the * dynamic string's current length so that futher str_cat(), * str_ncat(), or STR_NEXT(), etc. operations simply overwrite it. * * str_ncat - concatenate a C string with a given length onto the * end of a dynamic string * * Grow the storage region as necessary. */ void str_cat(sp, s) register struct str *sp; const char *s; { str_ncat(sp, s, (size_t) strlen(s) + 1); STR_PREV(sp); } void str_ncat(sp, s, n) register struct str *sp; const char *s; size_t n; { if (STR_LEN(sp) + n > STR_ALLOCSZ(sp)) { /* * need to expand the region: * bump up to a sufficiently large region which is a multiple * of STR_BUMP (except for a pointer). Allow at least 10 free * chars in the region, for future expansion. * * XXX why the removal of a pointer's worth? */ STR_ALLOCSZ(sp) = ((STR_LEN(sp) + n + STR_BUMP + 10) & (size_t) (~(STR_BUMP - 1))) - sizeof(char *); STR(sp) = xrealloc(STR(sp), STR_ALLOCSZ(sp)); } /* copy string to the end of the region, copy any NUL byte(s) too */ (void) memcpy(STR(sp) + STR_LEN(sp), s, n); STR_LEN(sp) += n; } /* * ivaltol - convert time interval string into a long integer * * Take a string defining an interval and convert it into seconds. An * interval is the sum of seconds desribed by terms formed from a * decimal integer and a suffix. The suffix gives a multiplier for * the term and can be one of 'y' (years), 'w' (weeks), 'd' (days), * 'h' (hours), 'm' (minutes) or 's' (seconds). If the last term does * not have a suffix, 's' is assumed. For example, an interval string * of "1h10m3s" would represent one hour, ten minutes and 30 seconds * and would be converted to the equivalent number of seconds. * * A negative integer is returned for illegal intervals. * * Interval multipliers may be upper- or lower-case and may be spelled out in * full although only the first character is used. * * No whitespace is allowed. * * Note: interval values are often only useful if they fit within an * unsigned integer, so callers of this routine should do range * checking to make sure the value returned is not too large. * * XXX does not validate format or detect errors. * * XXX Once returned a long int, thus the name */ time_t ivaltol(s) register char *s; /* string containing queue interval */ { long ret = 0; /* return value */ long cur = 0; /* value of current part */ while (*s) { switch (*s++) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (cur >= ((LONG_MAX - ret) / 10)) { return -1L; } cur = cur * 10 + s[-1] - '0'; break; case 'y': /* years = 365.24 days */ case 'Y': if (cur >= ((LONG_MAX - ret) / ((60*60*24*365)+1))) { return -1L; } ret += (cur * 60*60*24*365) + (cur * 60*60*24*24)/100; cur = 0; while (isalpha(*s)) { s++; } break; case 'w': /* weeks */ case 'W': if (cur >= ((LONG_MAX - ret) / (60*60*24*7))) { return -1L; } ret += cur * 60*60*24*7; cur = 0; while (isalpha(*s)) { s++; } break; case 'd': /* days */ case 'D': if (cur >= ((LONG_MAX - ret) / (60*60*24))) { return -1L; } ret += cur * 60*60*24; cur = 0; while (isalpha(*s)) { s++; } break; case 'h': /* hours */ case 'H': if (cur >= ((LONG_MAX - ret) / (60*60))) { return -1L; } ret += cur * 60*60; cur = 0; while (isalpha(*s)) { s++; } break; case 'm': /* minutes */ case 'M': if (cur >= ((LONG_MAX - ret) / 60)) { return -1L; } ret += cur * 60; cur = 0; while (isalpha(*s)) { s++; } break; case 's': /* seconds */ case 'S': if (cur > (LONG_MAX - ret)) { return -1L; } ret += cur; cur = 0; while (isalpha(*s)) { s++; } break; default: return -1L; /* illegal modifier */ } } if ((ret + cur) > LONG_MAX) { return -1L; } return (ret + cur); /* all done */ } /* * ltoival - convert long integer into a more human-friendly time interval string * * Break down an interval into weeks/days/hours/minutes/seconds, * expressing it as it might have been input to ivaltol. * * XXX once accepted a long int, thus its name.... */ char * ltoival(n) time_t n; { static char ret[BUFSIZ]; /* XXX for 64-bit: "292471208677yearsNNweeksNNdaysNNhoursNNminutesNNseconds" */ static char *units[] = { /* subinterval names */ "second", "minute", "hour", "day", "week" }; long vals[(sizeof(units) / sizeof(*units))];/* values of each subinterval */ int i; /* will go negative */ if (n < 0) { return NULL; } if (n == 0) { return "0"; } vals[0] = (n % 60); /* 's'econd(s) */ n /= 60; vals[1] = (n % 60); /* 'm'minute(s) */ n /= 60; vals[2] = (n % 24); /* 'h'hour(s) */ n /= 24; vals[3] = (n % 7); /* 'd'day(s) */ n /= 7; vals[4] = n; /* 'w'eek(s) */ ret[0] = '\0'; for (i = (int) (sizeof(units) / sizeof(*units)) - 1; i >= 0; --i) { if (vals[i]) { sprintf(ret + strlen(ret), "%ld%s%s", vals[i], units[i], (vals[i] > 1) ? "s" : ""); } } return ret; } /* * copy - copy a NUL-terminated string */ char * copy(s) char *s; { return rcopy(s, s + strlen(s)); } /* * rcopy - copy a string from start to end+1, creating a NUL-terminated result. */ char * rcopy(s, e) char *s, *e; { char *p; size_t n; n = (e - s); p = xmalloc(n + 1); (void) memcpy(p, s, n); p[n] = '\0'; return p; } /* * make_lib_fn - build a filename relative to the smail lib directory * * If the filename begins with '/' or is "-" return a verbatim copy of it. * * Return NULL if smail_lib_dir is undefined, or if the given * filename is NULL. */ char * make_lib_fn(fn) char *fn; /* filename to expand */ { if (fn == NULL) { return NULL; } if (fn[0] == '/' || (fn[0] == '-' && fn[1] == '\0')) { return COPY_STRING(fn); } if (smail_lib_dir == NULL) { return NULL; } return xprintf("%s/%s", smail_lib_dir, fn); } /* * make_util_fn - build a filename relative to the smail util directory * * If the filename begins with '/' or is "-" return a verbatim copy of it. * * Return NULL if smail_util_dir is undefined, or if the given * filename is NULL. */ char * make_util_fn(fn) char *fn; /* filename to expand */ { if (fn == NULL) { return NULL; } if (fn[0] == '/' || (fn[0] == '-' && fn[1] == '\0')) { return COPY_STRING(fn); } if (smail_util_dir == NULL) { return NULL; } return xprintf("%s/%s", smail_util_dir, fn); } /* * Local Variables: * c-file-style: "smail" * End: */