enum okeys: new flags, additions and removals
[s-mailx.git] / shexp.c
blob4caaab673c8d865b9d9f695903dc15f09aab6dc2
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 (!strcmp(user, myname) && !force && (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 *shellp, *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 if ((shellp = ok_vlook(SHELL)) == NULL)
177 shellp = UNCONST(XSHELL);
178 pid = start_command(shellp, NULL, COMMAND_FD_NULL, pivec[1],
179 "-c", cmdbuf, NULL, NULL);
180 if (pid < 0) {
181 close(pivec[0]);
182 close(pivec[1]);
183 goto jleave;
185 close(pivec[1]);
187 jagain:
188 l = read(pivec[0], xname, sizeof xname);
189 if (l < 0) {
190 if (errno == EINTR)
191 goto jagain;
192 n_perr(_("read"), 0);
193 close(pivec[0]);
194 goto jleave;
196 close(pivec[0]);
197 if (!wait_child(pid, &waits) && WTERMSIG(waits) != SIGPIPE) {
198 if (!(fexpm & FEXP_SILENT))
199 n_err(_("\"%s\": Expansion failed\n"), name);
200 goto jleave;
202 if (l == 0) {
203 if (!(fexpm & FEXP_SILENT))
204 n_err(_("\"%s\": No match\n"), name);
205 goto jleave;
207 if (l == sizeof xname) {
208 if (!(fexpm & FEXP_SILENT))
209 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
210 goto jleave;
212 xname[l] = 0;
213 for (cp = xname + l - 1; *cp == '\n' && cp > xname; --cp)
215 cp[1] = '\0';
216 if (!(fexpm & FEXP_MULTIOK) && strchr(xname, ' ') != NULL &&
217 stat(xname, &sbuf) < 0) {
218 if (!(fexpm & FEXP_SILENT))
219 n_err(_("\"%s\": Ambiguous\n"), name);
220 cp = NULL;
221 goto jleave;
223 cp = savestr(xname);
224 jleave:
225 NYD_LEAVE;
226 return cp;
227 #endif /* !HAVE_WORDEXP */
230 static char *
231 _sh_exp_var(struct shvar_stack *shsp)
233 struct shvar_stack next, *np, *tmp;
234 char const *vp;
235 char lc, c, *cp, *rv;
236 size_t i;
237 NYD2_ENTER;
239 if (*(vp = shsp->shs_value) != '$') {
240 bool_t bsesc = shsp->shs_bsesc;
241 union {bool_t hadbs; char c;} u = {FAL0};
243 shsp->shs_dat = vp;
244 for (lc = '\0', i = 0; ((c = *vp) != '\0'); ++i, ++vp) {
245 if (c == '$' && lc != '\\')
246 break;
247 if (!bsesc)
248 continue;
249 lc = (lc == '\\') ? (u.hadbs = TRU1, '\0') : c;
251 shsp->shs_len = i;
253 if (u.hadbs) {
254 shsp->shs_dat = cp = savestrbuf(shsp->shs_dat, i);
256 for (lc = '\0', rv = cp; (u.c = *cp++) != '\0';) {
257 if (u.c != '\\' || lc == '\\')
258 *rv++ = u.c;
259 lc = (lc == '\\') ? '\0' : u.c;
261 *rv = '\0';
263 shsp->shs_len = PTR2SIZE(rv - shsp->shs_dat);
265 } else {
266 if ((lc = (*++vp == '{')))
267 ++vp;
269 /* POSIX says
270 * Environment variable names used by the utilities in the Shell and
271 * Utilities volume of POSIX.1-2008 consist solely of uppercase
272 * letters, digits, and the <underscore> ('_') from the characters
273 * defined in Portable Character Set and do not begin with a digit.
274 * Other characters may be permitted by an implementation;
275 * applications shall tolerate the presence of such names.
276 * We do support the hyphen "-" because it is common for mailx. */
277 shsp->shs_dat = vp;
278 for (i = 0; (c = *vp) != '\0'; ++i, ++vp)
279 if (!alnumchar(c) && c != '_' && c != '-')
280 break;
282 if (lc) {
283 if (c != '}') {
284 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
285 shsp->shs_value);
286 shsp->shs_len = strlen(shsp->shs_value);
287 shsp->shs_dat = shsp->shs_value;
288 if (shsp->shs_err != NULL)
289 *shsp->shs_err = TRU1;
290 goto junroll;
292 c = *++vp;
295 shsp->shs_len = i;
296 if ((cp = vok_vlook(savestrbuf(shsp->shs_dat, i))) != NULL)
297 shsp->shs_len = strlen(shsp->shs_dat = cp);
299 if (c != '\0')
300 goto jrecurse;
302 /* That level made the great and completed encoding. Build result */
303 junroll:
304 for (i = 0, np = shsp, shsp = NULL; np != NULL;) {
305 i += np->shs_len;
306 tmp = np->shs_next;
307 np->shs_next = shsp;
308 shsp = np;
309 np = tmp;
312 cp = rv = salloc(i +1);
313 while (shsp != NULL) {
314 np = shsp;
315 shsp = shsp->shs_next;
316 memcpy(cp, np->shs_dat, np->shs_len);
317 cp += np->shs_len;
319 *cp = '\0';
321 jleave:
322 NYD2_LEAVE;
323 return rv;
324 jrecurse:
325 memset(&next, 0, sizeof next);
326 next.shs_next = shsp;
327 next.shs_value = vp;
328 next.shs_err = shsp->shs_err;
329 next.shs_bsesc = shsp->shs_bsesc;
330 rv = _sh_exp_var(&next);
331 goto jleave;
334 FL char *
335 fexpand(char const *name, enum fexp_mode fexpm)
337 char cbuf[PATH_MAX +1];
338 char const *res;
339 struct str s;
340 bool_t dyn;
341 NYD_ENTER;
343 /* The order of evaluation is "%" and "#" expand into constants.
344 * "&" can expand into "+". "+" can expand into shell meta characters.
345 * Shell meta characters expand into constants.
346 * This way, we make no recursive expansion */
347 if ((fexpm & FEXP_NSHORTCUT) || (res = shortcut_expand(name)) == NULL)
348 res = UNCONST(name);
350 if (fexpm & FEXP_SHELL) {
351 dyn = FAL0;
352 goto jshell;
354 jnext:
355 dyn = FAL0;
356 switch (*res) {
357 case '%':
358 if (res[1] == ':' && res[2] != '\0') {
359 res = &res[2];
360 goto jnext;
362 _findmail(cbuf, sizeof cbuf, (res[1] != '\0' ? res + 1 : myname),
363 (res[1] != '\0' || (options & OPT_u_FLAG)));
364 res = cbuf;
365 goto jislocal;
366 case '#':
367 if (res[1] != '\0')
368 break;
369 if (prevfile[0] == '\0') {
370 n_err(_("No previous file\n"));
371 res = NULL;
372 goto jleave;
374 res = prevfile;
375 goto jislocal;
376 case '&':
377 if (res[1] == '\0') {
378 if ((res = ok_vlook(MBOX)) == NULL)
379 res = UNCONST("~/mbox"); /* XXX no magics (POSIX though) */
380 else if (res[0] != '&' || res[1] != '\0')
381 goto jnext;
383 break;
386 if (res[0] == '+' && getfold(cbuf, sizeof cbuf)) {
387 size_t i = strlen(cbuf);
389 res = str_concat_csvl(&s, cbuf,
390 ((i > 0 && cbuf[i - 1] == '/') ? "" : "/"), res + 1, NULL)->s;
391 dyn = TRU1;
393 if (res[0] == '%' && res[1] == ':') {
394 res += 2;
395 goto jnext;
399 /* Catch the most common shell meta character */
400 jshell:
401 if (res[0] == '~') {
402 res = n_shell_expand_tilde(res, NULL);
403 dyn = TRU1;
405 if (anyof(res, "|&;<>{}()[]*?$`'\"\\"))
406 switch (which_protocol(res)) {
407 case PROTO_FILE:
408 case PROTO_MAILDIR:
409 res = (fexpm & FEXP_NSHELL) ? n_shell_expand_var(res, TRU1, NULL)
410 : _globname(res, fexpm);
411 dyn = TRU1;
412 goto jleave;
413 default:
414 break;
416 jislocal:
417 if (fexpm & FEXP_LOCAL)
418 switch (which_protocol(res)) {
419 case PROTO_FILE:
420 case PROTO_MAILDIR:
421 break;
422 default:
423 n_err(_("Not a local file or directory: \"%s\"\n"), name);
424 res = NULL;
425 break;
427 jleave:
428 if (res && !dyn)
429 res = savestr(res);
430 NYD_LEAVE;
431 return UNCONST(res);
434 FL char *
435 fexpand_nshell_quote(char const *name)
437 size_t i, j;
438 char *rv, c;
439 NYD_ENTER;
441 for (i = j = 0; (c = name[i]) != '\0'; ++i)
442 if (c == '\\')
443 ++j;
445 if (j == 0)
446 rv = savestrbuf(name, i);
447 else {
448 rv = salloc(i + j +1);
449 for (i = j = 0; (c = name[i]) != '\0'; ++i) {
450 rv[j++] = c;
451 if (c == '\\')
452 rv[j++] = c;
454 rv[j] = '\0';
456 NYD_LEAVE;
457 return rv;
460 FL char *
461 n_shell_expand_tilde(char const *s, bool_t *err_or_null)
463 struct passwd *pwp;
464 size_t nl, rl;
465 char const *rp, *np;
466 char *rv;
467 bool_t err;
468 NYD2_ENTER;
470 err = FAL0;
472 if (s[0] != '~')
473 goto jasis;
475 if (*(rp = s + 1) == '/' || *rp == '\0')
476 np = homedir;
477 else {
478 if ((rp = strchr(s + 1, '/')) == NULL)
479 rp = (np = UNCONST(s)) + 1;
480 else {
481 nl = PTR2SIZE(rp - s);
482 np = savestrbuf(s, nl);
485 if ((pwp = getpwnam(np)) == NULL) {
486 err = TRU1;
487 goto jasis;
489 np = pwp->pw_name;
492 nl = strlen(np);
493 rl = strlen(rp);
494 rv = salloc(nl + 1 + rl +1);
495 memcpy(rv, np, nl);
496 if (rl > 0) {
497 memcpy(rv + nl, rp, rl);
498 nl += rl;
500 rv[nl] = '\0';
501 goto jleave;
503 jasis:
504 rv = savestr(s);
505 jleave:
506 if (err_or_null != NULL)
507 *err_or_null = err;
508 NYD2_LEAVE;
509 return rv;
512 FL char *
513 n_shell_expand_var(char const *s, bool_t bsescape, bool_t *err_or_null)
515 struct shvar_stack top;
516 char *rv;
517 NYD2_ENTER;
519 memset(&top, 0, sizeof top);
521 top.shs_value = s;
522 if ((top.shs_err = err_or_null) != NULL)
523 *err_or_null = FAL0;
524 top.shs_bsesc = bsescape;
525 rv = _sh_exp_var(&top);
526 NYD2_LEAVE;
527 return rv;
530 FL int
531 n_shell_expand_escape(char const **s, bool_t use_nail_extensions)
533 char const *xs;
534 int c, n;
535 NYD2_ENTER;
537 xs = *s;
539 if ((c = *xs & 0xFF) == '\0')
540 goto jleave;
541 ++xs;
542 if (c != '\\')
543 goto jleave;
545 switch ((c = *xs & 0xFF)) {
546 case 'a': c = '\a'; break;
547 case 'b': c = '\b'; break;
548 case 'c': c = PROMPT_STOP; break;
549 case 'f': c = '\f'; break;
550 case 'n': c = '\n'; break;
551 case 'r': c = '\r'; break;
552 case 't': c = '\t'; break;
553 case 'v': c = '\v'; break;
555 /* Hexadecimal TODO uses ASCII */
556 case 'X':
557 case 'x': {
558 static ui8_t const hexatoi[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
559 #undef a_HEX
560 #define a_HEX(n) \
561 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
563 c = 0;
564 ++xs;
565 if(hexchar(*xs))
566 c = a_HEX(*xs);
567 else{
568 --xs;
569 if(options & OPT_D_V)
570 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs - 1);
571 c = '\\';
572 goto jleave;
574 ++xs;
575 if(hexchar(*xs)){
576 c <<= 4;
577 c += a_HEX(*xs);
578 ++xs;
580 goto jleave;
582 #undef a_HEX
584 /* octal, with optional 0 prefix */
585 case '0':
586 ++xs;
587 if(0){
588 default:
589 if(*xs == '\0'){
590 c = '\\';
591 break;
594 for (c = 0, n = 3; n-- > 0 && octalchar(*xs); ++xs) {
595 c <<= 3;
596 c |= *xs - '0';
598 goto jleave;
600 /* S-nail extension for nice (get)prompt(()) support */
601 case '&':
602 case '?':
603 case '$':
604 case '@':
605 if (use_nail_extensions) {
606 switch (c) {
607 case '&': c = ok_blook(bsdcompat) ? '&' : '?'; break;
608 case '?': c = (pstate & PS_EVAL_ERROR) ? '1' : '0'; break;
609 case '$': c = PROMPT_DOLLAR; break;
610 case '@': c = PROMPT_AT; break;
612 break;
615 /* FALLTHRU */
616 case '\0':
617 /* A sole <backslash> at EOS is treated as-is! */
618 c = '\\';
619 /* FALLTHRU */
620 case '\\':
621 break;
624 ++xs;
625 jleave:
626 *s = xs;
627 NYD2_LEAVE;
628 return c;
631 /* s-it-mode */