1/************************************************************************ 2 * Collection of library-worthy routines * 3 * * 4 * Copyright (c) 1990-1998, S.R. van den Berg, The Netherlands * 5 * Copyright (c) 1998-2001, Philip Guenther, The United States * 6 * of America * 7 * #include "../README" * 8 ************************************************************************/ 9#ifdef RCS 10static /*const*/char rcsid[]= 11 "$Id: goodies.c,v 1.74 2001/08/04 07:17:44 guenther Exp $"; 12#endif 13#include "procmail.h" 14#include "sublib.h" 15#include "robust.h" 16#include "shell.h" 17#include "misc.h" 18#include "pipes.h" 19#include "common.h" 20#include "acommon.h" 21#include "cstdio.h" 22#include "variables.h" 23#include "goodies.h" 24 25const char test[]="test"; 26const char*Tmnate,*All_args; 27 28static const char*evalenv(skipping) /* expects the variable name in buf2 */ 29 int skipping; 30{ int j=buf2[0]-'0'; 31 return skipping?(const char*)0: /* speed this up when skipping */ 32 (unsigned)j>9?getenv(buf2): 33 !j?argv0: 34 j<=crestarg?restargv[j-1]:(const char*)0; 35} 36 37#define NOTHING_YET (-1) /* readparse understands a very complete */ 38#define SKIPPING_SPACE 0 /* subset of the standard /bin/sh syntax */ 39#define NORMAL_TEXT 1 /* that includes single-, double- and back- */ 40#define DOUBLE_QUOTED 2 /* quotes, backslashes and $subtitutions */ 41#define SINGLE_QUOTED 3 42 43#define fgetc() (*fpgetc)() /* some compilers previously choked on it */ 44#define CHECKINC() (fencepost<p?(skipping|=1,p=fencepost):0) 45 46/* sarg==0 : normal parsing, split up arguments like in /bin/sh 47 * sarg==1 : environment assignment parsing, parse up till first whitespace 48 * sarg==2 : normal parsing, split up arguments by existing whitespace 49 */ 50int readparse(p,fpgetc,sarg,skipping)register char*p;int(*const fpgetc)(); 51 const int sarg;int skipping; 52{ static int i,skipbracelev,bracegot;int got,bracelev,qbracelev; 53 charNUM(num,long),*startb,*const fencepost=buf+linebuf, 54 *const fencepost2=buf2+linebuf; 55 static char*skipback;static const char*oldstartb; 56 bracelev=qbracelev=0;All_args=0; 57 if(skipping)skipping=2; /* bottom bit is whether overflow occurred */ 58 for(got=NOTHING_YET;;) /* buf2 is used as scratch space */ 59loop: 60 { i=fgetc(); 61newchar: 62 fencepost[1]='\0';CHECKINC(); 63 switch(i) 64 { case EOF: /* check sarg too to prevent warnings in the recipe- */ 65 if(sarg<2&&got>NORMAL_TEXT) /* condition expansion code */ 66early_eof: nlog(unexpeof); 67ready: if(got!=SKIPPING_SPACE||sarg) /* not terminated yet or sarg==2 ? */ 68 *p++='\0'; 69 Tmnate=p; 70 if(skipping&1) 71 { nlog(exceededlb);setoverflow(); 72 } 73 return skipping&1; 74 case '\\': 75 if(got==SINGLE_QUOTED) 76 break; 77 i=fgetc(); 78Quoted: switch(i) 79 { case EOF: 80 goto early_eof; /* can't quote EOF */ 81 case '\n': 82 continue; /* concatenate lines */ 83 case '#': 84 if(got>SKIPPING_SPACE) /* escaped comment at start of word? */ 85 goto noesc; /* apparently not, literally */ 86 case ' ':case '\t':case '\'': 87 if(got==DOUBLE_QUOTED) 88 goto noesc; 89 case '"':case '\\':case '$':case '`': 90 goto nodelim; 91 case '}': 92 if(got<=NORMAL_TEXT&&bracelev|| 93 got==DOUBLE_QUOTED&&bracelev>qbracelev) 94 goto nodelim; 95 } 96 if(got>NORMAL_TEXT) 97noesc: *p++='\\'; /* nothing to escape, just echo both */ 98 break; 99 case '`': 100 if(got==SINGLE_QUOTED) 101 goto nodelim; 102 for(startb=p;;) /* mark your position */ 103 { switch(i=fgetc()) /* copy till next backquote */ 104 { case '"': 105 if(got!=DOUBLE_QUOTED) /* missing closing backquote? */ 106 break; 107forcebquote: case EOF:case '`': 108 if(skipping) 109 *(p=startb)='\0'; 110 else 111 { int osh=sh; 112 *p='\0'; 113 if(!(sh=!!strpbrk(startb,shellmetas))) 114 { const char*save=sgetcp,*sAll_args; 115 sgetcp=p=tstrdup(startb);sAll_args=All_args; 116 if(readparse(startb,sgetc,0,0) /* overflow? */ 117#ifndef GOT_bin_test 118 ||!strcmp(test,startb) /* oops, `test' found */ 119#endif 120 )strcpy(startb,p),sh=1; 121 All_args=sAll_args; 122 free(p);sgetcp=save; /* chopped up */ 123 } /* drop source buffer, read from program */ 124 startb=fromprog( 125 p=startb,startb,(size_t)(buf-startb+linebuf-3)); 126 sh=osh; /* restore sh */ 127 } 128 if(got!=DOUBLE_QUOTED) 129 { i=0;startb=p; 130 goto simplsplit; /* split it up */ 131 } 132 if(i=='"'||got<=SKIPPING_SPACE) /* missing closing ` ? */ 133 got=NORMAL_TEXT; 134 p=startb; 135 goto loop; 136 case '\\': 137 switch(i=fgetc()) 138 { case EOF:nlog(unexpeof); 139 goto forcebquote; 140 case '\n': 141 continue; 142 case '"': 143 if(got!=DOUBLE_QUOTED) 144 break; 145 case '\\':case '$':case '`': 146 goto escaped; 147 } 148 *p++='\\'; 149 } 150escaped: CHECKINC();*p++=i; 151 } 152 case '"': 153 switch(got) 154 { case DOUBLE_QUOTED: 155 if(qbracelev<bracelev) /* still inside a ${...}? */ 156 case SINGLE_QUOTED: 157 goto nodelim; /* nonsense */ 158 got=NORMAL_TEXT; 159 continue; /* closing " */ 160 } 161 qbracelev=bracelev;got=DOUBLE_QUOTED; 162 continue; /* opening " */ 163 case '\'': 164 switch(got) 165 { case DOUBLE_QUOTED: 166 goto nodelim; 167 case SINGLE_QUOTED:got=NORMAL_TEXT; 168 continue; /* closing ' */ 169 } 170 got=SINGLE_QUOTED; 171 continue; /* opening ' */ 172 case '}': 173 if(got<=NORMAL_TEXT&&bracelev|| 174 got==DOUBLE_QUOTED&&bracelev>qbracelev) 175 { bracelev--; 176 if(skipback&&bracelev==skipbracelev) 177 { skipping-=2;p=skipback;skipback=0;startb=(char*)oldstartb; 178 got=bracegot; 179 goto closebrace; 180 } 181 continue; 182 } 183 goto nodelim; 184 case '#': 185 if(got>SKIPPING_SPACE) /* comment at start of word? */ 186 break; 187 while((i=fgetc())!=EOF&&i!='\n'); /* skip till EOL */ 188 goto ready; 189 case '$': 190 if(got==SINGLE_QUOTED) 191 break; 192 startb=buf2; 193 switch(i=fgetc()) 194 { case EOF:*p++='$';got=NORMAL_TEXT; 195 goto ready; 196 case '@': 197 if(got!=DOUBLE_QUOTED) 198 goto normchar; 199 if(!skipping) /* don't do it while skipping (braces) */ 200 All_args=p; 201 continue; 202 case '{': /* ${name} */ 203 while(EOF!=(i=fgetc())&&alphanum(i)) 204 { if(startb>=fencepost2) 205 startb=buf2+2,skipping|=1; 206 *startb++=i; 207 } 208 *startb='\0'; 209 if(numeric(*buf2)&&buf2[1]) 210 goto badsub; 211 startb=(char*)evalenv(skipping); 212 switch(i) 213 { default: 214 goto badsub; 215 case ':': 216 switch(i=fgetc()) 217 { case '-': 218 if(startb&&*startb) 219 goto noalt; 220 goto doalt; 221 case '+': 222 if(startb&&*startb) 223 goto doalt; 224 goto noalt; 225 default: 226badsub: nlog("Bad substitution of");logqnl(buf2); 227 continue; 228 } 229 case '+': 230 if(startb) 231 goto doalt; 232 goto noalt; 233 case '-': 234 if(startb) 235noalt: if(!skipping) 236 { skipping+=2;skipback=p;skipbracelev=bracelev; 237 oldstartb=startb;bracegot=got; 238 } 239doalt: bracelev++; 240 continue; 241#if 0 242 case '%': /* this is where processing of ${var%%pat} */ 243 case '#': /* and friends would/will go */ 244#endif 245 case '}': 246closebrace: if(!startb) 247 startb=(char*)empty; 248 break; 249 } 250 goto ibreak; /* $$ =pid */ 251 case '$':ultstr(0,(unsigned long)thepid,startb=num); 252 goto ieofstr; 253 case '?':ltstr(0,(long)lexitcode,startb=num); 254 goto ieofstr; 255 case '#':ultstr(0,(unsigned long)crestarg,startb=num); 256 goto ieofstr; 257 case '=':ltstr(0,lastscore,startb=num); 258ieofstr: i='\0'; 259 goto copyit; 260 case '_':startb=incnamed?incnamed->ename:(char*)empty; 261 goto ibreak; 262 case '-':startb=(char*)tgetenv(lastfolder); /* $- =$LASTFOLDER */ 263ibreak: i='\0'; 264 break; 265 default: 266 { int quoted=0; 267 if(numeric(i)) /* $n positional argument */ 268 { *startb++=i;i='\0'; 269 goto finsb; 270 } 271 if(i=='\\') 272 quoted=1,i=fgetc(); 273 if(alphanum(i)) /* $name */ 274 { do 275 { if(startb>=fencepost2) 276 startb=buf2+2,skipping|=1; 277 *startb++=i; 278 } 279 while(EOF!=(i=fgetc())&&alphanum(i)); 280 if(i==EOF) 281 i='\0'; 282finsb: *startb='\0'; 283 if(!(startb=(char*)evalenv(skipping))) 284 startb=(char*)empty; 285 if(quoted) 286 { *p++='(';CHECKINC(); /* protect leading character */ 287 *p++=')'; 288 for(;CHECKINC(),*startb;*p++= *startb++) 289 if(strchr("(|)*?+.^$[\\",*startb)) /* specials? */ 290 *p++='\\'; /* take them literally */ 291normchar: quoted=0; 292 } 293 else 294 break; 295 } 296 else /* not a substitution */ 297 *p++='$'; /* pretend nothing happened */ 298 if(got<=SKIPPING_SPACE) 299 got=NORMAL_TEXT; 300 if(quoted) 301 goto Quoted; 302 goto eeofstr; 303 } 304 } 305 if(got!=DOUBLE_QUOTED) 306simplsplit: { char*q; 307 if(sarg) 308 goto copyit; 309 if(q=simplesplit(p,startb,fencepost,&got)) /* simply split */ 310 p=q; /* it up in arguments */ 311 else 312 skipping|=1,p=fencepost; 313 } 314 else 315copyit: { size_t len=fencepost-p+1; 316 if(strlcpy(p,startb,len)>=len) /* simply copy it */ 317 skipping|=1; /* did we truncate it? */ 318 if(got<=SKIPPING_SPACE) /* can only occur if sarg!=0 */ 319 got=NORMAL_TEXT; 320 p=strchr(p,'\0'); 321 } 322eeofstr: if(i) /* already read next character? */ 323 goto newchar; 324 continue; 325#if 0 /* autodetect quoted specials? */ 326 case '~': 327 if(got==NORMAL_TEXT&&p[-1]!='='&&p[-1]!=':') 328 break; 329 case '&':case '|':case '<':case '>':case ';': 330 case '?':case '*':case '[': 331 if(got<=NORMAL_TEXT) 332 sh=1; 333 break; 334#endif 335 case ' ':case '\t': 336 switch(got) 337 { case NORMAL_TEXT: 338 if(sarg==1) 339 goto ready; /* already fetched a single argument */ 340 got=SKIPPING_SPACE;*p++=sarg?' ':'\0'; /* space or \0 sep. */ 341 case NOTHING_YET:case SKIPPING_SPACE: 342 continue; /* skip space */ 343 } 344 case '\n': 345 if(got<=NORMAL_TEXT) 346 goto ready; /* EOL means we're ready */ 347 } 348nodelim: 349 *p++=i; /* ah, a normal character */ 350 if(got<=SKIPPING_SPACE) /* should we bother to change mode? */ 351 got=NORMAL_TEXT; 352 } 353} 354 355char*simplesplit(to,from,fencepost,gotp)char*to;const char*from,*fencepost; 356 int*gotp; 357{ register int got= *gotp; 358 for(;to<=fencepost;from++) 359 { switch(*from) 360 { case ' ':case '\t':case '\n': 361 if(got>SKIPPING_SPACE) 362 *to++='\0',got=SKIPPING_SPACE; 363 continue; 364 case '\0': 365 goto ret; 366 } 367 *to++= *from;got=NORMAL_TEXT; 368 } 369 to=0; 370ret: 371 *gotp=got; 372 return to; 373} 374 375void concatenate(p)register char*p; 376{ while(p!=Tmnate) /* concatenate all other arguments */ 377 { while(*p) 378 p++; 379 *p++=' '; 380 } 381 *p=p[-1]='\0'; 382} 383 384void metaparse(p)const char*p; /* result in buf */ 385{ if(sh=!!strpbrk(p,shellmetas)) 386 strcpy(buf,p); /* copy literally, shell will parse */ 387 else 388 { sgetcp=p=tstrdup(p); 389 if(readparse(buf,sgetc,0,0) /* parse it yourself */ 390#ifndef GOT_bin_test 391 ||!strcmp(test,buf) 392#endif 393 ) 394 strcpy(buf,p),sh=1; /* oops, overflow or `test' found */ 395 free((char*)p); 396 } 397} 398 399void ltstr(minwidth,val,dest)const int minwidth;const long val;char*dest; 400{ if(val<0) 401 { *dest=' ';ultstr(minwidth-1,-val,dest+1); 402 while(*++dest==' '); /* look for the first non-space */ 403 dest[-1]='-'; /* replace it with a minus */ 404 } 405 else 406 ultstr(minwidth,val,dest); /* business as usual */ 407} 408 409#ifdef NOstrtod 410double strtod(str,ptr)const char*str;char**const ptr; 411{ int sign,any;unsigned i;char*chp;double acc,fracc; 412 fracc=1;acc=any=sign=0; 413 switch(*(chp=skpspace(str))) /* the sign */ 414 { case '-':sign=1; 415 case '+':chp++; 416 } 417 while((i=(unsigned)*chp++-'0')<=9) /* before the decimal point */ 418 acc=acc*10+i,any=1; 419 switch(i) 420 { case (unsigned)'.'-'0':case (unsigned)','-'0': 421 while(fracc/=10,(i=(unsigned)*chp++-'0')<=9) /* the fractional part */ 422 acc+=fracc*i,any=1; 423 } 424 if(ptr) 425 *ptr=any?chp-1:(char**)str; 426 return sign?-acc:acc; 427} 428#endif 429