Redefine *folder* / getfold->folder_query() / *HOME* (Justin Ellingwood)..
[s-mailx.git] / shexp.c
blob0be4c59be9411f8165644fcc553ca0bb407890e1
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 char * _findmail(char const *user, bool_t force);
62 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
63 static char * _globname(char const *name, enum fexp_mode fexpm);
65 /* Perform shell variable expansion */
66 static char * _sh_exp_var(struct shvar_stack *shsp);
68 static char *
69 _findmail(char const *user, bool_t force)
71 char *rv;
72 char const *cp;
73 NYD_ENTER;
75 if (force || (cp = ok_vlook(MAIL)) == NULL) {
76 size_t ul = strlen(user), i = sizeof(MAILSPOOL) -1 + 1 + ul +1;
78 rv = salloc(i);
79 memcpy(rv, MAILSPOOL, i = sizeof(MAILSPOOL));
80 rv[i] = '/';
81 memcpy(&rv[++i], user, ul +1);
82 } else if ((rv = fexpand(cp, FEXP_NSHELL)) == NULL)
83 rv = savestr(cp);
84 NYD_LEAVE;
85 return rv;
88 static char *
89 _globname(char const *name, enum fexp_mode fexpm)
91 #ifdef HAVE_WORDEXP
92 wordexp_t we;
93 char *cp = NULL;
94 sigset_t nset;
95 int i;
96 NYD_ENTER;
98 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
99 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
100 memset(&we, 0, sizeof we);
102 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
103 * and wait, which will fail if our SIGCHLD handler is active */
104 sigemptyset(&nset);
105 sigaddset(&nset, SIGCHLD);
106 sigprocmask(SIG_BLOCK, &nset, NULL);
107 # ifndef WRDE_NOCMD
108 # define WRDE_NOCMD 0
109 # endif
110 i = wordexp(name, &we, WRDE_NOCMD);
111 sigprocmask(SIG_UNBLOCK, &nset, NULL);
113 switch (i) {
114 case 0:
115 break;
116 #ifdef WRDE_CMDSUB
117 case WRDE_CMDSUB:
118 if (!(fexpm & FEXP_SILENT))
119 n_err(_("\"%s\": Command substitution not allowed\n"), name);
120 goto jleave;
121 #endif
122 case WRDE_NOSPACE:
123 if (!(fexpm & FEXP_SILENT))
124 n_err(_("\"%s\": Expansion buffer overflow\n"), name);
125 goto jleave;
126 case WRDE_BADCHAR:
127 case WRDE_SYNTAX:
128 default:
129 if (!(fexpm & FEXP_SILENT))
130 n_err(_("Syntax error in \"%s\"\n"), name);
131 goto jleave;
134 switch (we.we_wordc) {
135 case 1:
136 cp = savestr(we.we_wordv[0]);
137 break;
138 case 0:
139 if (!(fexpm & FEXP_SILENT))
140 n_err(_("\"%s\": No match\n"), name);
141 break;
142 default:
143 if (fexpm & FEXP_MULTIOK) {
144 size_t j, l;
146 for (l = 0, j = 0; j < we.we_wordc; ++j)
147 l += strlen(we.we_wordv[j]) + 1;
148 ++l;
149 cp = salloc(l);
150 for (l = 0, j = 0; j < we.we_wordc; ++j) {
151 size_t x = strlen(we.we_wordv[j]);
152 memcpy(cp + l, we.we_wordv[j], x);
153 l += x;
154 cp[l++] = ' ';
156 cp[l] = '\0';
157 } else if (!(fexpm & FEXP_SILENT))
158 n_err(_("\"%s\": Ambiguous\n"), name);
159 break;
161 jleave:
162 wordfree(&we);
163 NYD_LEAVE;
164 return cp;
166 #else /* HAVE_WORDEXP */
167 struct stat sbuf;
168 char xname[PATH_MAX +1], cmdbuf[PATH_MAX +1], /* also used for files */
169 cp = NULL;
170 int pivec[2], pid, l, waits;
171 NYD_ENTER;
173 if (pipe(pivec) < 0) {
174 n_perr(_("pipe"), 0);
175 goto jleave;
177 snprintf(cmdbuf, sizeof cmdbuf, "echo %s", name);
178 pid = start_command(ok_vlook(SHELL), 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 /* Check getenv(3) shall no internal variable exist! */
297 if ((rv = vok_vlook(cp = savestrbuf(shsp->shs_dat, i))) != NULL ||
298 (rv = getenv(cp)) != NULL)
299 shsp->shs_len = strlen(shsp->shs_dat = rv);
300 else
301 shsp->shs_len = 0, shsp->shs_dat = UNCONST("");
303 if (c != '\0')
304 goto jrecurse;
306 /* That level made the great and completed encoding. Build result */
307 junroll:
308 for (i = 0, np = shsp, shsp = NULL; np != NULL;) {
309 i += np->shs_len;
310 tmp = np->shs_next;
311 np->shs_next = shsp;
312 shsp = np;
313 np = tmp;
316 cp = rv = salloc(i +1);
317 while (shsp != NULL) {
318 np = shsp;
319 shsp = shsp->shs_next;
320 memcpy(cp, np->shs_dat, np->shs_len);
321 cp += np->shs_len;
323 *cp = '\0';
325 jleave:
326 NYD2_LEAVE;
327 return rv;
328 jrecurse:
329 memset(&next, 0, sizeof next);
330 next.shs_next = shsp;
331 next.shs_value = vp;
332 next.shs_err = shsp->shs_err;
333 next.shs_bsesc = shsp->shs_bsesc;
334 rv = _sh_exp_var(&next);
335 goto jleave;
338 FL char *
339 fexpand(char const *name, enum fexp_mode fexpm)
341 struct str s;
342 char const *cp, *res;
343 bool_t dyn;
344 NYD_ENTER;
346 /* The order of evaluation is "%" and "#" expand into constants.
347 * "&" can expand into "+". "+" can expand into shell meta characters.
348 * Shell meta characters expand into constants.
349 * This way, we make no recursive expansion */
350 if ((fexpm & FEXP_NSHORTCUT) || (res = shortcut_expand(name)) == NULL)
351 res = UNCONST(name);
353 if (fexpm & FEXP_SHELL) {
354 dyn = FAL0;
355 goto jshell;
357 jnext:
358 dyn = FAL0;
359 switch (*res) {
360 case '%':
361 if (res[1] == ':' && res[2] != '\0') {
362 res = &res[2];
363 goto jnext;
365 res = _findmail((res[1] != '\0' ? res + 1 : myname), (res[1] != '\0'));
366 goto jislocal;
367 case '#':
368 if (res[1] != '\0')
369 break;
370 if (prevfile[0] == '\0') {
371 n_err(_("No previous file\n"));
372 res = NULL;
373 goto jleave;
375 res = prevfile;
376 goto jislocal;
377 case '&':
378 if (res[1] == '\0')
379 res = ok_vlook(MBOX);
380 break;
383 /* POSIX: if *folder* unset or null, "+" shall be retained */
384 if (*res == '+' && *(cp = folder_query()) != '\0') {
385 size_t i = strlen(cp);
387 res = str_concat_csvl(&s, cp,
388 ((i == 0 || cp[i -1] == '/') ? "" : "/"), res + 1, NULL)->s;
389 dyn = TRU1;
391 /* TODO *folder* can't start with %[:], can it!?! */
392 if (res[0] == '%' && res[1] == ':') {
393 res += 2;
394 goto jnext;
398 /* Catch the most common shell meta character */
399 jshell:
400 if (res[0] == '~') {
401 res = n_shell_expand_tilde(res, NULL);
402 dyn = TRU1;
404 if (anyof(res, "|&;<>{}()[]*?$`'\"\\"))
405 switch (which_protocol(res)) {
406 case PROTO_FILE:
407 case PROTO_MAILDIR:
408 res = (fexpm & FEXP_NSHELL) ? n_shell_expand_var(res, TRU1, NULL)
409 : _globname(res, fexpm);
410 dyn = TRU1;
411 goto jleave;
412 default:
413 break;
415 jislocal:
416 if (fexpm & FEXP_LOCAL)
417 switch (which_protocol(res)) {
418 case PROTO_FILE:
419 case PROTO_MAILDIR:
420 break;
421 default:
422 n_err(_("Not a local file or directory: \"%s\"\n"), name);
423 res = NULL;
424 break;
426 jleave:
427 if (res && !dyn)
428 res = savestr(res);
429 NYD_LEAVE;
430 return UNCONST(res);
433 FL char *
434 fexpand_nshell_quote(char const *name)
436 size_t i, j;
437 char *rv, c;
438 NYD_ENTER;
440 for (i = j = 0; (c = name[i]) != '\0'; ++i)
441 if (c == '\\')
442 ++j;
444 if (j == 0)
445 rv = savestrbuf(name, i);
446 else {
447 rv = salloc(i + j +1);
448 for (i = j = 0; (c = name[i]) != '\0'; ++i) {
449 rv[j++] = c;
450 if (c == '\\')
451 rv[j++] = c;
453 rv[j] = '\0';
455 NYD_LEAVE;
456 return rv;
459 FL char *
460 n_shell_expand_tilde(char const *s, bool_t *err_or_null)
462 struct passwd *pwp;
463 size_t nl, rl;
464 char const *rp, *np;
465 char *rv;
466 bool_t err;
467 NYD2_ENTER;
469 err = FAL0;
471 if (s[0] != '~')
472 goto jasis;
474 if (*(rp = s + 1) == '/' || *rp == '\0')
475 np = ok_vlook(HOME);
476 else {
477 if ((rp = strchr(s + 1, '/')) == NULL)
478 rp = (np = UNCONST(s)) + 1;
479 else {
480 nl = PTR2SIZE(rp - s);
481 np = savestrbuf(s, nl);
484 if ((pwp = getpwnam(np)) == NULL) {
485 err = TRU1;
486 goto jasis;
488 np = pwp->pw_name;
491 nl = strlen(np);
492 rl = strlen(rp);
493 rv = salloc(nl + 1 + rl +1);
494 memcpy(rv, np, nl);
495 if (rl > 0) {
496 memcpy(rv + nl, rp, rl);
497 nl += rl;
499 rv[nl] = '\0';
500 goto jleave;
502 jasis:
503 rv = savestr(s);
504 jleave:
505 if (err_or_null != NULL)
506 *err_or_null = err;
507 NYD2_LEAVE;
508 return rv;
511 FL char *
512 n_shell_expand_var(char const *s, bool_t bsescape, bool_t *err_or_null)
514 struct shvar_stack top;
515 char *rv;
516 NYD2_ENTER;
518 memset(&top, 0, sizeof top);
520 top.shs_value = s;
521 if ((top.shs_err = err_or_null) != NULL)
522 *err_or_null = FAL0;
523 top.shs_bsesc = bsescape;
524 rv = _sh_exp_var(&top);
525 NYD2_LEAVE;
526 return rv;
529 FL int
530 n_shell_expand_escape(char const **s, bool_t use_nail_extensions)
532 char const *xs;
533 int c, n;
534 NYD2_ENTER;
536 xs = *s;
538 if ((c = *xs & 0xFF) == '\0')
539 goto jleave;
540 ++xs;
541 if (c != '\\')
542 goto jleave;
544 switch ((c = *xs & 0xFF)) {
545 case 'a': c = '\a'; break;
546 case 'b': c = '\b'; break;
547 case 'c': c = PROMPT_STOP; break;
548 case 'f': c = '\f'; break;
549 case 'n': c = '\n'; break;
550 case 'r': c = '\r'; break;
551 case 't': c = '\t'; break;
552 case 'v': c = '\v'; break;
554 /* Hexadecimal TODO uses ASCII */
555 case 'X':
556 case 'x': {
557 static ui8_t const hexatoi[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
558 #undef a_HEX
559 #define a_HEX(n) \
560 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
562 c = 0;
563 ++xs;
564 if(hexchar(*xs))
565 c = a_HEX(*xs);
566 else{
567 --xs;
568 if(options & OPT_D_V)
569 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs - 1);
570 c = '\\';
571 goto jleave;
573 ++xs;
574 if(hexchar(*xs)){
575 c <<= 4;
576 c += a_HEX(*xs);
577 ++xs;
579 goto jleave;
581 #undef a_HEX
583 /* octal, with optional 0 prefix */
584 case '0':
585 ++xs;
586 if(0){
587 default:
588 if(*xs == '\0'){
589 c = '\\';
590 break;
593 for (c = 0, n = 3; n-- > 0 && octalchar(*xs); ++xs) {
594 c <<= 3;
595 c |= *xs - '0';
597 goto jleave;
599 /* S-nail extension for nice (get)prompt(()) support */
600 case '&':
601 case '?':
602 case '$':
603 case '@':
604 if (use_nail_extensions) {
605 switch (c) {
606 case '&': c = ok_blook(bsdcompat) ? '&' : '?'; break;
607 case '?': c = (pstate & PS_EVAL_ERROR) ? '1' : '0'; break;
608 case '$': c = PROMPT_DOLLAR; break;
609 case '@': c = PROMPT_AT; break;
611 break;
614 /* FALLTHRU */
615 case '\0':
616 /* A sole <backslash> at EOS is treated as-is! */
617 c = '\\';
618 /* FALLTHRU */
619 case '\\':
620 break;
623 ++xs;
624 jleave:
625 *s = xs;
626 NYD2_LEAVE;
627 return c;
630 /* s-it-mode */