ftp.nice.ch/pub/next/unix/mail/smail3.1.20.s.tar.gz#/smail3.1.20/src/expand.c

This is expand.c in view mode; [Download] [Up]

/* @(#)src/expand.c	1.2 24 Oct 1990 05:22:50 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * expand.c:
 *	expand filenames used by directors.
 *
 *	external functions:  expand_string, build_cmd_line
 */
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include "defs.h"
#include "smail.h"
#include "addr.h"
#include "transport.h"
#include "log.h"
#include "alloc.h"
#include "dys.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "debug.h"
# include "extern.h"
#endif

/* library functions */
extern long time();

/* functions local to this file */
static char **build_argv();
static char *substitute();
static char *lc_fold();
static char *uc_fold();
static char *strip_fold();
#ifndef NODEBUG
static void bad_subst();
#endif


/*
 * expand_string - expand a string containing parts to be expanded
 *
 * This function does ~user and ~/ expansion and also performs expansions
 * of the form $name or ${name}.  See substitute() for the possible
 * substitutions.
 *
 * If `addr' is NULL, then a dummy addr structure is formed with all
 * items NULL except for home and user, which are taken from the
 * arguments to expand_string().
 *
 * return NULL on error attempting expansion.  The area returned may
 * be reused on subsequent calls.  If the caller wishes to retain the
 * returned data, it should be copied elsewhere.
 */
char *
expand_string(string, addr, home, user)
    register char *string;		/* unexpanded string */
    struct addr *addr;			/* addr structure with values */
    char *home;				/* home directory */
    char *user;				/* user name for $user */
{
    char *save_string = string;		/* save pointer to start of string */
    static struct str str;		/* build strings here */
    static int inited = FALSE;		/* TRUE if str inited */
    int modified = FALSE;		/* TRUE if any expansion ocured */
    static struct addr *dummy_addr = NULL; /* dummy addr for home and user */

    DEBUG3(DBG_DRIVER_HI, "expand_string(%s, %s, %s) called\n",
	   string, home, user);
    if (! inited) {
	STR_INIT(&str);
	inited = TRUE;
    } else {
	STR_CHECK(&str);
	str.i = 0;
    }

    if (addr == NULL) {
	/* no addr structure given, setup a dummy one */
	if (dummy_addr == NULL) {
	    dummy_addr = alloc_addr();
	}
	addr = dummy_addr;
	addr->home = home;
	addr->next_addr = user;
    }

    if (string[0] == '~') {
	/* do some kind of twiddle expansion */
	if (string[1] == '/') {
	    /* ~/ turns into home/ */
	    if (addr->home) {
		modified = TRUE;
		string++;
		STR_CAT(&str, addr->home);
	    } else {
		/* no home directory, so ~/ is not valid */
		DEBUG(DBG_DRIVER_MID, "no home directory, ~/ invalid\n");
		return NULL;
	    }
	} else {
	    /* ~user turns into home director for the given user */
	    char *p = string + 1;
	    struct passwd *pw;
	    extern struct passwd *getpwbyname();

	    while (*string && *string != '/') {
		string++;
	    }
	    if (*string) {
		*string = '\0';
		pw = getpwbyname(p);
		*string = '/';
	    } else {
		pw = getpwbyname(p);
	    }
	    if (pw == NULL) {
		/* ~user but username isn't valid */
		DEBUG1(DBG_DRIVER_MID, "user not found, ~%s invalid\n", p);
		return NULL;
	    }
	    modified = TRUE;
	    STR_CAT(&str, pw->pw_dir);
	}
    }

    /*
     * we have the silly ~ business out of the way, now
     * get all of the rest of the silly business out of the way
     */
    while (*string) {
	if (*string == '$') {
	    /* do a $-substitution */
	    string++;
	    if (*string == '{') {
		/*
		 * handle expansions of the form ${name}
		 */
		char *p = string + 1;
		char *new;

		while (*string && *string != '}') {
		    string++;
		}
		if (*string == '\0') {
		    /* no matching } for the opening ${ */
		    return NULL;
		}
		new = substitute(addr, (char *)NULL, p, string - p);
		if (new) {
		    STR_CAT(&str, new);
		} else {
		    /* unrecognized substitution */
#ifndef NODEBUG
		    bad_subst(p, string - p);
#endif
		    return NULL;
		}
		string++;
	    } else {
		/*
		 * handle $name expansions
		 */
		char *p = string;
		char *new;

		while (*string && (isalnum(*++string) || *string == '_')) ;

		new = substitute(addr, (char *)NULL, p, string - p);
		if (new) {
		    STR_CAT(&str, new);
		} else {
		    /* unrecognized substitution */
#ifndef NODEBUG
		    bad_subst(p, string - p);
#endif
		    return NULL;
		}
	    }
	    modified = TRUE;
	} else {
	    /*
	     * regular character, copy it into the result
	     */
	    STR_NEXT(&str, *string++);
	}
    }

    if (!modified) {
	/*
	 * no expansion needed to be done, just return the old string
	 */
	DEBUG1(DBG_DRIVER_HI, "unmodified, expand_string returns %s\n",
	       save_string);
	return save_string;
    }

    /*
     * expansion was done, finish up the string and return it
     */
    STR_NEXT(&str, '\0');
    DEBUG1(DBG_DRIVER_HI, "expand_string returns %s\n", str.p);
    return str.p;
}

