mutt_expand_file_fmt -> m_quotefile_fmt in file.c
[apps/madmutt.git] / muttlib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <grp.h>
14 #include <pwd.h>
15 #include <utime.h>
16
17 #include <lib-mime/mime.h>
18 #include <lib-ui/curses.h>
19 #include <lib-ui/enter.h>
20 #include <lib-sys/unix.h>
21 #include <lib-mx/mx.h>
22
23 #include "alias.h"
24 #include "mutt.h"
25 #include "attach.h"
26
27 #include "version.h"
28
29 #include <imap/imap.h>
30
31 #include <lib-crypt/crypt.h>
32
33 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
34
35 /* Modified by blong to accept a "suggestion" for file name.  If
36  * that file exists, then construct one with unique name but 
37  * keep any extension.  This might fail, I guess.
38  * Renamed to mutt_adv_mktemp so I only have to change where it's
39  * called, and not all possible cases.
40  */
41 void mutt_adv_mktemp (const char* dir, char *s, ssize_t l)
42 {
43     int fd;
44
45     fd = m_tempfd(s, l, m_strisempty(dir) ? NONULL(Tempdir) : dir, s);
46     if (fd < 0) {
47         *s = '\0';
48     } else {
49         close(fd);
50         unlink(s);
51     }
52 }
53
54 /* returns true if the header contained in "s" is in list "t" */
55 int mutt_matches_ignore (const char *s, string_list_t * t)
56 {
57   for (; t; t = t->next) {
58     if (!ascii_strncasecmp (s, t->data, m_strlen(t->data))
59         || *t->data == '*')
60       return 1;
61   }
62   return 0;
63 }
64
65 ssize_t _mutt_expand_path(char *s, ssize_t slen, int rx)
66 {
67     char p[_POSIX_PATH_MAX] = "";
68     char tmp[_POSIX_PATH_MAX];
69     const char *tail = "";
70
71     do {
72         const address_t *alias;
73
74         switch (*s) {
75           case '~':
76             if (s[1] == '/' || s[1] == '\0') {
77                 m_strcpy(p, sizeof(p), Homedir);
78                 tail = s + 1;
79             } else {
80                 struct passwd *pw;
81                 tail = m_strchrnul(s + 1, '/');
82
83                 m_strncpy(tmp, sizeof(tmp), s + 1, tail - s - 1);
84
85                 if ((pw = getpwnam(tmp))) {
86                     m_strcpy(p, sizeof(p), pw->pw_dir);
87                 } else {
88                     /* user not found! */
89                     tail = s;
90                 }
91             }
92             break;
93
94           case '=':
95           case '+':
96             /* if folder = imap[s]://host/: don't append slash */
97             if (imap_is_magic(NONULL(Maildir), NULL) == M_IMAP
98             &&  Maildir[m_strlen(Maildir) - 1] == '/') {
99                 m_strcpy(p, sizeof(p), Maildir);
100             } else {
101                 snprintf(p, sizeof(p), "%s/", NONULL(Maildir));
102             }
103
104             tail = s + 1;
105             break;
106
107             /* elm compatibility, @ expands alias to user name */
108
109           case '@':
110             if ((alias = alias_lookup(s + 1))) {
111                 HEADER h;
112                 header_init(&h);
113                 h.env = envelope_new();
114                 h.env->from = h.env->to = (address_t *)alias;
115                 mutt_default_save(p, sizeof (p), &h);
116                 h.env->from = h.env->to = NULL;
117                 header_wipe(&h);
118
119                 if (*p != '@') {
120                     /* recurse iff the result do not starts with '@' */
121                     m_strcpy(s, slen, p);
122                     continue;
123                 }
124             }
125             break;
126
127           case '>':
128             m_strcpy(p, sizeof(p), Inbox);
129             tail = s + 1;
130             break;
131
132           case '<':
133             m_strcpy(p, sizeof(p), Outbox);
134             tail = s + 1;
135             break;
136
137           case '!':
138             if (s[1] == '!') {
139                 m_strcpy(p, sizeof(p), LastFolder);
140                 tail = s + 2;
141             } else {
142                 m_strcpy(p, sizeof(p), Spoolfile);
143                 tail = s + 1;
144             }
145             break;
146
147           case '-':
148             m_strcpy(p, sizeof(p), NONULL(LastFolder));
149             tail = s + 1;
150             break;
151
152           case '^':
153             m_strcpy(p, sizeof(p), NONULL(CurrentFolder));
154             tail = s + 1;
155             break;
156
157           default:
158             *p = '\0';
159             tail = s;
160         }
161     } while (0);
162
163     if (rx) {
164         char q[_POSIX_PATH_MAX];
165         rx_sanitize_string(q, sizeof(q), p);
166         snprintf(tmp, sizeof(tmp), "%s%s", q, tail);
167     } else {
168         snprintf(tmp, sizeof(tmp), "%s%s", p, tail);
169     }
170
171     return m_strcpy(s, slen, tmp);
172 }
173
174 void mutt_mktemp(char *s)
175 {
176     int fd = m_tempfd(s, _POSIX_PATH_MAX, NONULL(Tempdir), NULL);
177     if (fd < 0) {
178         *s = '\0';
179     } else {
180         close(fd);
181         unlink(s);
182     }
183 }
184
185 /* collapse the pathname using ~ or = when possible */
186 void mutt_pretty_mailbox (char *s)
187 {
188   char *p = s, *q = s;
189   ssize_t len;
190   url_scheme_t scheme;
191
192   scheme = url_check_scheme (s);
193
194   if (scheme == U_IMAP || scheme == U_IMAPS) {
195     imap_pretty_mailbox (s);
196     return;
197   }
198
199   /* if s is an url, only collapse path component */
200   if (scheme != U_UNKNOWN) {
201     p = strchr (s, ':') + 1;
202     if (!strncmp (p, "//", 2))
203       q = strchr (p + 2, '/');
204     if (!q)
205       q = strchr (p, '\0');
206     p = q;
207   }
208
209   /* first attempt to collapse the pathname */
210   while (*p) {
211     if (*p == '/' && p[1] == '/') {
212       *q++ = '/';
213       p += 2;
214     }
215     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
216       *q++ = '/';
217       p += 3;
218     }
219     else
220       *q++ = *p++;
221   }
222   *q = 0;
223
224   if (m_strncmp(s, Maildir, (len = m_strlen(Maildir))) == 0 &&
225       s[len] == '/') {
226     *s++ = '=';
227     memmove (s, s + len, m_strlen(s + len) + 1);
228   }
229   else if (m_strncmp(s, Homedir, (len = m_strlen(Homedir))) == 0 &&
230            s[len] == '/') {
231     *s++ = '~';
232     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
233   }
234 }
235
236 /* return 0 on success, -1 on abort, 1 on error */
237 int mutt_check_overwrite (const char *attname, const char *path,
238                           char *fname, ssize_t flen, int *append,
239                           char **directory)
240 {
241   int rc = 0;
242   char tmp[_POSIX_PATH_MAX];
243   struct stat st;
244
245   m_strcpy(fname, flen, path);
246   if (access (fname, F_OK) != 0)
247     return 0;
248   if (stat (fname, &st) != 0)
249     return -1;
250   if (S_ISDIR (st.st_mode)) {
251     if (directory) {
252       switch (mutt_multi_choice
253               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
254                _("yna"))) {
255       case 3:                  /* all */
256         m_strreplace(directory, fname);
257         break;
258       case 1:                  /* yes */
259         p_delete(directory);
260         break;
261       case -1:                 /* abort */
262         p_delete(directory);
263         return -1;
264       case 2:                  /* no */
265         p_delete(directory);
266         return 1;
267       }
268     }
269     else
270       if ((rc = mutt_yesorno(_("File is a directory, save under it?"),
271                              M_YES)) != M_YES)
272       return (rc == M_NO) ? 1 : -1;
273
274     if (!attname || !attname[0]) {
275       tmp[0] = 0;
276       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
277                           M_FILE | M_CLEAR) != 0 || !tmp[0])
278         return (-1);
279       mutt_concat_path(fname, flen, path, tmp);
280     }
281     else
282       mutt_concat_path(fname, flen, path, mutt_basename(attname));
283   }
284
285   if (*append == 0 && access (fname, F_OK) == 0) {
286     switch (mutt_multi_choice
287             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
288     {
289     case -1:                   /* abort */
290       return -1;
291     case 3:                    /* cancel */
292       return 1;
293
294     case 2:                    /* append */
295       *append = M_SAVE_APPEND;
296       break;
297     case 1:                    /* overwrite */
298       *append = M_SAVE_OVERWRITE;
299       break;
300     }
301   }
302   return 0;
303 }
304
305 void mutt_save_path(char *d, ssize_t dsize, address_t *a)
306 {
307     if (a && a->mailbox) {
308         m_strcpy(d, dsize, a->mailbox);
309
310         if (!option(OPTSAVEADDRESS)) {
311             char *p = strpbrk(d, "%@");
312             if (p)
313                 *p = '\0';
314         }
315         m_strtolower(d);
316     } else {
317         *d = '\0';
318     }
319 }
320
321 void mutt_safe_path(char *s, ssize_t l, address_t *a)
322 {
323     mutt_save_path(s, l, a);
324
325     while (*s) {
326         if (*s == '/' || ISSPACE(*s) || !isprint((unsigned char)*s))
327             *s = '_';
328         s++;
329     }
330 }
331
332 void mutt_FormatString (char *dest,     /* output buffer */
333                         ssize_t destlen, /* output buffer len */
334                         const char *src,        /* template string */
335                         format_t * callback,    /* callback for processing */
336                         unsigned long data,     /* callback data */
337                         format_flag flags)
338 {                               /* callback flags */
339   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
340   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
341   ssize_t wlen, wid, count, col, len;
342
343   prefix[0] = '\0';
344   destlen--;                    /* save room for the terminal \0 */
345   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
346   col = wlen;
347
348   while (*src && wlen < destlen) {
349     if (*src == '%') {
350       if (*++src == '%') {
351         *wptr++ = '%';
352         wlen++;
353         col++;
354         src++;
355         continue;
356       }
357
358       if (*src == '?') {
359         flags |= M_FORMAT_OPTIONAL;
360         src++;
361       }
362       else {
363         flags &= ~M_FORMAT_OPTIONAL;
364
365         /* eat the format string */
366         cp = prefix;
367         count = 0;
368         while (count < ssizeof (prefix) &&
369                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
370         {
371           *cp++ = *src++;
372           count++;
373         }
374         *cp = 0;
375       }
376
377       if (!*src)
378         break;                  /* bad format */
379
380       ch = *src++;              /* save the character to switch on */
381
382       if (flags & M_FORMAT_OPTIONAL) {
383         if (*src != '?')
384           break;                /* bad format */
385         src++;
386
387         /* eat the `if' part of the string */
388         cp = ifstring;
389         count = 0;
390         while (count < ssizeof (ifstring) && *src && *src != '?'
391                && *src != '&') {
392           *cp++ = *src++;
393           count++;
394         }
395         *cp = 0;
396
397         /* eat the `else' part of the string (optional) */
398         if (*src == '&')
399           src++;                /* skip the & */
400         cp = elsestring;
401         count = 0;
402         while (count < ssizeof (elsestring) && *src && *src != '?') {
403           *cp++ = *src++;
404           count++;
405         }
406         *cp = 0;
407
408         if (!*src)
409           break;                /* bad format */
410
411         src++;                  /* move past the trailing `?' */
412       }
413
414       /* handle generic cases first */
415       if (ch == '>') {
416         /* right justify to EOL */
417         ch = *src++;            /* pad char */
418         /* calculate space left on line.  if we've already written more data
419            than will fit on the line, ignore the rest of the line */
420         if (DrawFullLine || option (OPTSTATUSONTOP))
421           count = (COLS < destlen ? COLS : destlen);
422         else
423           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
424         if (count > col) {
425           count -= col;         /* how many columns left on this line */
426           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
427           wid = m_strlen(buf);
428           if (count > wid) {
429             count -= wid;       /* how many chars to pad */
430             memset (wptr, ch, count);
431             wptr += count;
432             col += count;
433           }
434           if (wid + wlen > destlen)
435             len = destlen - wlen;
436           else
437             len = wid;
438           memcpy (wptr, buf, len);
439           wptr += len;
440           wlen += len;
441           col += mutt_strwidth (buf);
442         }
443         break;                  /* skip rest of input */
444       }
445       else if (ch == '|') {
446         /* pad to EOL */
447         ch = *src++;
448         if (destlen > COLS)
449           destlen = COLS;
450         if (destlen > wlen) {
451           count = destlen - wlen;
452           memset (wptr, ch, count);
453           wptr += count;
454         }
455         break;                  /* skip rest of input */
456       }
457       else {
458         short lower = 0;
459         short nodots = 0;
460
461         while (ch == '_' || ch == ':') {
462           if (ch == '_')
463             lower = 1;
464           else if (ch == ':')
465             nodots = 1;
466
467           ch = *src++;
468         }
469
470         /* use callback function to handle this case */
471         src =
472           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
473                     data, flags);
474
475         if (lower)
476           m_strtolower(buf);
477         if (nodots) {
478           char *p = buf;
479
480           for (; *p; p++)
481             if (*p == '.')
482               *p = '_';
483         }
484
485         if ((len = m_strlen(buf)) + wlen > destlen)
486           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
487
488         memcpy (wptr, buf, len);
489         wptr += len;
490         wlen += len;
491         col += mutt_strwidth (buf);
492       }
493     }
494     else if (*src == '\\') {
495       if (!*++src)
496         break;
497       switch (*src) {
498       case 'n':
499         *wptr = '\n';
500         break;
501       case 't':
502         *wptr = '\t';
503         break;
504       case 'r':
505         *wptr = '\r';
506         break;
507       case 'f':
508         *wptr = '\f';
509         break;
510       case 'v':
511         *wptr = '\v';
512         break;
513       default:
514         *wptr = *src;
515         break;
516       }
517       src++;
518       wptr++;
519       wlen++;
520       col++;
521     }
522     else {
523       unsigned int bar = strcspn(src, "%\\");
524       char *bar2 = p_dupstr(src, bar);
525
526       while (bar--) {
527         *wptr++ = *src++;
528         wlen++;
529       }
530       col += mutt_strwidth (bar2);
531       p_delete(&bar2);
532     }
533   }
534   *wptr = 0;
535 }
536
537 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
538 int mutt_save_confirm (const char *s, struct stat *st)
539 {
540   char tmp[_POSIX_PATH_MAX];
541   int ret = 0;
542   int rc;
543   int magic = 0;
544
545   magic = mx_get_magic (s);
546
547   if (magic == M_POP) {
548     mutt_error _("Can't save message to POP mailbox.");
549
550     return 1;
551   }
552
553 #ifdef USE_NNTP
554   if (magic == M_NNTP) {
555     mutt_error _("Can't save message to newsserver.");
556
557     return 0;
558   }
559 #endif
560
561   if (magic > 0 && !mx_access (s, W_OK)) {
562     if (option (OPTCONFIRMAPPEND) &&
563         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
564       /* if we're appending to the trash, there's no point in asking */
565       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
566       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
567         ret = 1;
568       else if (rc == -1)
569         ret = -1;
570     }
571   }
572
573   if (stat (s, st) != -1) {
574     if (magic == -1) {
575       mutt_error (_("%s is not a mailbox!"), s);
576       return 1;
577     }
578   } else {
579     if (magic != M_IMAP)
580     {
581       st->st_mtime = 0;
582       st->st_atime = 0;
583
584       if (errno == ENOENT) {
585         if (option (OPTCONFIRMCREATE)) {
586           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
587           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
588             ret = 1;
589           else if (rc == -1)
590             ret = -1;
591         }
592       } else {
593         mutt_perror (s);
594         return 1;
595       }
596     }
597   }
598
599   CLEARLINE (LINES - 1);
600   return (ret);
601 }
602
603 void mutt_sleep (short s)
604 {
605     sleep(MAX(s, SleepTime));
606 }
607
608 /* Decrease a file's modification time by 1 second */
609 time_t mutt_decrease_mtime (const char *f, struct stat *st)
610 {
611   struct utimbuf utim;
612   struct stat _st;
613   time_t mtime;
614
615   if (!st) {
616     if (stat (f, &_st) == -1)
617       return -1;
618     st = &_st;
619   }
620
621   if ((mtime = st->st_mtime) == time (NULL)) {
622     mtime -= 1;
623     utim.actime = mtime;
624     utim.modtime = mtime;
625     utime (f, &utim);
626   }
627
628   return mtime;
629 }
630
631 const char *mutt_make_version (int full)
632 {
633   static char vstring[STRING];
634
635   if (full)
636     snprintf (vstring, sizeof (vstring),
637               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
638               MUTT_VERSION, MUTT_REVISION);
639   else
640     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
641               MUTT_VERSION, MUTT_REVISION);
642   return vstring;
643 }
644
645 int mutt_match_spam_list (const char *s, rx_t * l, char *text, int x)
646 {
647   static regmatch_t *pmatch = NULL;
648   static int nmatch = 0;
649   int i, n, tlen;
650   char *p;
651
652   if (!s)
653     return 0;
654
655   tlen = 0;
656
657   for (; l; l = l->next) {
658     /* If this pattern needs more matches, expand pmatch. */
659     if (l->nmatch > nmatch) {
660       p_realloc(&pmatch, l->nmatch);
661       nmatch = l->nmatch;
662     }
663
664     /* Does this pattern match? */
665     if (regexec(l->rx, s, l->nmatch, (regmatch_t *)pmatch, (int) 0) == 0) {
666       /* Copy template into text, with substitutions. */
667       for (p = l->template; *p;) {
668         if (*p == '%') {
669           n = atoi (++p);       /* find pmatch index */
670           while (isdigit ((unsigned char) *p))
671             ++p;                /* skip subst token */
672           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
673             text[tlen++] = s[i];
674         }
675         else {
676           text[tlen++] = *p++;
677         }
678       }
679       text[tlen] = '\0';
680       return 1;
681     }
682   }
683
684   return 0;
685 }
686
687 /* return 1 if address lists are strictly identical */
688 static int mutt_cmp_addr (const address_t * a, const address_t * b)
689 {
690   while (a && b) {
691     if (m_strcmp(a->mailbox, b->mailbox) ||
692         m_strcmp(a->personal, b->personal))
693       return (0);
694
695     a = a->next;
696     b = b->next;
697   }
698   if (a || b)
699     return (0);
700
701   return (1);
702 }
703
704 static int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
705 {
706   while (a && b) {
707     if (m_strcmp(a->data, b->data))
708       return (0);
709
710     a = a->next;
711     b = b->next;
712   }
713   if (a || b)
714     return (0);
715
716   return (1);
717 }
718
719 static int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
720 {
721   if (e1 && e2) {
722     if (m_strcmp(e1->message_id, e2->message_id) ||
723         m_strcmp(e1->subject, e2->subject) ||
724         !mutt_cmp_list (e1->references, e2->references) ||
725         !mutt_cmp_addr (e1->from, e2->from) ||
726         !mutt_cmp_addr (e1->sender, e2->sender) ||
727         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
728         !mutt_cmp_addr (e1->to, e2->to) ||
729         !mutt_cmp_addr (e1->cc, e2->cc) ||
730         !mutt_cmp_addr (e1->return_path, e2->return_path))
731       return (0);
732     else
733       return (1);
734   }
735   else {
736     if (e1 == NULL && e2 == NULL)
737       return (1);
738     else
739       return (0);
740   }
741 }
742
743 static int mutt_cmp_body (const BODY * b1, const BODY * b2)
744 {
745   if (b1->type != b2->type ||
746       b1->encoding != b2->encoding ||
747       m_strcmp(b1->subtype, b2->subtype) ||
748       m_strcmp(b1->description, b2->description) ||
749       !parameter_equal(b1->parameter, b2->parameter) ||
750       b1->length != b2->length)
751     return (0);
752   return (1);
753 }
754 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
755   if (h1 && h2) {
756     if (h1->received != h2->received ||
757         h1->date_sent != h2->date_sent ||
758         h1->content->length != h2->content->length ||
759         h1->lines != h2->lines ||
760         h1->zhours != h2->zhours ||
761         h1->zminutes != h2->zminutes ||
762         h1->zoccident != h2->zoccident ||
763         h1->mime != h2->mime ||
764         !mutt_cmp_env (h1->env, h2->env) ||
765         !mutt_cmp_body (h1->content, h2->content))
766       return (0);
767     else
768       return (1);
769   }
770   else {
771     if (h1 == NULL && h2 == NULL)
772       return (1);
773     else
774       return (0);
775   }
776 }
777
778
779 int mutt_extract_token(BUFFER *dest, BUFFER *tok, int flags)
780 {
781     char ch;
782     char qc = 0;                  /* quote char */
783     char *pc;
784
785     /* reset the destination pointer to the beginning of the buffer */
786     dest->dptr = dest->data;
787
788     tok->dptr = vskipspaces(tok->dptr);
789     while ((ch = *tok->dptr)) {
790         if (!qc) {
791             if ((ISSPACE(ch) && !(flags & M_TOKEN_SPACE))
792             || (ch == '#' && !(flags & M_TOKEN_COMMENT))
793             || (ch == '=' && (flags & M_TOKEN_EQUAL))
794             || (ch == ';' && !(flags & M_TOKEN_SEMICOLON))
795             || ((flags & M_TOKEN_PATTERN) && strchr("~=!|", ch)))
796             {
797                 break;
798             }
799         }
800
801         tok->dptr++;
802
803         if (ch == qc) {
804             qc = 0;                     /* end of quote */
805         } else
806         if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE)) {
807             qc = ch;
808         } else
809         if (ch == '\\' && qc != '\'') {
810             if (!*tok->dptr)
811                 return -1;              /* premature end of token */
812
813             switch (ch = *tok->dptr++) {
814               case 'c':
815               case 'C':
816                 if (!*tok->dptr)
817                     return -1;          /* premature end of token */
818                 mutt_buffer_addch(dest,
819                                   (ascii_toupper(*tok->dptr) - 'A' + 1) & 0x7f);
820                 tok->dptr++;
821                 break;
822               case 'r':
823                 mutt_buffer_addch(dest, '\r');
824                 break;
825               case 'n':
826                 mutt_buffer_addch(dest, '\n');
827                 break;
828               case 't':
829                 mutt_buffer_addch(dest, '\t');
830                 break;
831               case 'f':
832                 mutt_buffer_addch(dest, '\f');
833                 break;
834               case 'e':
835                 mutt_buffer_addch(dest, '\033');
836                 break;
837               default:
838                 if (isdigit((unsigned char)ch)
839                 &&  isdigit((unsigned char)*tok->dptr)
840                 &&  isdigit((unsigned char)*(tok->dptr + 1)))
841                 {
842                     mutt_buffer_addch(dest, (ch << 6) + (*tok->dptr << 3) +
843                                             *(tok->dptr + 1) - 3504);
844                     tok->dptr += 2;
845                 } else {
846                     mutt_buffer_addch(dest, ch);
847                 }
848             }
849         } else
850         if (ch == '^' && (flags & M_TOKEN_CONDENSE)) {
851             if (!*tok->dptr)
852                 return -1;              /* premature end of token */
853             ch = *tok->dptr++;
854             if (ch == '^') {
855                 mutt_buffer_addch(dest, ch);
856             } else
857             if (ch == '[') {
858                 mutt_buffer_addch(dest, '\033');
859             } else
860             if (isalpha((unsigned char)ch)) {
861                 mutt_buffer_addch(dest, ascii_toupper(ch) - 'A' + 1);
862             } else {
863                 mutt_buffer_addch(dest, '^');
864                 mutt_buffer_addch(dest, ch);
865             }
866         } else
867         if (ch == '`' && (!qc || qc == '"')) {
868             FILE *fp;
869             pid_t pid;
870             char *cmd, *ptr;
871             ssize_t expnlen;
872             BUFFER expn;
873             int line = 0;
874
875             pc = tok->dptr;
876             do {
877                 if ((pc = strpbrk(pc, "\\`"))) {
878                     /* skip any quoted chars */
879                     if (*pc == '\\')
880                         pc += 2;
881                 }
882             } while (pc && *pc != '`');
883             if (!pc) {
884                 return (-1);
885             }
886
887             cmd = p_dupstr(tok->dptr, pc - tok->dptr);
888             if ((pid = mutt_create_filter(cmd, NULL, &fp, NULL)) < 0) {
889                 p_delete(&cmd);
890                 return -1;
891             }
892             p_delete(&cmd);
893
894             tok->dptr = pc + 1;
895
896             /* read line */
897             p_clear(&expn, 1);
898             expn.data = mutt_read_line(NULL, &expn.dsize, fp, &line);
899             m_fclose(&fp);
900             mutt_wait_filter(pid);
901
902             /* if we got output, make a new string consiting of the shell ouptput
903                plus whatever else was left on the original line */
904             /* BUT: If this is inside a quoted string, directly add output to 
905              * the token */
906             if (expn.data && qc) {
907                 mutt_buffer_addstr(dest, expn.data);
908                 p_delete(&expn.data);
909             } else
910             if (expn.data) {
911                 expnlen = m_strlen(expn.data);
912                 tok->dsize = expnlen + m_strlen(tok->dptr) + 1;
913                 ptr = xmalloc(tok->dsize);
914                 memcpy(ptr, expn.data, expnlen);
915                 strcpy(ptr + expnlen, tok->dptr);      /* __STRCPY_CHECKED__ */
916                 if (tok->destroy)
917                     p_delete(&tok->data);
918                 tok->data = ptr;
919                 tok->dptr = ptr;
920                 tok->destroy = 1;       /* mark that the caller should destroy this data */
921                 ptr = NULL;
922                 p_delete(&expn.data);
923             }
924         } else
925         if (ch == '$' && (!qc || qc == '"')
926         && (*tok->dptr == '{' || isalpha((unsigned char)*tok->dptr)))
927         {
928             char *env = NULL, *var = NULL;
929
930             if (*tok->dptr == '{') {
931                 tok->dptr++;
932                 if ((pc = strchr (tok->dptr, '}'))) {
933                     var = p_dupstr(tok->dptr, pc - tok->dptr);
934                     tok->dptr = pc + 1;
935                 }
936             } else {
937                 for (pc = tok->dptr; isalnum((unsigned char)*pc) || *pc == '_';
938                      pc++);
939                 var = p_dupstr(tok->dptr, pc - tok->dptr);
940                 tok->dptr = pc;
941             }
942             if (var) {
943                 char tmp[STRING];
944                 if ((env = getenv (var))
945                 || (mutt_option_value (var, tmp, sizeof (tmp)) && (env = tmp)))
946                 {
947                     mutt_buffer_addstr (dest, env);
948                 }
949             }
950             p_delete(&var);
951         } else {
952             mutt_buffer_addch(dest, ch);
953         }
954     }
955     mutt_buffer_addch(dest, 0);  /* terminate the string */
956     tok->dptr = vskipspaces(tok->dptr);
957     return 0;
958 }