Add _VF_(POS)?NUM checks; fix: forgot *toplines* defval=..
[s-mailx.git] / shexp.c
blobbe965bb62d05c6cc1e5baa5d9f456a2d818bb26d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Shell "word", file- and other name expansions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE shexp
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <sys/wait.h>
44 #include <pwd.h>
46 #ifdef HAVE_WORDEXP
47 # include <wordexp.h>
48 #endif
50 struct shvar_stack {
51 struct shvar_stack *shs_next; /* Outer stack frame */
52 char const *shs_value; /* Remaining value to expand */
53 size_t shs_len; /* gth of .shs_dat this level */
54 char const *shs_dat; /* Result data of this level */
55 bool_t *shs_err; /* Or NULL */
56 bool_t shs_bsesc; /* Shall backslash escaping be performed */
59 /* Locate the user's mailbox file (where new, unread mail is queued) */
60 static void _findmail(char *buf, size_t bufsize, char const *user,
61 bool_t force);
63 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
64 static char * _globname(char const *name, enum fexp_mode fexpm);
66 /* Perform shell variable expansion */
67 static char * _sh_exp_var(struct shvar_stack *shsp);
69 static void
70 _findmail(char *buf, size_t bufsize, char const *user, bool_t force)
72 char *cp;
73 NYD_ENTER;
75 if (!force && !strcmp(user, myname) && (cp = ok_vlook(folder)) != NULL) {
79 if (force || (cp = ok_vlook(MAIL)) == NULL)
80 snprintf(buf, bufsize, "%s/%s", MAILSPOOL, user); /* TODO */
81 else
82 n_strscpy(buf, cp, bufsize);
83 NYD_LEAVE;
86 static char *
87 _globname(char const *name, enum fexp_mode fexpm)
89 #ifdef HAVE_WORDEXP
90 wordexp_t we;
91 char *cp = NULL;
92 sigset_t nset;
93 int i;
94 NYD_ENTER;
96 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
97 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
98 memset(&we, 0, sizeof we);
100 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
101 * and wait, which will fail if our SIGCHLD handler is active */
102 sigemptyset(&nset);
103 sigaddset(&nset, SIGCHLD);
104 sigprocmask(SIG_BLOCK, &nset, NULL);
105 # ifndef WRDE_NOCMD
106 # define WRDE_NOCMD 0
107 # endif
108 i = wordexp(name, &we, WRDE_NOCMD);
109 sigprocmask(SIG_UNBLOCK, &nset, NULL);
111 switch (i) {
112 case 0:
113 break;
114 #ifdef WRDE_CMDSUB
115 case WRDE_CMDSUB:
116 if (!(fexpm & FEXP_SILENT))
117 n_err(_("\"%s\": Command substitution not allowed\n"), name);
118 goto jleave;
119 #endif
120 case WRDE_NOSPACE:
121 if (!(fexpm & FEXP_SILENT))
122 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
123 goto jleave;
124 case WRDE_BADCHAR:
125 case WRDE_SYNTAX:
126 default:
127 if (!(fexpm & FEXP_SILENT))
128 n_err(_("Syntax error in \"%s\"\n"), name);
129 goto jleave;
132 switch (we.we_wordc) {
133 case 1:
134 cp = savestr(we.we_wordv[0]);
135 break;
136 case 0:
137 if (!(fexpm & FEXP_SILENT))
138 n_err(_("\"%s\": No match\n"), name);
139 break;
140 default:
141 if (fexpm & FEXP_MULTIOK) {
142 size_t j, l;
144 for (l = 0, j = 0; j < we.we_wordc; ++j)
145 l += strlen(we.we_wordv[j]) + 1;
146 ++l;
147 cp = salloc(l);
148 for (l = 0, j = 0; j < we.we_wordc; ++j) {
149 size_t x = strlen(we.we_wordv[j]);
150 memcpy(cp + l, we.we_wordv[j], x);
151 l += x;
152 cp[l++] = ' ';
154 cp[l] = '\0';
155 } else if (!(fexpm & FEXP_SILENT))
156 n_err(_("\"%s\": Ambiguous\n"), name);
157 break;
159 jleave:
160 wordfree(&we);
161 NYD_LEAVE;
162 return cp;
164 #else /* HAVE_WORDEXP */
165 struct stat sbuf;
166 char xname[PATH_MAX +1], cmdbuf[PATH_MAX +1], /* also used for files */
167 cp = NULL;
168 int pivec[2], pid, l, waits;
169 NYD_ENTER;
171 if (pipe(pivec) < 0) {
172 n_perr(_("pipe"), 0);
173 goto jleave;
175 snprintf(cmdbuf, sizeof cmdbuf, "echo %s", name);
176 pid = start_command(ok_vlook(SHELL), NULL, COMMAND_FD_NULL, pivec[1],
177 "-c", cmdbuf, NULL, NULL);
178 if (pid < 0) {
179 close(pivec[0]);
180 close(pivec[1]);
181 goto jleave;
183 close(pivec[1]);
185 jagain:
186 l = read(pivec[0], xname, sizeof xname);
187 if (l < 0) {
188 if (errno == EINTR)
189 goto jagain;
190 n_perr(_("read"), 0);
191 close(pivec[0]);
192 goto jleave;
194 close(pivec[0]);
195 if (!wait_child(pid, &waits) && WTERMSIG(waits) != SIGPIPE) {
196 if (!(fexpm & FEXP_SILENT))
197 n_err(_("\"%s\": Expansion failed\n"), name);
198 goto jleave;
200 if (l == 0) {
201 if (!(fexpm & FEXP_SILENT))
202 n_err(_("\"%s\": No match\n"), name);
203 goto jleave;
205 if (l == sizeof xname) {
206 if (!(fexpm & FEXP_SILENT))
207 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
208 goto jleave;
210 xname[l] = 0;
211 for (cp = xname + l - 1; *cp == '\n' && cp > xname; --cp)
213 cp[1] = '\0';
214 if (!(fexpm & FEXP_MULTIOK) && strchr(xname, ' ') != NULL &&
215 stat(xname, &sbuf) < 0) {
216 if (!(fexpm & FEXP_SILENT))
217 n_err(_("\"%s\": Ambiguous\n"), name);
218 cp = NULL;
219 goto jleave;
221 cp = savestr(xname);
222 jleave:
223 NYD_LEAVE;
224 return cp;
225 #endif /* !HAVE_WORDEXP */
228 static char *
229 _sh_exp_var(struct shvar_stack *shsp)
231 struct shvar_stack next, *np, *tmp;
232 char const *vp;
233 char lc, c, *cp, *rv;
234 size_t i;
235 NYD2_ENTER;
237 if (*(vp = shsp->shs_value) != '$') {
238 bool_t bsesc = shsp->shs_bsesc;
239 union {bool_t hadbs; char c;} u = {FAL0};
241 shsp->shs_dat = vp;
242 for (lc = '\0', i = 0; ((c = *vp) != '\0'); ++i, ++vp) {
243 if (c == '$' && lc != '\\')
244 break;
245 if (!bsesc)
246 continue;
247 lc = (lc == '\\') ? (u.hadbs = TRU1, '\0') : c;
249 shsp->shs_len = i;
251 if (u.hadbs) {
252 shsp->shs_dat = cp = savestrbuf(shsp->shs_dat, i);
254 for (lc = '\0', rv = cp; (u.c = *cp++) != '\0';) {
255 if (u.c != '\\' || lc == '\\')
256 *rv++ = u.c;
257 lc = (lc == '\\') ? '\0' : u.c;
259 *rv = '\0';
261 shsp->shs_len = PTR2SIZE(rv - shsp->shs_dat);
263 } else {
264 if ((lc = (*++vp == '{')))
265 ++vp;
267 /* POSIX says
268 * Environment variable names used by the utilities in the Shell and
269 * Utilities volume of POSIX.1-2008 consist solely of uppercase
270 * letters, digits, and the <underscore> ('_') from the characters
271 * defined in Portable Character Set and do not begin with a digit.
272 * Other characters may be permitted by an implementation;
273 * applications shall tolerate the presence of such names.
274 * We do support the hyphen "-" because it is common for mailx. */
275 shsp->shs_dat = vp;
276 for (i = 0; (c = *vp) != '\0'; ++i, ++vp)
277 if (!alnumchar(c) && c != '_' && c != '-')
278 break;
280 if (lc) {
281 if (c != '}') {
282 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
283 shsp->shs_value);
284 shsp->shs_len = strlen(shsp->shs_value);
285 shsp->shs_dat = shsp->shs_value;
286 if (shsp->shs_err != NULL)
287 *shsp->shs_err = TRU1;
288 goto junroll;
290 c = *++vp;
293 shsp->shs_len = i;
294 /* Check getenv(3) shall no internal variable exist! */
295 if ((rv = vok_vlook(cp = savestrbuf(shsp->shs_dat, i))) != NULL ||
296 (rv = getenv(cp)) != NULL)
297 shsp->shs_len = strlen(shsp->shs_dat = rv);
298 else
299 shsp->shs_len = 0, shsp->shs_dat = UNCONST("");
301 if (c != '\0')
302 goto jrecurse;
304 /* That level made the great and completed encoding. Build result */
305 junroll:
306 for (i = 0, np = shsp, shsp = NULL; np != NULL;) {
307 i += np->shs_len;
308 tmp = np->shs_next;
309 np->shs_next = shsp;
310 shsp = np;
311 np = tmp;
314 cp = rv = salloc(i +1);
315 while (shsp != NULL) {
316 np = shsp;
317 shsp = shsp->shs_next;
318 memcpy(cp, np->shs_dat, np->shs_len);
319 cp += np->shs_len;
321 *cp = '\0';
323 jleave:
324 NYD2_LEAVE;
325 return rv;
326 jrecurse:
327 memset(&next, 0, sizeof next);
328 next.shs_next = shsp;
329 next.shs_value = vp;
330 next.shs_err = shsp->shs_err;
331 next.shs_bsesc = shsp->shs_bsesc;
332 rv = _sh_exp_var(&next);
333 goto jleave;
336 FL char *
337 fexpand(char const *name, enum fexp_mode fexpm)
339 char cbuf[PATH_MAX +1];
340 char const *res;
341 struct str s;
342 bool_t dyn;
343 NYD_ENTER;
345 /* The order of evaluation is "%" and "#" expand into constants.
346 * "&" can expand into "+". "+" can expand into shell meta characters.
347 * Shell meta characters expand into constants.
348 * This way, we make no recursive expansion */
349 if ((fexpm & FEXP_NSHORTCUT) || (res = shortcut_expand(name)) == NULL)
350 res = UNCONST(name);
352 if (fexpm & FEXP_SHELL) {
353 dyn = FAL0;
354 goto jshell;
356 jnext:
357 dyn = FAL0;
358 switch (*res) {
359 case '%':
360 if (res[1] == ':' && res[2] != '\0') {
361 res = &res[2];
362 goto jnext;
364 _findmail(cbuf, sizeof cbuf, (res[1] != '\0' ? res + 1 : myname),
365 (res[1] != '\0'));
366 res = cbuf;
367 goto jislocal;
368 case '#':
369 if (res[1] != '\0')
370 break;
371 if (prevfile[0] == '\0') {
372 n_err(_("No previous file\n"));
373 res = NULL;
374 goto jleave;
376 res = prevfile;
377 goto jislocal;
378 case '&':
379 if (res[1] == '\0')
380 res = ok_vlook(MBOX);
381 break;
384 if (res[0] == '+' && getfold(cbuf, sizeof cbuf)) {
385 size_t i = strlen(cbuf);
387 res = str_concat_csvl(&s, cbuf,
388 ((i > 0 && cbuf[i - 1] == '/') ? "" : "/"), res + 1, NULL)->s;
389 dyn = TRU1;
391 if (res[0] == '%' && res[1] == ':') {
392 res += 2;
393 goto jnext;
397 /* Catch the most common shell meta character */
398 jshell:
399 if (res[0] == '~') {
400 res = n_shell_expand_tilde(res, NULL);
401 dyn = TRU1;
403 if (anyof(res, "|&;<>{}()[]*?$`'\"\\"))
404 switch (which_protocol(res)) {
405 case PROTO_FILE:
406 case PROTO_MAILDIR:
407 res = (fexpm & FEXP_NSHELL) ? n_shell_expand_var(res, TRU1, NULL)
408 : _globname(res, fexpm);
409 dyn = TRU1;
410 goto jleave;
411 default:
412 break;
414 jislocal:
415 if (fexpm & FEXP_LOCAL)
416 switch (which_protocol(res)) {
417 case PROTO_FILE:
418 case PROTO_MAILDIR:
419 break;
420 default:
421 n_err(_("Not a local file or directory: \"%s\"\n"), name);
422 res = NULL;
423 break;
425 jleave:
426 if (res && !dyn)
427 res = savestr(res);
428 NYD_LEAVE;
429 return UNCONST(res);
432 FL char *
433 fexpand_nshell_quote(char const *name)
435 size_t i, j;
436 char *rv, c;
437 NYD_ENTER;
439 for (i = j = 0; (c = name[i]) != '\0'; ++i)
440 if (c == '\\')
441 ++j;
443 if (j == 0)
444 rv = savestrbuf(name, i);
445 else {
446 rv = salloc(i + j +1);
447 for (i = j = 0; (c = name[i]) != '\0'; ++i) {
448 rv[j++] = c;
449 if (c == '\\')
450 rv[j++] = c;
452 rv[j] = '\0';
454 NYD_LEAVE;
455 return rv;
458 FL char *
459 n_shell_expand_tilde(char const *s, bool_t *err_or_null)
461 struct passwd *pwp;
462 size_t nl, rl;
463 char const *rp, *np;
464 char *rv;
465 bool_t err;
466 NYD2_ENTER;
468 err = FAL0;
470 if (s[0] != '~')
471 goto jasis;
473 if (*(rp = s + 1) == '/' || *rp == '\0')
474 np = homedir;
475 else {
476 if ((rp = strchr(s + 1, '/')) == NULL)
477 rp = (np = UNCONST(s)) + 1;
478 else {
479 nl = PTR2SIZE(rp - s);
480 np = savestrbuf(s, nl);
483 if ((pwp = getpwnam(np)) == NULL) {
484 err = TRU1;
485 goto jasis;
487 np = pwp->pw_name;
490 nl = strlen(np);
491 rl = strlen(rp);
492 rv = salloc(nl + 1 + rl +1);
493 memcpy(rv, np, nl);
494 if (rl > 0) {
495 memcpy(rv + nl, rp, rl);
496 nl += rl;
498 rv[nl] = '\0';
499 goto jleave;
501 jasis:
502 rv = savestr(s);
503 jleave:
504 if (err_or_null != NULL)
505 *err_or_null = err;
506 NYD2_LEAVE;
507 return rv;
510 FL char *
511 n_shell_expand_var(char const *s, bool_t bsescape, bool_t *err_or_null)
513 struct shvar_stack top;
514 char *rv;
515 NYD2_ENTER;
517 memset(&top, 0, sizeof top);
519 top.shs_value = s;
520 if ((top.shs_err = err_or_null) != NULL)
521 *err_or_null = FAL0;
522 top.shs_bsesc = bsescape;
523 rv = _sh_exp_var(&top);
524 NYD2_LEAVE;
525 return rv;
528 FL int
529 n_shell_expand_escape(char const **s, bool_t use_nail_extensions)
531 char const *xs;
532 int c, n;
533 NYD2_ENTER;
535 xs = *s;
537 if ((c = *xs & 0xFF) == '\0')
538 goto jleave;
539 ++xs;
540 if (c != '\\')
541 goto jleave;
543 switch ((c = *xs & 0xFF)) {
544 case 'a': c = '\a'; break;
545 case 'b': c = '\b'; break;
546 case 'c': c = PROMPT_STOP; break;
547 case 'f': c = '\f'; break;
548 case 'n': c = '\n'; break;
549 case 'r': c = '\r'; break;
550 case 't': c = '\t'; break;
551 case 'v': c = '\v'; break;
553 /* Hexadecimal TODO uses ASCII */
554 case 'X':
555 case 'x': {
556 static ui8_t const hexatoi[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
557 #undef a_HEX
558 #define a_HEX(n) \
559 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
561 c = 0;
562 ++xs;
563 if(hexchar(*xs))
564 c = a_HEX(*xs);
565 else{
566 --xs;
567 if(options & OPT_D_V)
568 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs - 1);
569 c = '\\';
570 goto jleave;
572 ++xs;
573 if(hexchar(*xs)){
574 c <<= 4;
575 c += a_HEX(*xs);
576 ++xs;
578 goto jleave;
580 #undef a_HEX
582 /* octal, with optional 0 prefix */
583 case '0':
584 ++xs;
585 if(0){
586 default:
587 if(*xs == '\0'){
588 c = '\\';
589 break;
592 for (c = 0, n = 3; n-- > 0 && octalchar(*xs); ++xs) {
593 c <<= 3;
594 c |= *xs - '0';
596 goto jleave;
598 /* S-nail extension for nice (get)prompt(()) support */
599 case '&':
600 case '?':
601 case '$':
602 case '@':
603 if (use_nail_extensions) {
604 switch (c) {
605 case '&': c = ok_blook(bsdcompat) ? '&' : '?'; break;
606 case '?': c = (pstate & PS_EVAL_ERROR) ? '1' : '0'; break;
607 case '$': c = PROMPT_DOLLAR; break;
608 case '@': c = PROMPT_AT; break;
610 break;
613 /* FALLTHRU */
614 case '\0':
615 /* A sole <backslash> at EOS is treated as-is! */
616 c = '\\';
617 /* FALLTHRU */
618 case '\\':
619 break;
622 ++xs;
623 jleave:
624 *s = xs;
625 NYD2_LEAVE;
626 return c;
629 /* s-it-mode */