#ifndef NODEBUG
/*
 * bad_subst - generate a debugging message for a failed substitution.
 *
 * note that we can't use "%*.*s" here since dprintf() is simple-minded.
 */
static void
bad_subst(var, len)
    char *var;
    int len;
{
    int c_save = var[len];
    var[len] = 0;
    DEBUG1(DBG_DRIVER_MID, "expand_string: no expansion for $%s\n", var);
    var[len] = c_save;
}
#endif


/*
 * build_cmd_line - build up an arg vector suitable for execv
 *
 * transports can call this to build up a command line in a standard
 * way.  Of course, if they want to they can build up a command line in
 * a totally different fashion.
 *
 * Caution: return value points to a region which may be reused by
 *	    subsequent calls to build_cmd_line()
 *
 * Notes on the replacement algorithm:
 *   o	Within a $( and $) pair, substitutions are made once for
 *	each address on the input list.
 *   o	Otherwise the substitution is made relative to the first
 *	address on the input list.
 *   o	Substitutions:
 *	o  grade ==> $grade
 *	o  addr->next_host ==> $host
 *	o  addr->next_addr ==> $addr or $user
 *	o  addr->home ==> $home or $HOME
 *	o  sender ==> $from or $sender
 *	o  file ==> $file
 *	o  message_id ==> $message_id
 *	o  unix_date() ==> $ctime
 *	o  get_arpa_date() ==> $date
 *	o  getpid() ==> $$
 *	o  uucp_name ==> $uucp_name
 *	o  visible_name ==> $visible_name
 *	o  primary_name ==> $primary_name
 *	o  VERSION ==> $version
 *	o  version() ==> $version_string
 *   o	single quotes, double quotes and backslash work as with /bin/sh
 *
 * return NULL for parsing errors, and load `error' with a message
 * explaining the error.
 */
