1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the BSD package               *
4 *Copyright (c) 1978-2006 The Regents of the University of California an*
5 *                                                                      *
6 * Redistribution and use in source and binary forms, with or           *
7 * without modification, are permitted provided that the following      *
8 * conditions are met:                                                  *
9 *                                                                      *
10 *    1. Redistributions of source code must retain the above           *
11 *       copyright notice, this list of conditions and the              *
12 *       following disclaimer.                                          *
13 *                                                                      *
14 *    2. Redistributions in binary form must reproduce the above        *
15 *       copyright notice, this list of conditions and the              *
16 *       following disclaimer in the documentation and/or other         *
17 *       materials provided with the distribution.                      *
18 *                                                                      *
19 *    3. Neither the name of The Regents of the University of California*
20 *       names of its contributors may be used to endorse or            *
21 *       promote products derived from this software without            *
22 *       specific prior written permission.                             *
23 *                                                                      *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND               *
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,          *
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF             *
27 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE             *
28 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS    *
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,             *
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED      *
31 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,        *
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON    *
33 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,      *
34 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY       *
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE              *
36 * POSSIBILITY OF SUCH DAMAGE.                                          *
37 *                                                                      *
38 * Redistribution and use in source and binary forms, with or without   *
39 * modification, are permitted provided that the following conditions   *
40 * are met:                                                             *
41 * 1. Redistributions of source code must retain the above copyright    *
42 *    notice, this list of conditions and the following disclaimer.     *
43 * 2. Redistributions in binary form must reproduce the above copyright *
44 *    notice, this list of conditions and the following disclaimer in   *
45 *    the documentation and/or other materials provided with the        *
46 *    distribution.                                                     *
47 * 3. Neither the name of the University nor the names of its           *
48 *    contributors may be used to endorse or promote products derived   *
49 *    from this software without specific prior written permission.     *
50 *                                                                      *
51 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS"    *
52 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED    *
53 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A      *
54 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS    *
55 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,      *
56 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT     *
57 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF     *
58 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
59 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   *
60 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT   *
61 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF   *
62 * SUCH DAMAGE.                                                         *
63 *                                                                      *
64 *                          Kurt Shoens (UCB)                           *
65 *                                 gsf                                  *
66 *                                                                      *
67 ***********************************************************************/
68 #pragma prototyped
69 /*
70  * Mail -- a mail program
71  *
72  * Auxiliary functions.
73  *
74  * So why isn't this stuff still in the file aux.c?
75  * What file base name is special on what system.
76  * I mean really special.
77  * Any wagers on whether aux is in the POSIX conformance test suite?
78  */
79 
80 #include "mailx.h"
81 
82 #include <stdarg.h>
83 
84 /*
85  * Note message.
86  */
87 void
note(register int flags,const char * fmt,...)88 note(register int flags, const char* fmt, ...)
89 {
90 	register FILE*	fp;
91 	va_list		ap;
92 
93 	va_start(ap, fmt);
94 	if ((flags & DEBUG) && !state.var.debug)
95 		return;
96 	if (flags & (ERROR|PANIC)) {
97 		fp = stderr;
98 		fflush(stdout);
99 	}
100 	else
101 		fp = stdout;
102 	if (state.var.coprocess)
103 		fprintf(fp, "%s ", state.var.coprocess);
104 	if (flags & IDENTIFY)
105 		fprintf(fp, "mail: ");
106 	if (flags & PANIC)
107 		fprintf(fp, T("panic: "));
108 	else if (flags & WARNING)
109 		fprintf(fp, T("warning: "));
110 	else if (flags & DEBUG)
111 		fprintf(fp, T("debug: "));
112 	vfprintf(fp, T(fmt), ap);
113 	va_end(ap);
114 	if (flags & SYSTEM)
115 		fprintf(fp, ": %s", strerror(errno));
116 	if (!(flags & PROMPT))
117 		fprintf(fp, "\n");
118 	fflush(fp);
119 	if (flags & PANIC)
120 		abort();
121 	if (flags & (FATAL|PANIC))
122 		exit(1);
123 }
124 
125 /*
126  * Return a pointer to a dynamic copy of the argument.
127  */
128 char*
savestr(char * str)129 savestr(char* str)
130 {
131 	char*	p;
132 	int	size = strlen(str) + 1;
133 
134 	if ((p = salloc(size)))
135 		memcpy(p, str, size);
136 	return p;
137 }
138 
139 /*
140  * Touch the named message by setting its MTOUCH flag.
141  * Touched messages have the effect of not being sent
142  * back to the system mailbox on exit.
143  */
144 void
touchmsg(register struct msg * mp)145 touchmsg(register struct msg* mp)
146 {
147 	if (mp->m_flag & MREAD)
148 		msgflags(mp, MTOUCH, 0);
149 	else
150 		msgflags(mp, MREAD|MSTATUS, 0);
151 }
152 
153 /*
154  * Test to see if the passed file name is a directory.
155  */
156 int
isdir(char * name)157 isdir(char* name)
158 {
159 	struct stat	st;
160 
161 	if (!name || stat(name, &st) < 0)
162 		return 0;
163 	return S_ISDIR(st.st_mode);
164 }
165 
166 /*
167  * Test to see if the passed file name is a regular file.
168  */
169 int
isreg(char * name)170 isreg(char* name)
171 {
172 	struct stat	st;
173 
174 	if (!name || stat(name, &st) < 0)
175 		return 0;
176 	return S_ISREG(st.st_mode);
177 }
178 
179 /*
180  * The following code deals with input stacking to do source
181  * commands.  All but the current file pointer are saved on
182  * the stack.
183  */
184 
185 /*
186  * Pushdown current input file and switch to a new one.
187  * Set the global flag "sourcing" so that others will realize
188  * that they are no longer reading from a tty (in all probability).
189  */
190 int
source(char ** arglist)191 source(char** arglist)
192 {
193 	FILE*	fp;
194 
195 	if (!(fp = fileopen(*arglist, "EXr")))
196 		return 1;
197 	if (state.source.sp >= NOFILE - 1) {
198 		note(0, "Too much \"sourcing\" going on");
199 		fileclose(fp);
200 		return 1;
201 	}
202 	state.source.stack[state.source.sp].input = state.input;
203 	state.source.stack[state.source.sp].cond = state.cond;
204 	state.source.stack[state.source.sp].loading = state.loading;
205 	state.source.sp++;
206 	state.loading = 0;
207 	state.cond = 0;
208 	state.input = fp;
209 	state.sourcing++;
210 	return 0;
211 }
212 
213 /*
214  * Pop the current input back to the previous level.
215  * Update the "sourcing" flag as appropriate.
216  */
217 int
unstack(void)218 unstack(void)
219 {
220 	if (state.source.sp <= 0) {
221 		note(0, "\"Source\" stack over-pop");
222 		state.sourcing = 0;
223 		return 1;
224 	}
225 	fileclose(state.input);
226 	if (state.cond)
227 		note(0, "Unmatched \"if\"");
228 	state.source.sp--;
229 	state.cond = state.source.stack[state.source.sp].cond;
230 	state.loading = state.source.stack[state.source.sp].loading;
231 	state.input = state.source.stack[state.source.sp].input;
232 	if (state.source.sp == 0)
233 		state.sourcing = state.loading;
234 	return 0;
235 }
236 
237 /*
238  * Touch the indicated file.
239  * This is nifty for the shell.
240  */
241 void
alter(char * name)242 alter(char* name)
243 {
244 	touch(name, (time_t)0, (time_t)(-1), 0);
245 }
246 
247 /*
248  * Examine the passed line buffer and
249  * return true if it is all blanks and tabs.
250  */
251 int
blankline(char * linebuf)252 blankline(char* linebuf)
253 {
254 	register char*	cp;
255 
256 	for (cp = linebuf; *cp; cp++)
257 		if (*cp != ' ' && *cp != '\t')
258 			return 0;
259 	return 1;
260 }
261 
262 /*
263  * Start of a "comment".
264  * Ignore it.
265  */
266 static char*
skip_comment(register char * cp)267 skip_comment(register char* cp)
268 {
269 	register int	nesting = 1;
270 
271 	for (; nesting > 0 && *cp; cp++) {
272 		switch (*cp) {
273 		case '\\':
274 			if (cp[1])
275 				cp++;
276 			break;
277 		case '(':
278 			nesting++;
279 			break;
280 		case ')':
281 			nesting--;
282 			break;
283 		}
284 	}
285 	return cp;
286 }
287 
288 /*
289  * shorten host if it is part of the local domain
290  */
291 
292 char*
localize(char * host)293 localize(char* host)
294 {
295 	register char*	lp;
296 	register char*	le;
297 	register char*	hx;
298 
299 	hx = strchr(host, '.');
300 	lp = state.var.local;
301 	for (;;) {
302 		if (le = strchr(lp, ','))
303 			*le = 0;
304 		if (!strcasecmp(lp, host)) {
305 			if (le)
306 				*le = ',';
307 			return 0;
308 		}
309 		if (hx && !strcasecmp(lp, hx + 1)) {
310 			*hx = 0;
311 			if (le)
312 				*le = ',';
313 			return host;
314 		}
315 		if (!(lp = le))
316 			break;
317 		*lp++ = ',';
318 	}
319 	return host;
320 }
321 
322 /*
323  * apply GCOMPARE, GDISPLAY, state.var.allnet, state.var.local
324  */
325 
326 char*
normalize(char * addr,unsigned long type,char * buf,size_t size)327 normalize(char* addr, unsigned long type, char* buf, size_t size)
328 {
329 	register char*	p;
330 	register char*	e;
331 	register int	n;
332 	char*		uucp;
333 	char*		arpa;
334 	char*		user;
335 	char*		inet;
336 	int		hadarpa;
337 	int		hadinet;
338 	char		temp[LINESIZE];
339 	char		norm[LINESIZE];
340 
341 	while (isspace(*addr))
342 		addr++;
343 	if (!(type & GFROM) && (p = strrchr(addr, ':')))
344 		addr = p + 1;
345 	if ((type & GCOMPARE) && state.var.allnet) {
346 		if (p = strrchr(addr, '!'))
347 			addr = p + 1;
348 		if ((p = strchr(addr, '%')) || (p = strchr(addr, '@'))) {
349 			if (buf) {
350 				if ((n = p - addr) > size)
351 					n = size;
352 				addr = (char*)memcpy(buf, addr, n);
353 				p = addr + n;
354 			}
355 			*p = 0;
356 			return addr;
357 		}
358 	}
359 	else {
360 		strncopy(user = temp, addr, sizeof(temp));
361 		uucp = arpa = inet = 0;
362 		hadarpa = hadinet = 0;
363 		if (p = strrchr(user, '!')) {
364 			uucp = user;
365 			*p++ = 0;
366 			user = p;
367 			if (p = strrchr(uucp, '!'))
368 				p++;
369 			if (p && (type & GDISPLAY)) {
370 				uucp = p;
371 				p = 0;
372 			}
373 			if (p && state.var.local)
374 				uucp = localize(p);
375 		}
376 		if (p = strchr(user, '@')) {
377 			hadinet = 1;
378 			*p++ = 0;
379 			inet = state.var.local ? localize(p) : p;
380 		}
381 		if (p = strchr(user, '%')) {
382 			hadarpa = 1;
383 			*p++ = 0;
384 			if (!(type & (GCOMPARE|GDISPLAY)))
385 				arpa = state.var.local ? localize(p) : p;
386 		}
387 		if (uucp &&
388 		    (hadinet || (inet && streq(uucp, inet)) ||
389 		    (hadarpa || arpa && streq(uucp, arpa))))
390 			uucp = 0;
391 		if (arpa && (hadinet || inet && streq(arpa, inet)))
392 			arpa = 0;
393 		if (type & GDISPLAY) {
394 			if (inet)
395 				uucp = 0;
396 			else if (uucp) {
397 				inet = uucp;
398 				uucp = 0;
399 			}
400 		}
401 		p = norm;
402 		e = p + sizeof(norm);
403 		if (uucp) {
404 			p = strncopy(p, uucp, e - p);
405 			p = strncopy(p, "!", e - p);
406 		}
407 		p = strncopy(p, user, e - p);
408 		if (arpa) {
409 			p = strncopy(p, "%", e - p);
410 			p = strncopy(p, arpa, e - p);
411 		}
412 		if (inet) {
413 			p = strncopy(p, "@", e - p);
414 			p = strncopy(p, inet, e - p);
415 		}
416 		if (!streq(addr, norm))
417 			return savestr(norm);
418 	}
419 	return buf ? (char*)0 : (type & GSTACK) ? savestr(addr) : addr;
420 }
421 
422 /*
423  * Skin an arpa net address according to the RFC 822 interpretation
424  * of "host-phrase."
425  */
426 char*
skin(char * name,unsigned long type)427 skin(char* name, unsigned long type)
428 {
429 	register int	c;
430 	register char*	cp;
431 	register char*	cp2;
432 	char*		bufend;
433 	int		gotlt;
434 	int		lastsp;
435 	char		buf[LINESIZE];
436 
437 	if (!name)
438 		return 0;
439 	if (type & (GMESSAGEID|GREFERENCES))
440 		return savestr(name);
441 	if (!strchr(name, '(') && !strchr(name, '<') && !strchr(name, ' '))
442 		return normalize(name, type, NiL, 0);
443 	gotlt = 0;
444 	lastsp = 0;
445 	bufend = buf;
446 	for (cp = name, cp2 = bufend; c = *cp++; ) {
447 		switch (c) {
448 		case '(':
449 			cp = skip_comment(cp);
450 			lastsp = 0;
451 			break;
452 
453 		case '"':
454 			/*
455 			 * Start of a "quoted-string".
456 			 * Copy it in its entirety.
457 			 */
458 			while (c = *cp) {
459 				cp++;
460 				if (c == '"')
461 					break;
462 				if (c != '\\')
463 					*cp2++ = c;
464 				else if (c = *cp) {
465 					*cp2++ = c;
466 					cp++;
467 				}
468 			}
469 			lastsp = 0;
470 			break;
471 
472 		case ' ':
473 			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
474 				cp += 3, *cp2++ = '@';
475 			else if (cp[0] == '@' && cp[1] == ' ')
476 				cp += 2, *cp2++ = '@';
477 			else
478 				lastsp = 1;
479 			break;
480 
481 		case '<':
482 			cp2 = bufend;
483 			gotlt++;
484 			lastsp = 0;
485 			break;
486 
487 		case '>':
488 			if (gotlt) {
489 				gotlt = 0;
490 				while ((c = *cp) && c != ',') {
491 					cp++;
492 					if (c == '(')
493 						cp = skip_comment(cp);
494 					else if (c == '"')
495 						while (c = *cp) {
496 							cp++;
497 							if (c == '"')
498 								break;
499 							if (c == '\\' && *cp)
500 								cp++;
501 						}
502 				}
503 				lastsp = 0;
504 				break;
505 			}
506 			/* Fall into . . . */
507 
508 		default:
509 			if (lastsp) {
510 				lastsp = 0;
511 				*cp2++ = ' ';
512 			}
513 			*cp2++ = c;
514 			if (c == ',' && !gotlt) {
515 				*cp2++ = ' ';
516 				for (; *cp == ' '; cp++)
517 					;
518 				lastsp = 0;
519 				bufend = cp2;
520 			}
521 		}
522 	}
523 	*cp2 = 0;
524 	return normalize(buf, type|GSTACK, NiL, 0);
525 }
526 
527 /*
528  * Are any of the characters in the two strings the same?
529  */
530 int
anyof(register char * s1,register char * s2)531 anyof(register char* s1, register char* s2)
532 {
533 
534 	while (*s1)
535 		if (strchr(s2, *s1++))
536 			return 1;
537 	return 0;
538 }
539 
540 /*
541  * Convert c to lower case
542  */
543 int
lower(register int c)544 lower(register int c)
545 {
546 	return isupper(c) ? tolower(c) : c;
547 }
548 
549 /*
550  * Convert c to upper case
551  */
552 int
upper(register int c)553 upper(register int c)
554 {
555 	return islower(c) ? toupper(c) : c;
556 }
557 
558 /*
559  * Convert s to lower case
560  */
561 char*
strlower(register char * s)562 strlower(register char* s)
563 {
564 	register char*	b = s;
565 	register int	c;
566 
567 	while (c = *s)
568 		*s++ = isupper(c) ? tolower(c) : c;
569 	return b;
570 }
571 
572 /*
573  * 0 terminate tmp string stream, rewind, and return beginning of string
574  */
575 char*
struse(Sfio_t * sp)576 struse(Sfio_t* sp)
577 {
578 	char*	s;
579 
580 	if (!(s = sfstruse(sp)))
581 		note(FATAL, "out of space");
582 	return s;
583 }
584 
585 /*
586  * See if the given header field is supposed to be ignored.
587  */
588 int
ignored(Dt_t ** ignore,const char * field)589 ignored(Dt_t** ignore, const char* field)
590 {
591 	struct name*	tp;
592 
593 	if (ignore == &state.ignoreall)
594 		return 1;
595 	tp = dictsearch(ignore, field, LOOKUP);
596 	if (*ignore && (dictflags(ignore) & RETAIN))
597 		return !tp || !(tp->flags & RETAIN);
598 	return tp && (tp->flags & IGNORE);
599 }
600 
601 /*
602  * Allocate size more bytes of space and return the address of the
603  * first byte to the caller.  An even number of bytes are always
604  * allocated so that the space will always be on a word boundary.
605  * The string spaces are of exponentially increasing size, to satisfy
606  * the occasional user with enormous string size requests.
607  *
608  * Strings handed out here are reclaimed at the top of the command
609  * loop each time, so they need not be freed.
610  */
611 
612 char*
salloc(register int size)613 salloc(register int size)
614 {
615 	register char*			t;
616 	register struct strings*	sp;
617 	int				index;
618 
619 	if (state.onstack <= 0) {
620 		if (!(t = newof(0, char, size, 0)))
621 			note(PANIC, "Out of space");
622 		return t;
623 	}
624 	size += 7;
625 	size &= ~7;
626 	index = 0;
627 	for (sp = &state.stringdope[0]; sp < &state.stringdope[elementsof(state.stringdope)]; sp++) {
628 		if (!sp->s_topfree && (STRINGSIZE << index) >= size)
629 			break;
630 		if (sp->s_nleft >= size)
631 			break;
632 		index++;
633 	}
634 	if (sp >= &state.stringdope[elementsof(state.stringdope)])
635 		note(PANIC, "String too large");
636 	if (!sp->s_topfree) {
637 		index = sp - &state.stringdope[0];
638 		sp->s_topfree = (char*)malloc(STRINGSIZE << index);
639 		if (!sp->s_topfree)
640 			note(PANIC, "No room for dynamic string space %d", index);
641 		sp->s_nextfree = sp->s_topfree;
642 		sp->s_nleft = STRINGSIZE << index;
643 	}
644 	sp->s_nleft -= size;
645 	t = sp->s_nextfree;
646 	sp->s_nextfree += size;
647 	return t;
648 }
649 
650 /*
651  * Reset the string area to be empty.
652  * Called to free all strings allocated
653  * since last reset.
654  */
655 void
sreset(void)656 sreset(void)
657 {
658 	register struct strings*	sp;
659 	register int			index;
660 
661 	if (state.noreset)
662 		return;
663 	index = 0;
664 	for (sp = &state.stringdope[0]; sp < &state.stringdope[elementsof(state.stringdope)]; sp++) {
665 		if (!sp->s_topfree)
666 			continue;
667 		sp->s_nextfree = sp->s_topfree;
668 		sp->s_nleft = STRINGSIZE << index;
669 		index++;
670 	}
671 	dictreset();
672 }
673 
674 /*
675  * Return lines/chars for display.
676  */
677 char*
counts(int wide,off_t lines,off_t chars)678 counts(int wide, off_t lines, off_t chars)
679 {
680 	sfsprintf(state.counts, sizeof(state.counts), wide ? "%5ld/%-7ld" : "%3ld/%-5ld", (long)lines, (long)chars);
681 	return state.counts;
682 }
683 
684 /*
685  * Check if s matches `all'.
686  */
687 int
isall(register const char * s)688 isall(register const char* s)
689 {
690 	return s && (streq(s, "all") || streq(s, "*"));
691 }
692 
693 /*
694  * Check if name is a pipe command.
695  */
696 char*
iscmd(register char * s)697 iscmd(register char* s)
698 {
699 	if (!s)
700 		return 0;
701 	while (isspace(*s))
702 		s++;
703 	if (*s != '!' && *s != '|')
704 		return 0;
705 	do {
706 		if (!*++s)
707 			return 0;
708 	} while (isspace(*s));
709 	return s;
710 }
711 
712 /*
713  * Set/Clear message flags
714  */
715 
716 void
msgflags(register struct msg * mp,int set,int clr)717 msgflags(register struct msg* mp, int set, int clr)
718 {
719 	if (state.folder == FIMAP)
720 		imap_msgflags(mp, set, clr);
721 	else {
722 		if (clr)
723 			mp->m_flag &= ~clr;
724 		if (set)
725 			mp->m_flag |= set;
726 	}
727 }
728 
729 /*
730  * strncpy() with trailing nul, even if n==0
731  * pointer to the copied nul returned
732  */
733 
734 char*
strncopy(register char * t,register const char * f,size_t n)735 strncopy(register char* t, register const char* f, size_t n)
736 {
737 	register char*	e = t + n - 1;
738 
739 	do
740 	{
741 		if (t >= e)
742 		{
743 			*t = 0;
744 			return t;
745 		}
746 	} while (*t++ = *f++);
747 	return t - 1;
748 }
749 
750 /*
751  * quote s into sp according to sh syntax
752  */
753 
754 void
shquote(register Sfio_t * sp,char * s)755 shquote(register Sfio_t* sp, char* s)
756 {
757 	register char*	t;
758 	register char*	b;
759 	register int	c;
760 	register int	q;
761 
762 	if (*s == '"' && *(s + strlen(s) - 1) == '"')
763 	{
764 		sfprintf(sp, "\\\"%s\\\"", s);
765 		return;
766 	}
767 	q = 0;
768 	b = 0;
769 	for (t = s;;)
770 	{
771 		switch (c = *t++)
772 		{
773 		case 0:
774 			break;
775 		case '\n':
776 		case ';':
777 		case '&':
778 		case '|':
779 		case '<':
780 		case '>':
781 		case '(':
782 		case ')':
783 		case '[':
784 		case ']':
785 		case '{':
786 		case '}':
787 		case '*':
788 		case '?':
789 		case ' ':
790 		case '\t':
791 		case '\\':
792 			q |= 4;
793 			continue;
794 		case '\'':
795 			q |= 1;
796 			if (q & 2)
797 				break;
798 			continue;
799 		case '"':
800 		case '$':
801 			q |= 2;
802 			if (q & 1)
803 				break;
804 			continue;
805 		case '=':
806 			if (!q && !b && *(b = t) == '=')
807 				b++;
808 			continue;
809 		default:
810 			continue;
811 		}
812 		break;
813 	}
814 	if (!q)
815 		sfputr(sp, s, -1);
816 	else if (!(q & 1))
817 	{
818 		if (b)
819 			sfprintf(sp, "%-.*s'%s'", b - s, s, b);
820 		else
821 			sfprintf(sp, "'%s'", s);
822 	}
823 	else if (!(q & 2))
824 	{
825 		if (b)
826 			sfprintf(sp, "%-.*s\"%s\"", b - s, s, b);
827 		else
828 			sfprintf(sp, "\"%s\"", s);
829 	}
830 	else
831 		for (t = s;;)
832 			switch (c = *t++)
833 			{
834 			case 0:
835 				return;
836 			case '\n':
837 				sfputc(sp, '"');
838 				sfputc(sp, c);
839 				sfputc(sp, '"');
840 				break;
841 			case ';':
842 			case '&':
843 			case '|':
844 			case '<':
845 			case '>':
846 			case '(':
847 			case ')':
848 			case '[':
849 			case ']':
850 			case '{':
851 			case '}':
852 			case '$':
853 			case '*':
854 			case '?':
855 			case ' ':
856 			case '\t':
857 			case '\\':
858 			case '\'':
859 			case '"':
860 				sfputc(sp, '\\');
861 				/*FALLTHROUGH*/
862 			default:
863 				sfputc(sp, c);
864 				break;
865 			}
866 }
867