mutt_*mktemp--
[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 void
237 mutt_expand_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
238 {
239     char tmp[LONG_STRING];
240     mutt_quote_filename(tmp, sizeof(tmp), src);
241     m_file_fmt(dst, n, fmt, tmp);
242 }
243
244 /* return 0 on success, -1 on abort, 1 on error */
245 int mutt_check_overwrite (const char *attname, const char *path,
246                           char *fname, ssize_t flen, int *append,
247                           char **directory)
248 {
249   int rc = 0;
250   char tmp[_POSIX_PATH_MAX];
251   struct stat st;
252
253   m_strcpy(fname, flen, path);
254   if (access (fname, F_OK) != 0)
255     return 0;
256   if (stat (fname, &st) != 0)
257     return -1;
258   if (S_ISDIR (st.st_mode)) {
259     if (directory) {
260       switch (mutt_multi_choice
261               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
262                _("yna"))) {
263       case 3:                  /* all */
264         m_strreplace(directory, fname);
265         break;
266       case 1:                  /* yes */
267         p_delete(directory);
268         break;
269       case -1:                 /* abort */
270         p_delete(directory);
271         return -1;
272       case 2:                  /* no */
273         p_delete(directory);
274         return 1;
275       }
276     }
277     else
278       if ((rc = mutt_yesorno(_("File is a directory, save under it?"),
279                              M_YES)) != M_YES)
280       return (rc == M_NO) ? 1 : -1;
281
282     if (!attname || !attname[0]) {
283       tmp[0] = 0;
284       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
285                           M_FILE | M_CLEAR) != 0 || !tmp[0])
286         return (-1);
287       mutt_concat_path(fname, flen, path, tmp);
288     }
289     else
290       mutt_concat_path(fname, flen, path, mutt_basename(attname));
291   }
292
293   if (*append == 0 && access (fname, F_OK) == 0) {
294     switch (mutt_multi_choice
295             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
296     {
297     case -1:                   /* abort */
298       return -1;
299     case 3:                    /* cancel */
300       return 1;
301
302     case 2:                    /* append */
303       *append = M_SAVE_APPEND;
304       break;
305     case 1:                    /* overwrite */
306       *append = M_SAVE_OVERWRITE;
307       break;
308     }
309   }
310   return 0;
311 }
312
313 void mutt_save_path(char *d, ssize_t dsize, address_t *a)
314 {
315     if (a && a->mailbox) {
316         m_strcpy(d, dsize, a->mailbox);
317
318         if (!option(OPTSAVEADDRESS)) {
319             char *p = strpbrk(d, "%@");
320             if (p)
321                 *p = '\0';
322         }
323         m_strtolower(d);
324     } else {
325         *d = '\0';
326     }
327 }
328
329 void mutt_safe_path(char *s, ssize_t l, address_t *a)
330 {
331     mutt_save_path(s, l, a);
332
333     while (*s) {
334         if (*s == '/' || ISSPACE(*s) || !isprint((unsigned char)*s))
335             *s = '_';
336         s++;
337     }
338 }
339
340 void mutt_FormatString (char *dest,     /* output buffer */
341                         ssize_t destlen, /* output buffer len */
342                         const char *src,        /* template string */
343                         format_t * callback,    /* callback for processing */
344                         unsigned long data,     /* callback data */
345                         format_flag flags)
346 {                               /* callback flags */
347   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
348   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
349   ssize_t wlen, wid, count, col, len;
350
351   prefix[0] = '\0';
352   destlen--;                    /* save room for the terminal \0 */
353   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
354   col = wlen;
355
356   while (*src && wlen < destlen) {
357     if (*src == '%') {
358       if (*++src == '%') {
359         *wptr++ = '%';
360         wlen++;
361         col++;
362         src++;
363         continue;
364       }
365
366       if (*src == '?') {
367         flags |= M_FORMAT_OPTIONAL;
368         src++;
369       }
370       else {
371         flags &= ~M_FORMAT_OPTIONAL;
372
373         /* eat the format string */
374         cp = prefix;
375         count = 0;
376         while (count < ssizeof (prefix) &&
377                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
378         {
379           *cp++ = *src++;
380           count++;
381         }
382         *cp = 0;
383       }
384
385       if (!*src)
386         break;                  /* bad format */
387
388       ch = *src++;              /* save the character to switch on */
389
390       if (flags & M_FORMAT_OPTIONAL) {
391         if (*src != '?')
392           break;                /* bad format */
393         src++;
394
395         /* eat the `if' part of the string */
396         cp = ifstring;
397         count = 0;
398         while (count < ssizeof (ifstring) && *src && *src != '?'
399                && *src != '&') {
400           *cp++ = *src++;
401           count++;
402         }
403         *cp = 0;
404
405         /* eat the `else' part of the string (optional) */
406         if (*src == '&')
407           src++;                /* skip the & */
408         cp = elsestring;
409         count = 0;
410         while (count < ssizeof (elsestring) && *src && *src != '?') {
411           *cp++ = *src++;
412           count++;
413         }
414         *cp = 0;
415
416         if (!*src)
417           break;                /* bad format */
418
419         src++;                  /* move past the trailing `?' */
420       }
421
422       /* handle generic cases first */
423       if (ch == '>') {
424         /* right justify to EOL */
425         ch = *src++;            /* pad char */
426         /* calculate space left on line.  if we've already written more data
427            than will fit on the line, ignore the rest of the line */
428         if (DrawFullLine || option (OPTSTATUSONTOP))
429           count = (COLS < destlen ? COLS : destlen);
430         else
431           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
432         if (count > col) {
433           count -= col;         /* how many columns left on this line */
434           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
435           wid = m_strlen(buf);
436           if (count > wid) {
437             count -= wid;       /* how many chars to pad */
438             memset (wptr, ch, count);
439             wptr += count;
440             col += count;
441           }
442           if (wid + wlen > destlen)
443             len = destlen - wlen;
444           else
445             len = wid;
446           memcpy (wptr, buf, len);
447           wptr += len;
448           wlen += len;
449           col += mutt_strwidth (buf);
450         }
451         break;                  /* skip rest of input */
452       }
453       else if (ch == '|') {
454         /* pad to EOL */
455         ch = *src++;
456         if (destlen > COLS)
457           destlen = COLS;
458         if (destlen > wlen) {
459           count = destlen - wlen;
460           memset (wptr, ch, count);
461           wptr += count;
462         }
463         break;                  /* skip rest of input */
464       }
465       else {
466         short lower = 0;
467         short nodots = 0;
468
469         while (ch == '_' || ch == ':') {
470           if (ch == '_')
471             lower = 1;
472           else if (ch == ':')
473             nodots = 1;
474
475           ch = *src++;
476         }
477
478         /* use callback function to handle this case */
479         src =
480           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
481                     data, flags);
482
483         if (lower)
484           m_strtolower(buf);
485         if (nodots) {
486           char *p = buf;
487
488           for (; *p; p++)
489             if (*p == '.')
490               *p = '_';
491         }
492
493         if ((len = m_strlen(buf)) + wlen > destlen)
494           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
495
496         memcpy (wptr, buf, len);
497         wptr += len;
498         wlen += len;
499         col += mutt_strwidth (buf);
500       }
501     }
502     else if (*src == '\\') {
503       if (!*++src)
504         break;
505       switch (*src) {
506       case 'n':
507         *wptr = '\n';
508         break;
509       case 't':
510         *wptr = '\t';
511         break;
512       case 'r':
513         *wptr = '\r';
514         break;
515       case 'f':
516         *wptr = '\f';
517         break;
518       case 'v':
519         *wptr = '\v';
520         break;
521       default:
522         *wptr = *src;
523         break;
524       }
525       src++;
526       wptr++;
527       wlen++;
528       col++;
529     }
530     else {
531       unsigned int bar = strcspn(src, "%\\");
532       char *bar2 = p_dupstr(src, bar);
533
534       while (bar--) {
535         *wptr++ = *src++;
536         wlen++;
537       }
538       col += mutt_strwidth (bar2);
539       p_delete(&bar2);
540     }
541   }
542   *wptr = 0;
543 }
544
545 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
546 int mutt_save_confirm (const char *s, struct stat *st)
547 {
548   char tmp[_POSIX_PATH_MAX];
549   int ret = 0;
550   int rc;
551   int magic = 0;
552
553   magic = mx_get_magic (s);
554
555   if (magic == M_POP) {
556     mutt_error _("Can't save message to POP mailbox.");
557
558     return 1;
559   }
560
561 #ifdef USE_NNTP
562   if (magic == M_NNTP) {
563     mutt_error _("Can't save message to newsserver.");
564
565     return 0;
566   }
567 #endif
568
569   if (magic > 0 && !mx_access (s, W_OK)) {
570     if (option (OPTCONFIRMAPPEND) &&
571         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
572       /* if we're appending to the trash, there's no point in asking */
573       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
574       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
575         ret = 1;
576       else if (rc == -1)
577         ret = -1;
578     }
579   }
580
581   if (stat (s, st) != -1) {
582     if (magic == -1) {
583       mutt_error (_("%s is not a mailbox!"), s);
584       return 1;
585     }
586   } else {
587     if (magic != M_IMAP)
588     {
589       st->st_mtime = 0;
590       st->st_atime = 0;
591
592       if (errno == ENOENT) {
593         if (option (OPTCONFIRMCREATE)) {
594           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
595           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
596             ret = 1;
597           else if (rc == -1)
598             ret = -1;
599         }
600       } else {
601         mutt_perror (s);
602         return 1;
603       }
604     }
605   }
606
607   CLEARLINE (LINES - 1);
608   return (ret);
609 }
610
611 void mutt_sleep (short s)
612 {
613     sleep(MAX(s, SleepTime));
614 }
615
616 /* Decrease a file's modification time by 1 second */
617 time_t mutt_decrease_mtime (const char *f, struct stat *st)
618 {
619   struct utimbuf utim;
620   struct stat _st;
621   time_t mtime;
622
623   if (!st) {
624     if (stat (f, &_st) == -1)
625       return -1;
626     st = &_st;
627   }
628
629   if ((mtime = st->st_mtime) == time (NULL)) {
630     mtime -= 1;
631     utim.actime = mtime;
632     utim.modtime = mtime;
633     utime (f, &utim);
634   }
635
636   return mtime;
637 }
638
639 const char *mutt_make_version (int full)
640 {
641   static char vstring[STRING];
642
643   if (full)
644     snprintf (vstring, sizeof (vstring),
645               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
646               MUTT_VERSION, MUTT_REVISION);
647   else
648     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
649               MUTT_VERSION, MUTT_REVISION);
650   return vstring;
651 }
652
653 int mutt_match_spam_list (const char *s, rx_t * l, char *text, int x)
654 {
655   static regmatch_t *pmatch = NULL;
656   static int nmatch = 0;
657   int i, n, tlen;
658   char *p;
659
660   if (!s)
661     return 0;
662
663   tlen = 0;
664
665   for (; l; l = l->next) {
666     /* If this pattern needs more matches, expand pmatch. */
667     if (l->nmatch > nmatch) {
668       p_realloc(&pmatch, l->nmatch);
669       nmatch = l->nmatch;
670     }
671
672     /* Does this pattern match? */
673     if (regexec(l->rx, s, l->nmatch, (regmatch_t *)pmatch, (int) 0) == 0) {
674       /* Copy template into text, with substitutions. */
675       for (p = l->template; *p;) {
676         if (*p == '%') {
677           n = atoi (++p);       /* find pmatch index */
678           while (isdigit ((unsigned char) *p))
679             ++p;                /* skip subst token */
680           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
681             text[tlen++] = s[i];
682         }
683         else {
684           text[tlen++] = *p++;
685         }
686       }
687       text[tlen] = '\0';
688       return 1;
689     }
690   }
691
692   return 0;
693 }
694
695 /* return 1 if address lists are strictly identical */
696 static int mutt_cmp_addr (const address_t * a, const address_t * b)
697 {
698   while (a && b) {
699     if (m_strcmp(a->mailbox, b->mailbox) ||
700         m_strcmp(a->personal, b->personal))
701       return (0);
702
703     a = a->next;
704     b = b->next;
705   }
706   if (a || b)
707     return (0);
708
709   return (1);
710 }
711
712 static int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
713 {
714   while (a && b) {
715     if (m_strcmp(a->data, b->data))
716       return (0);
717
718     a = a->next;
719     b = b->next;
720   }
721   if (a || b)
722     return (0);
723
724   return (1);
725 }
726
727 static int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
728 {
729   if (e1 && e2) {
730     if (m_strcmp(e1->message_id, e2->message_id) ||
731         m_strcmp(e1->subject, e2->subject) ||
732         !mutt_cmp_list (e1->references, e2->references) ||
733         !mutt_cmp_addr (e1->from, e2->from) ||
734         !mutt_cmp_addr (e1->sender, e2->sender) ||
735         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
736         !mutt_cmp_addr (e1->to, e2->to) ||
737         !mutt_cmp_addr (e1->cc, e2->cc) ||
738         !mutt_cmp_addr (e1->return_path, e2->return_path))
739       return (0);
740     else
741       return (1);
742   }
743   else {
744     if (e1 == NULL && e2 == NULL)
745       return (1);
746     else
747       return (0);
748   }
749 }
750
751 static int mutt_cmp_body (const BODY * b1, const BODY * b2)
752 {
753   if (b1->type != b2->type ||
754       b1->encoding != b2->encoding ||
755       m_strcmp(b1->subtype, b2->subtype) ||
756       m_strcmp(b1->description, b2->description) ||
757       !parameter_equal(b1->parameter, b2->parameter) ||
758       b1->length != b2->length)
759     return (0);
760   return (1);
761 }
762 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
763   if (h1 && h2) {
764     if (h1->received != h2->received ||
765         h1->date_sent != h2->date_sent ||
766         h1->content->length != h2->content->length ||
767         h1->lines != h2->lines ||
768         h1->zhours != h2->zhours ||
769         h1->zminutes != h2->zminutes ||
770         h1->zoccident != h2->zoccident ||
771         h1->mime != h2->mime ||
772         !mutt_cmp_env (h1->env, h2->env) ||
773         !mutt_cmp_body (h1->content, h2->content))
774       return (0);
775     else
776       return (1);
777   }
778   else {
779     if (h1 == NULL && h2 == NULL)
780       return (1);
781     else
782       return (0);
783   }
784 }
785
786
787 int mutt_extract_token(BUFFER *dest, BUFFER *tok, int flags)
788 {
789     char ch;
790     char qc = 0;                  /* quote char */
791     char *pc;
792
793     /* reset the destination pointer to the beginning of the buffer */
794     dest->dptr = dest->data;
795
796     tok->dptr = vskipspaces(tok->dptr);
797     while ((ch = *tok->dptr)) {
798         if (!qc) {
799             if ((ISSPACE(ch) && !(flags & M_TOKEN_SPACE))
800             || (ch == '#' && !(flags & M_TOKEN_COMMENT))
801             || (ch == '=' && (flags & M_TOKEN_EQUAL))
802             || (ch == ';' && !(flags & M_TOKEN_SEMICOLON))
803             || ((flags & M_TOKEN_PATTERN) && strchr("~=!|", ch)))
804             {
805                 break;
806             }
807         }
808
809         tok->dptr++;
810
811         if (ch == qc) {
812             qc = 0;                     /* end of quote */
813         } else
814         if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE)) {
815             qc = ch;
816         } else
817         if (ch == '\\' && qc != '\'') {
818             if (!*tok->dptr)
819                 return -1;              /* premature end of token */
820
821             switch (ch = *tok->dptr++) {
822               case 'c':
823               case 'C':
824                 if (!*tok->dptr)
825                     return -1;          /* premature end of token */
826                 mutt_buffer_addch(dest,
827                                   (ascii_toupper(*tok->dptr) - 'A' + 1) & 0x7f);
828                 tok->dptr++;
829                 break;
830               case 'r':
831                 mutt_buffer_addch(dest, '\r');
832                 break;
833               case 'n':
834                 mutt_buffer_addch(dest, '\n');
835                 break;
836               case 't':
837                 mutt_buffer_addch(dest, '\t');
838                 break;
839               case 'f':
840                 mutt_buffer_addch(dest, '\f');
841                 break;
842               case 'e':
843                 mutt_buffer_addch(dest, '\033');
844                 break;
845               default:
846                 if (isdigit((unsigned char)ch)
847                 &&  isdigit((unsigned char)*tok->dptr)
848                 &&  isdigit((unsigned char)*(tok->dptr + 1)))
849                 {
850                     mutt_buffer_addch(dest, (ch << 6) + (*tok->dptr << 3) +
851                                             *(tok->dptr + 1) - 3504);
852                     tok->dptr += 2;
853                 } else {
854                     mutt_buffer_addch(dest, ch);
855                 }
856             }
857         } else
858         if (ch == '^' && (flags & M_TOKEN_CONDENSE)) {
859             if (!*tok->dptr)
860                 return -1;              /* premature end of token */
861             ch = *tok->dptr++;
862             if (ch == '^') {
863                 mutt_buffer_addch(dest, ch);
864             } else
865             if (ch == '[') {
866                 mutt_buffer_addch(dest, '\033');
867             } else
868             if (isalpha((unsigned char)ch)) {
869                 mutt_buffer_addch(dest, ascii_toupper(ch) - 'A' + 1);
870             } else {
871                 mutt_buffer_addch(dest, '^');
872                 mutt_buffer_addch(dest, ch);
873             }
874         } else
875         if (ch == '`' && (!qc || qc == '"')) {
876             FILE *fp;
877             pid_t pid;
878             char *cmd, *ptr;
879             ssize_t expnlen;
880             BUFFER expn;
881             int line = 0;
882
883             pc = tok->dptr;
884             do {
885                 if ((pc = strpbrk(pc, "\\`"))) {
886                     /* skip any quoted chars */
887                     if (*pc == '\\')
888                         pc += 2;
889                 }
890             } while (pc && *pc != '`');
891             if (!pc) {
892                 return (-1);
893             }
894
895             cmd = p_dupstr(tok->dptr, pc - tok->dptr);
896             if ((pid = mutt_create_filter(cmd, NULL, &fp, NULL)) < 0) {
897                 p_delete(&cmd);
898                 return -1;
899             }
900             p_delete(&cmd);
901
902             tok->dptr = pc + 1;
903
904             /* read line */
905             p_clear(&expn, 1);
906             expn.data = mutt_read_line(NULL, &expn.dsize, fp, &line);
907             m_fclose(&fp);
908             mutt_wait_filter(pid);
909
910             /* if we got output, make a new string consiting of the shell ouptput
911                plus whatever else was left on the original line */
912             /* BUT: If this is inside a quoted string, directly add output to 
913              * the token */
914             if (expn.data && qc) {
915                 mutt_buffer_addstr(dest, expn.data);
916                 p_delete(&expn.data);
917             } else
918             if (expn.data) {
919                 expnlen = m_strlen(expn.data);
920                 tok->dsize = expnlen + m_strlen(tok->dptr) + 1;
921                 ptr = xmalloc(tok->dsize);
922                 memcpy(ptr, expn.data, expnlen);
923                 strcpy(ptr + expnlen, tok->dptr);      /* __STRCPY_CHECKED__ */
924                 if (tok->destroy)
925                     p_delete(&tok->data);
926                 tok->data = ptr;
927                 tok->dptr = ptr;
928                 tok->destroy = 1;       /* mark that the caller should destroy this data */
929                 ptr = NULL;
930                 p_delete(&expn.data);
931             }
932         } else
933         if (ch == '$' && (!qc || qc == '"')
934         && (*tok->dptr == '{' || isalpha((unsigned char)*tok->dptr)))
935         {
936             char *env = NULL, *var = NULL;
937
938             if (*tok->dptr == '{') {
939                 tok->dptr++;
940                 if ((pc = strchr (tok->dptr, '}'))) {
941                     var = p_dupstr(tok->dptr, pc - tok->dptr);
942                     tok->dptr = pc + 1;
943                 }
944             } else {
945                 for (pc = tok->dptr; isalnum((unsigned char)*pc) || *pc == '_';
946                      pc++);
947                 var = p_dupstr(tok->dptr, pc - tok->dptr);
948                 tok->dptr = pc;
949             }
950             if (var) {
951                 char tmp[STRING];
952                 if ((env = getenv (var))
953                 || (mutt_option_value (var, tmp, sizeof (tmp)) && (env = tmp)))
954                 {
955                     mutt_buffer_addstr (dest, env);
956                 }
957             }
958             p_delete(&var);
959         } else {
960             mutt_buffer_addch(dest, ch);
961         }
962     }
963     mutt_buffer_addch(dest, 0);  /* terminate the string */
964     tok->dptr = vskipspaces(tok->dptr);
965     return 0;
966 }