char **
build_cmd_line(cmd, addr, file, error)
    register char *cmd;			/* input command line */
    struct addr *addr;			/* list of remote addresses */
    char *file;				/* substitution for $file */
    char **error;			/* error message */
{
    static struct str str;		/* generated region */
    static int inited = FALSE;		/* TRUE if str has been inited */
    char *mark;				/* temp mark in cmd line */
    char *new;				/* new string from substitute */
    int ct = 1;				/* count of args, at least one */
    int state = 0;			/* notes about parse state */
    struct addr *save_addr = addr;	/* replace addr from this after $) */
    char *save_cmd;			/* start of a $( ... $) group */
    int last_char = '\0';		/* hold last *cmd value */
#define DQUOTE	0x01			/* double quote in effect */
#define GROUP	0x02			/* $( ... $) grouping in effect */

    /* initialize for building up the arg vectors */
    if (! inited) {
	STR_INIT(&str);
	inited = TRUE;
    } else {
	STR_CHECK(&str);
	str.i = 0;
    }

    while (*cmd) {
	switch (*cmd) {
	case '\'':
	    /* after "'" copy literally to before next "'" char */
	    mark = index(cmd+1, '\'');
	    if (mark == NULL) {
		panic(EX_DATAERR, "no matching ' for cmd in transport %s",
		      addr->transport->name);
		/*NOTREACHED*/
	    }
	    *mark = '\0';		/* put null in for copy */
	    STR_CAT(&str, cmd+1);
	    *mark = '\'';		/* put quote back */
	    last_char = '\'';
	    cmd = mark;
	    break;

	case '\\':
	    /*
	     * char after \ is literal, unless in quote, in which case
	     * this is not so if the following char is not " or $ or \
	     */
	    if (*cmd++ == '\0') {
		*error = "\\ at end of command";
		return NULL;
	    }
	    if (!(state&DQUOTE) ||
		*cmd == '\\' || *cmd == '"' || *cmd == '$')
	    {
		STR_NEXT(&str, *cmd);
	    } else {
		STR_NEXT(&str, '\\');
		STR_NEXT(&str, *cmd);
	    }
	    last_char = '\\';
	    break;

	case '"':			/* double quote is a toggle */
	    state ^= DQUOTE;
	    last_char = '"';
	    break;

	case '$':			/* perform parameter substitution */
	    cmd++;
	    if (*cmd == '\0') {
		*error = "$ at end of command";
		return NULL;
	    }
	    if (*cmd == '(') {
		if (state&GROUP) {
		    *error = "recursive $( ... $)";
		    return NULL;
		}
		if (state&DQUOTE) {
		    *error = "$( illegal inside \"...\"";
		    return NULL;
		}
		save_cmd = cmd;
		state |= GROUP;
		break;
	    }
	    if (*cmd == ')') {
		if ((state&GROUP) == 0) {
		    *error = "no match for $)";
		    return NULL;
		}
		if (state&DQUOTE) {
		    *error = "$) illegal inside \"...\"";
		    return NULL;
		}
		if (!isspace(last_char)) {
		    /* end previous vector, create a new one */
		    ct++;
		    STR_NEXT(&str, '\0');
		}
		addr = addr->succ;
		if (addr) {
		    cmd = save_cmd;
		} else {
		    /* no more addrs to put in group */
		    addr = save_addr;
		    state &= ~GROUP;
		}
		last_char = ' ';	/* don't create an extra vector */
		break;
	    }
	    if (*cmd == '{') {
		mark = cmd+1;
		cmd = index(mark, '}');
		if (cmd == NULL) {
		    *error =  "no match for {";
		    return NULL;
		}
	    } else {
		/* use at least one char after $ for substitute name */
		mark = cmd;
		while (isalnum(*++cmd) || *cmd == '_') ;
		/* cmd now one beyond where it should be */
	    }
	    new = substitute(addr, file, mark, cmd - mark);
	    if (new == NULL) {
		int c_save = mark[cmd-mark];

		mark[cmd-mark] = '\0';
		/* TODO: This is a memory leak */
		*error = xprintf("bad substition: $s", mark);
		mark[cmd-mark] = c_save;
		return NULL;
	    }
	    STR_CAT(&str, new);
	    if (*cmd != '}') {
		--cmd;			/* correct next char pointer */
	    }
	    last_char = '$';
	    break;

	case ' ':			/* when not in a quote */
	case '\t':			/* white space separates words */
	case '\n':
	    if (state&DQUOTE) {
		STR_NEXT(&str, *cmd);
	    } else if (!isspace(last_char)) {
		/* end the previous arg vector */
		STR_NEXT(&str, '\0');
		ct++;			/* start a new one */
	    }
	    last_char = *cmd;
	    break;

	default:
	    STR_NEXT(&str, *cmd);
	    last_char = *cmd;
	}
	cmd++;				/* advance to next char */
    }
    if (state&DQUOTE) {
	*error = "no match for opening \"";
	return NULL;
    }
    if (state&GROUP) {
	*error = "no match for $(";
	return NULL;
    }

    if (isspace(last_char)) {
	--ct;				/* don't count just blanks */
    }
    STR_NEXT(&str, '\0');		/* null terminate the strings */
    return build_argv(str.p, ct);
}

