/*
#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 <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#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.
*
* <special> ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "."
* | "," | ";" | ":" | "@" """ | 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:
*/
syntax highlighted by Code2HTML, v. 0.9.1