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