/*
 * build_argv - build arg vectors from inline strings
 *
 * build_cmd_line produces chars with null characters separating
 * strings.  build_argv takes these chars and turns them into
 * an arg vector suitable for execv.
 *
 * Caution: the value returned by build_argv() points to a region
 *	    which may be reused on subsequent calls to build_argv().
 */
static char **
build_argv(p, ct)
    register char *p;			/* strings, one after another */
    register int ct;			/* count of strings */
{
    static char **argv = NULL;		/* reusable vector area */
    static int argc;
    register char **argp;

    if (argv == NULL) {
	argc = ct + 1;
	argv = (char **)xmalloc(argc * sizeof(*argv));
    } else {
	if (ct + 1 > argc) {
	    X_CHECK(argv);
	    argc = ct + 1;
	    argv = (char **)xrealloc((char *)argv, argc * sizeof(*argv));
	}
    }
    argp = argv;
    DEBUG(DBG_REMOTE_MID, "cmd =");
    while (ct--) {
	*argp++ = p;
	DEBUG1(DBG_REMOTE_MID, " '%s'", p);
	if (ct) {
	    while (*p++) ;		/* scan for next string */
	}
    }
    DEBUG(DBG_REMOTE_MID, "\n");
    *argp = NULL;			/* terminate vectors */
    return argv;
}

/*
 * substitute - relace a $paramater with its value
 *
 * panic on errors, see build_cmd_line for details.
 */
static char *
substitute(addr, file, var, len)
    struct addr *addr;			/* source for $host, $addr, $user */
    char *file;				/* source for $file */
    register char *var;			/* start of variable */
    register int len;			/* length of variable */
{
#define MATCH(x) (len==sizeof(x)-1 && strncmpic(var, x, sizeof(x)-1) == 0)

    if (strncmpic(var, "lc:", sizeof("lc:") - 1) == 0) {
	return lc_fold(substitute(addr, file, var + 3, len - 3));
    }
    if (strncmpic(var, "uc:", sizeof("uc:") - 1) == 0) {
	return uc_fold(substitute(addr, file, var + 3, len - 3));
    }
    if (strncmpic(var, "strip:", sizeof("strip:") - 1) == 0) {
	return strip_fold(substitute(addr, file, var + 6, len - 6));
    }
    if (strncmpic(var, "parent:", sizeof("parent:") - 1) == 0) {
	struct addr *parent = addr->parent;

	if (parent == NULL) {
	    return NULL;
	}
	return substitute(parent, file, var + 7, len - 7);
    }
    if (strncmpic(var, "top:", sizeof("top:") - 1) == 0) {
	struct addr *top = addr;

	while (top->parent) {
	    top = top->parent;
	}
	return substitute(top, file, var + 4, len - 4);
    }
    if (MATCH("grade")) {
	static char grade_str[2] = { 0, 0 };
	grade_str[0] = msg_grade;
	return grade_str;
    }
    if (MATCH("user") || MATCH("addr")) {
	return addr->next_addr;
    }
    if (MATCH("host")) {
	return addr->next_host;
    }
    if (MATCH("HOME") || MATCH("home")) {
	return addr->home;
    }
    if (MATCH("sender") || MATCH("from")) {
	return sender;
    }
    if (MATCH("file")) {
	return file;
    }
    if (MATCH("message_id") || MATCH("id")) {
	return message_id;
    }
    if (MATCH("ctime")) {
	return unix_date();
    }
    if (MATCH("date")) {
	/* get the current date in ARPA format */
	return get_arpa_date(time((long *)0));
    }
    if (MATCH("spool_date")) {
	/* get the spool date in ARPA format */
	return get_arpa_date(message_date());
    }
    if (MATCH("$") || MATCH("pid")) {
	static char pidbuf[10];

	(void) sprintf(pidbuf, "%d", getpid());
	return pidbuf;
    }
    if (MATCH("uucp_name")) {
	return uucp_name;
    }
    if (MATCH("visible_name") || MATCH("name")) {
	return visible_name;
    }
    if (MATCH("primary_name") || MATCH("primary")) {
	return primary_name;
    }
    if (MATCH("version")) {
	return version_number;
    }
    if (MATCH("version_string")) {
	return version();
    }
    if (MATCH("release_date") || MATCH("release")) {
	return release_date;
    }
    if (MATCH("patch_number") || MATCH("patch")) {
	return patch_number;
    }
    if (MATCH("patch_date")) {
	return patch_date;
    }
    if (MATCH("bat")) {
	return bat;
    }
    if (MATCH("compile_num") || MATCH("ld_num")) {
	static char s_compile_num[10];
	(void) sprintf(s_compile_num, "%d", compile_num);
	return s_compile_num;
    }
    if (MATCH("compile_date") || MATCH("ld_date")) {
	return compile_date;
    }
    if (MATCH("smail_lib_dir") || MATCH("lib_dir")) {
	return smail_lib_dir;
    }
    return NULL;			/* no match */
#undef	MATCH
}

