/************************************************************************
* Collection of library-worthy routines *
* *
* Copyright (c) 1990-1998, S.R. van den Berg, The Netherlands *
* Copyright (c) 1998-2001, Philip Guenther, The United States *
* of America *
* #include "../README" *
************************************************************************/
#ifdef RCS
static /*const*/char rcsid[]=
"$Id: goodies.c,v 1.74 2001/08/04 07:17:44 guenther Exp $";
#endif
#include "procmail.h"
#include "sublib.h"
#include "robust.h"
#include "shell.h"
#include "misc.h"
#include "pipes.h"
#include "common.h"
#include "acommon.h"
#include "cstdio.h"
#include "variables.h"
#include "goodies.h"
const char test[]="test";
const char*Tmnate,*All_args;
static const char*evalenv(skipping) /* expects the variable name in buf2 */
int skipping;
{ int j=buf2[0]-'0';
return skipping?(const char*)0: /* speed this up when skipping */
(unsigned)j>9?getenv(buf2):
!j?argv0:
j<=crestarg?restargv[j-1]:(const char*)0;
}
#define NOTHING_YET (-1) /* readparse understands a very complete */
#define SKIPPING_SPACE 0 /* subset of the standard /bin/sh syntax */
#define NORMAL_TEXT 1 /* that includes single-, double- and back- */
#define DOUBLE_QUOTED 2 /* quotes, backslashes and $subtitutions */
#define SINGLE_QUOTED 3
#define fgetc() (*fpgetc)() /* some compilers previously choked on it */
#define CHECKINC() (fencepost
NORMAL_TEXT) /* condition expansion code */
early_eof: nlog(unexpeof);
ready: if(got!=SKIPPING_SPACE||sarg) /* not terminated yet or sarg==2 ? */
*p++='\0';
Tmnate=p;
if(skipping&1)
{ nlog(exceededlb);setoverflow();
}
return skipping&1;
case '\\':
if(got==SINGLE_QUOTED)
break;
i=fgetc();
Quoted: switch(i)
{ case EOF:
goto early_eof; /* can't quote EOF */
case '\n':
continue; /* concatenate lines */
case '#':
if(got>SKIPPING_SPACE) /* escaped comment at start of word? */
goto noesc; /* apparently not, literally */
case ' ':case '\t':case '\'':
if(got==DOUBLE_QUOTED)
goto noesc;
case '"':case '\\':case '$':case '`':
goto nodelim;
case '}':
if(got<=NORMAL_TEXT&&bracelev||
got==DOUBLE_QUOTED&&bracelev>qbracelev)
goto nodelim;
}
if(got>NORMAL_TEXT)
noesc: *p++='\\'; /* nothing to escape, just echo both */
break;
case '`':
if(got==SINGLE_QUOTED)
goto nodelim;
for(startb=p;;) /* mark your position */
{ switch(i=fgetc()) /* copy till next backquote */
{ case '"':
if(got!=DOUBLE_QUOTED) /* missing closing backquote? */
break;
forcebquote: case EOF:case '`':
if(skipping)
*(p=startb)='\0';
else
{ int osh=sh;
*p='\0';
if(!(sh=!!strpbrk(startb,shellmetas)))
{ const char*save=sgetcp,*sAll_args;
sgetcp=p=tstrdup(startb);sAll_args=All_args;
if(readparse(startb,sgetc,0,0) /* overflow? */
#ifndef GOT_bin_test
||!strcmp(test,startb) /* oops, `test' found */
#endif
)strcpy(startb,p),sh=1;
All_args=sAll_args;
free(p);sgetcp=save; /* chopped up */
} /* drop source buffer, read from program */
startb=fromprog(
p=startb,startb,(size_t)(buf-startb+linebuf-3));
sh=osh; /* restore sh */
}
if(got!=DOUBLE_QUOTED)
{ i=0;startb=p;
goto simplsplit; /* split it up */
}
if(i=='"'||got<=SKIPPING_SPACE) /* missing closing ` ? */
got=NORMAL_TEXT;
p=startb;
goto loop;
case '\\':
switch(i=fgetc())
{ case EOF:nlog(unexpeof);
goto forcebquote;
case '\n':
continue;
case '"':
if(got!=DOUBLE_QUOTED)
break;
case '\\':case '$':case '`':
goto escaped;
}
*p++='\\';
}
escaped: CHECKINC();*p++=i;
}
case '"':
switch(got)
{ case DOUBLE_QUOTED:
if(qbracelevqbracelev)
{ bracelev--;
if(skipback&&bracelev==skipbracelev)
{ skipping-=2;p=skipback;skipback=0;startb=(char*)oldstartb;
got=bracegot;
goto closebrace;
}
continue;
}
goto nodelim;
case '#':
if(got>SKIPPING_SPACE) /* comment at start of word? */
break;
while((i=fgetc())!=EOF&&i!='\n'); /* skip till EOL */
goto ready;
case '$':
if(got==SINGLE_QUOTED)
break;
startb=buf2;
switch(i=fgetc())
{ case EOF:*p++='$';got=NORMAL_TEXT;
goto ready;
case '@':
if(got!=DOUBLE_QUOTED)
goto normchar;
if(!skipping) /* don't do it while skipping (braces) */
All_args=p;
continue;
case '{': /* ${name} */
while(EOF!=(i=fgetc())&&alphanum(i))
{ if(startb>=fencepost2)
startb=buf2+2,skipping|=1;
*startb++=i;
}
*startb='\0';
if(numeric(*buf2)&&buf2[1])
goto badsub;
startb=(char*)evalenv(skipping);
switch(i)
{ default:
goto badsub;
case ':':
switch(i=fgetc())
{ case '-':
if(startb&&*startb)
goto noalt;
goto doalt;
case '+':
if(startb&&*startb)
goto doalt;
goto noalt;
default:
badsub: nlog("Bad substitution of");logqnl(buf2);
continue;
}
case '+':
if(startb)
goto doalt;
goto noalt;
case '-':
if(startb)
noalt: if(!skipping)
{ skipping+=2;skipback=p;skipbracelev=bracelev;
oldstartb=startb;bracegot=got;
}
doalt: bracelev++;
continue;
#if 0
case '%': /* this is where processing of ${var%%pat} */
case '#': /* and friends would/will go */
#endif
case '}':
closebrace: if(!startb)
startb=(char*)empty;
break;
}
goto ibreak; /* $$ =pid */
case '$':ultstr(0,(unsigned long)thepid,startb=num);
goto ieofstr;
case '?':ltstr(0,(long)lexitcode,startb=num);
goto ieofstr;
case '#':ultstr(0,(unsigned long)crestarg,startb=num);
goto ieofstr;
case '=':ltstr(0,lastscore,startb=num);
ieofstr: i='\0';
goto copyit;
case '_':startb=incnamed?incnamed->ename:(char*)empty;
goto ibreak;
case '-':startb=(char*)tgetenv(lastfolder); /* $- =$LASTFOLDER */
ibreak: i='\0';
break;
default:
{ int quoted=0;
if(numeric(i)) /* $n positional argument */
{ *startb++=i;i='\0';
goto finsb;
}
if(i=='\\')
quoted=1,i=fgetc();
if(alphanum(i)) /* $name */
{ do
{ if(startb>=fencepost2)
startb=buf2+2,skipping|=1;
*startb++=i;
}
while(EOF!=(i=fgetc())&&alphanum(i));
if(i==EOF)
i='\0';
finsb: *startb='\0';
if(!(startb=(char*)evalenv(skipping)))
startb=(char*)empty;
if(quoted)
{ *p++='(';CHECKINC(); /* protect leading character */
*p++=')';
for(;CHECKINC(),*startb;*p++= *startb++)
if(strchr("(|)*?+.^$[\\",*startb)) /* specials? */
*p++='\\'; /* take them literally */
normchar: quoted=0;
}
else
break;
}
else /* not a substitution */
*p++='$'; /* pretend nothing happened */
if(got<=SKIPPING_SPACE)
got=NORMAL_TEXT;
if(quoted)
goto Quoted;
goto eeofstr;
}
}
if(got!=DOUBLE_QUOTED)
simplsplit: { char*q;
if(sarg)
goto copyit;
if(q=simplesplit(p,startb,fencepost,&got)) /* simply split */
p=q; /* it up in arguments */
else
skipping|=1,p=fencepost;
}
else
copyit: { size_t len=fencepost-p+1;
if(strlcpy(p,startb,len)>=len) /* simply copy it */
skipping|=1; /* did we truncate it? */
if(got<=SKIPPING_SPACE) /* can only occur if sarg!=0 */
got=NORMAL_TEXT;
p=strchr(p,'\0');
}
eeofstr: if(i) /* already read next character? */
goto newchar;
continue;
#if 0 /* autodetect quoted specials? */
case '~':
if(got==NORMAL_TEXT&&p[-1]!='='&&p[-1]!=':')
break;
case '&':case '|':case '<':case '>':case ';':
case '?':case '*':case '[':
if(got<=NORMAL_TEXT)
sh=1;
break;
#endif
case ' ':case '\t':
switch(got)
{ case NORMAL_TEXT:
if(sarg==1)
goto ready; /* already fetched a single argument */
got=SKIPPING_SPACE;*p++=sarg?' ':'\0'; /* space or \0 sep. */
case NOTHING_YET:case SKIPPING_SPACE:
continue; /* skip space */
}
case '\n':
if(got<=NORMAL_TEXT)
goto ready; /* EOL means we're ready */
}
nodelim:
*p++=i; /* ah, a normal character */
if(got<=SKIPPING_SPACE) /* should we bother to change mode? */
got=NORMAL_TEXT;
}
}
char*simplesplit(to,from,fencepost,gotp)char*to;const char*from,*fencepost;
int*gotp;
{ register int got= *gotp;
for(;to<=fencepost;from++)
{ switch(*from)
{ case ' ':case '\t':case '\n':
if(got>SKIPPING_SPACE)
*to++='\0',got=SKIPPING_SPACE;
continue;
case '\0':
goto ret;
}
*to++= *from;got=NORMAL_TEXT;
}
to=0;
ret:
*gotp=got;
return to;
}
void concatenate(p)register char*p;
{ while(p!=Tmnate) /* concatenate all other arguments */
{ while(*p)
p++;
*p++=' ';
}
*p=p[-1]='\0';
}
void metaparse(p)const char*p; /* result in buf */
{ if(sh=!!strpbrk(p,shellmetas))
strcpy(buf,p); /* copy literally, shell will parse */
else
{ sgetcp=p=tstrdup(p);
if(readparse(buf,sgetc,0,0) /* parse it yourself */
#ifndef GOT_bin_test
||!strcmp(test,buf)
#endif
)
strcpy(buf,p),sh=1; /* oops, overflow or `test' found */
free((char*)p);
}
}
void ltstr(minwidth,val,dest)const int minwidth;const long val;char*dest;
{ if(val<0)
{ *dest=' ';ultstr(minwidth-1,-val,dest+1);
while(*++dest==' '); /* look for the first non-space */
dest[-1]='-'; /* replace it with a minus */
}
else
ultstr(minwidth,val,dest); /* business as usual */
}
#ifdef NOstrtod
double strtod(str,ptr)const char*str;char**const ptr;
{ int sign,any;unsigned i;char*chp;double acc,fracc;
fracc=1;acc=any=sign=0;
switch(*(chp=skpspace(str))) /* the sign */
{ case '-':sign=1;
case '+':chp++;
}
while((i=(unsigned)*chp++-'0')<=9) /* before the decimal point */
acc=acc*10+i,any=1;
switch(i)
{ case (unsigned)'.'-'0':case (unsigned)','-'0':
while(fracc/=10,(i=(unsigned)*chp++-'0')<=9) /* the fractional part */
acc+=fracc*i,any=1;
}
if(ptr)
*ptr=any?chp-1:(char**)str;
return sign?-acc:acc;
}
#endif