/*
 * lc_fold - meta substitution to convert value to lower case
 */
static char *
lc_fold(value)
    register char *value;
{
    static int lc_size;			/* keep size of allocated region */
    int value_size;
    static char *lc = NULL;		/* retained malloc region */
    register char *p;			/* for scanning through lc */

    if (value == NULL) {
	return NULL;
    }
    value_size = strlen(value) + 1;

    /* get a region at least large enough for the value */
    if (lc == NULL) {
	lc = xmalloc(lc_size = value_size);
    } else if (value_size > lc_size) {
	X_CHECK(lc);
	lc = xrealloc(lc, lc_size = value_size);
    }
    p = lc;
    while (*value) {
	*p++ = lowercase(*value++);
    }
    *p = '\0';
    return lc;
}

/*
 * uc_fold - meta substitution to convert value to upper case
 */
static char *
uc_fold(value)
    register char *value;
{
    static int uc_size;			/* keep size of allocated region */
    int value_size;
    static char *uc = NULL;		/* retained malloc region */
    register char *p;			/* for scanning through lc */

    if (value == NULL) {
	return NULL;
    }
    value_size = strlen(value) + 1;

    /* get a region at least large enough for the value */
    if (uc == NULL) {
	uc = xmalloc(uc_size = value_size);
    } else if (value_size > uc_size) {
	X_CHECK(uc);
	uc = xrealloc(uc, uc_size = value_size);
    }
    p = uc;
    while (*value) {
	*p++ = uppercase(*value++);
    }
    *p = '\0';
    return uc;
}

/*
 * strip_fold - strip quotes and collapse spaces and dots
 *
 * strip quotes from the input string and collapse any sequence of one
 * or more white space and `.' characters into a single `.'.
 */
static char *
strip_fold(value)
    char *value;
{
    static int strip_size;		/* keep size of allocated region */
    int value_size;
    static char *strip_buf = NULL;	/* retained malloc region */
    register char *p;			/* for scanning through strip_buf */
    register char *q;			/* also for scanning strip_buf */

    if (value == NULL) {
	return NULL;
    }
    value_size = strlen(value) + 1;
    if (strip_buf == NULL) {
	strip_buf = xmalloc(strip_size = value_size);
    } else if (value_size > strip_size) {
	X_CHECK(strip_buf);
	strip_buf = xrealloc(strip_buf, strip_size = value_size);
    }
    (void) strcpy(strip_buf, value);
    (void) strip(strip_buf);

    /* q reads and p writes */
    p = q = strip_buf;
    /* strip initial -'s */
    while (*q == '-') {
	q++;
    }
    while (*q) {
	/* collapse multiple white-space chars and .'s into single dots */
	if (isspace(*q) || *q == '.') {
	    while (isspace(*++q) || *q == '.') ;
	    *p++ = '.';
	    continue;
	}
	*p++ = *q++;
    }
    *p = '\0';			/* finish off strip_buf */
    return strip_buf;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.