simplify mutt_expand_path
[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   char buf[_POSIX_PATH_MAX];
44   char tmp[_POSIX_PATH_MAX];
45   char *period;
46   ssize_t sl;
47   struct stat sb;
48
49   m_strcpy(buf, sizeof(buf), m_strisempty(dir) ? NONULL(Tempdir) : dir);
50   mutt_expand_path (buf, sizeof (buf));
51   if (s[0] == '\0') {
52     snprintf (s, l, "%s/muttXXXXXX", buf);
53     mktemp (s);
54   }
55   else {
56     m_strcpy(tmp, sizeof(tmp), s);
57     mutt_sanitize_filename (tmp, 1);
58     snprintf (s, l, "%s/%s", buf, tmp);
59     if (lstat (s, &sb) == -1 && errno == ENOENT)
60       return;
61     if ((period = strrchr (tmp, '.')) != NULL)
62       *period = 0;
63     snprintf (s, l, "%s/%s.XXXXXX", buf, tmp);
64     mktemp (s);
65     if (period != NULL) {
66       *period = '.';
67       sl = m_strlen(s);
68       m_strcpy(s + sl, l - sl, period);
69     }
70   }
71 }
72
73 /* create a send-mode duplicate from a receive-mode body */
74
75 int mutt_copy_body (FILE * fp, BODY ** tgt, BODY * src)
76 {
77   char tmp[_POSIX_PATH_MAX];
78   BODY *b;
79
80   parameter_t *par, **ppar;
81
82   short use_disp;
83
84   if (src->filename) {
85     use_disp = 1;
86     m_strcpy(tmp, sizeof(tmp), src->filename);
87   }
88   else {
89     use_disp = 0;
90     tmp[0] = '\0';
91   }
92
93   mutt_adv_mktemp (NULL, tmp, sizeof (tmp));
94   if (mutt_save_attachment (fp, src, tmp, 0, NULL) == -1)
95     return -1;
96
97   *tgt = body_new();
98   b = *tgt;
99
100   memcpy (b, src, sizeof (BODY));
101   b->parts = NULL;
102   b->next = NULL;
103
104   b->filename = m_strdup(tmp);
105   b->use_disp = use_disp;
106   b->unlink = 1;
107
108   if (mutt_is_text_part (b))
109     b->noconv = 1;
110
111   b->xtype = m_strdup(b->xtype);
112   b->subtype = m_strdup(b->subtype);
113   b->form_name = m_strdup(b->form_name);
114   b->filename = m_strdup(b->filename);
115   b->d_filename = m_strdup(b->d_filename);
116   b->description = m_strdup(b->description);
117
118   /* 
119    * we don't seem to need the HEADER structure currently.
120    * XXX - this may change in the future
121    */
122
123   if (b->hdr)
124     b->hdr = NULL;
125
126   /* copy parameters */
127   for (par = b->parameter, ppar = &b->parameter; par;
128        ppar = &(*ppar)->next, par = par->next) {
129     *ppar = parameter_new();
130     (*ppar)->attribute = m_strdup(par->attribute);
131     (*ppar)->value = m_strdup(par->value);
132   }
133
134   mutt_stamp_attachment (b);
135
136   return 0;
137 }
138
139 /* returns true if the header contained in "s" is in list "t" */
140 int mutt_matches_ignore (const char *s, string_list_t * t)
141 {
142   for (; t; t = t->next) {
143     if (!ascii_strncasecmp (s, t->data, m_strlen(t->data))
144         || *t->data == '*')
145       return 1;
146   }
147   return 0;
148 }
149
150 ssize_t _mutt_expand_path(char *s, ssize_t slen, int rx)
151 {
152     char p[_POSIX_PATH_MAX] = "";
153     char tmp[_POSIX_PATH_MAX];
154     const char *tail = "";
155
156     do {
157         const address_t *alias;
158
159         switch (*s) {
160           case '~':
161             if (s[1] == '/' || s[1] == '\0') {
162                 m_strcpy(p, sizeof(p), Homedir);
163                 tail = s + 1;
164             } else {
165                 struct passwd *pw;
166                 tail = m_strchrnul(s + 1, '/');
167
168                 m_strncpy(tmp, sizeof(tmp), s + 1, tail - s - 1);
169
170                 if ((pw = getpwnam(tmp))) {
171                     m_strcpy(p, sizeof(p), pw->pw_dir);
172                 } else {
173                     /* user not found! */
174                     tail = s;
175                 }
176             }
177             break;
178
179           case '=':
180           case '+':
181             /* if folder = imap[s]://host/: don't append slash */
182             if (imap_is_magic(NONULL(Maildir), NULL) == M_IMAP
183             &&  Maildir[m_strlen(Maildir) - 1] == '/') {
184                 m_strcpy(p, sizeof(p), Maildir);
185             } else {
186                 snprintf(p, sizeof(p), "%s/", NONULL(Maildir));
187             }
188
189             tail = s + 1;
190             break;
191
192             /* elm compatibility, @ expands alias to user name */
193
194           case '@':
195             if ((alias = alias_lookup(s + 1))) {
196                 HEADER h;
197                 header_init(&h);
198                 h.env = envelope_new();
199                 h.env->from = h.env->to = (address_t *)alias;
200                 mutt_default_save(p, sizeof (p), &h);
201                 h.env->from = h.env->to = NULL;
202                 header_wipe(&h);
203
204                 if (*p != '@') {
205                     /* recurse iff the result do not starts with '@' */
206                     m_strcpy(s, slen, p);
207                     continue;
208                 }
209             }
210             break;
211
212           case '>':
213             m_strcpy(p, sizeof(p), Inbox);
214             tail = s + 1;
215             break;
216
217           case '<':
218             m_strcpy(p, sizeof(p), Outbox);
219             tail = s + 1;
220             break;
221
222           case '!':
223             if (s[1] == '!') {
224                 m_strcpy(p, sizeof(p), LastFolder);
225                 tail = s + 2;
226             } else {
227                 m_strcpy(p, sizeof(p), Spoolfile);
228                 tail = s + 1;
229             }
230             break;
231
232           case '-':
233             m_strcpy(p, sizeof(p), NONULL(LastFolder));
234             tail = s + 1;
235             break;
236
237           case '^':
238             m_strcpy(p, sizeof(p), NONULL(CurrentFolder));
239             tail = s + 1;
240             break;
241
242           default:
243             *p = '\0';
244             tail = s;
245         }
246     } while (0);
247
248     if (rx) {
249         char q[_POSIX_PATH_MAX];
250         rx_sanitize_string(q, sizeof(q), p);
251         snprintf(tmp, sizeof(tmp), "%s%s", q, tail);
252     } else {
253         snprintf(tmp, sizeof(tmp), "%s%s", p, tail);
254     }
255
256     return m_strcpy(s, slen, tmp);
257 }
258
259 void mutt_mktemp (char *s)
260 {
261
262   snprintf (s, _POSIX_PATH_MAX, "%s/madmutt-%s-%d-%d-%d-%x%x", NONULL (Tempdir),
263             NONULL (Hostname), (int) getuid (), (int) getpid (), Counter++, 
264             (unsigned int) rand(), (unsigned int) rand());
265   unlink (s);
266 }
267
268 /* collapse the pathname using ~ or = when possible */
269 void mutt_pretty_mailbox (char *s)
270 {
271   char *p = s, *q = s;
272   ssize_t len;
273   url_scheme_t scheme;
274
275   scheme = url_check_scheme (s);
276
277   if (scheme == U_IMAP || scheme == U_IMAPS) {
278     imap_pretty_mailbox (s);
279     return;
280   }
281
282   /* if s is an url, only collapse path component */
283   if (scheme != U_UNKNOWN) {
284     p = strchr (s, ':') + 1;
285     if (!strncmp (p, "//", 2))
286       q = strchr (p + 2, '/');
287     if (!q)
288       q = strchr (p, '\0');
289     p = q;
290   }
291
292   /* first attempt to collapse the pathname */
293   while (*p) {
294     if (*p == '/' && p[1] == '/') {
295       *q++ = '/';
296       p += 2;
297     }
298     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
299       *q++ = '/';
300       p += 3;
301     }
302     else
303       *q++ = *p++;
304   }
305   *q = 0;
306
307   if (m_strncmp(s, Maildir, (len = m_strlen(Maildir))) == 0 &&
308       s[len] == '/') {
309     *s++ = '=';
310     memmove (s, s + len, m_strlen(s + len) + 1);
311   }
312   else if (m_strncmp(s, Homedir, (len = m_strlen(Homedir))) == 0 &&
313            s[len] == '/') {
314     *s++ = '~';
315     memmove (s, s + len - 1, m_strlen(s + len - 1) + 1);
316   }
317 }
318
319 void mutt_pretty_size (char *s, ssize_t len, long n)
320 {
321   if (n == 0)
322     m_strcpy(s, len, "0K");
323   else if (n < 10189)           /* 0.1K - 9.9K */
324     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
325   else if (n < 1023949) {       /* 10K - 999K */
326     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
327     snprintf (s, len, "%ldK", (n + 51) / 1024);
328   }
329   else if (n < 10433332)        /* 1.0M - 9.9M */
330     snprintf (s, len, "%3.1fM", n / 1048576.0);
331   else {                        /* 10M+ */
332
333     /* (10433332 + 52428) / 1048576 = 10 */
334     snprintf (s, len, "%ldM", (n + 52428) / 1048576);
335   }
336 }
337
338 void mutt_expand_file_fmt(char *dest, ssize_t destlen,
339                           const char *fmt, const char *src)
340 {
341     char tmp[LONG_STRING];
342
343     mutt_quote_filename(tmp, sizeof(tmp), src);
344     mutt_expand_fmt(dest, destlen, fmt, tmp);
345 }
346
347 void mutt_expand_fmt(char *dst, ssize_t dlen,
348                      const char *fmt, const char *src)
349 {
350     ssize_t pos = 0;
351     int found = 0;
352
353     while (*fmt && pos < dlen - 1) {
354         if (*fmt == '%') {
355             switch (*++fmt) {
356               case 's':
357                 found = 1;
358                 pos += m_strcpy(dst + pos, dlen - pos, src);
359                 break;
360
361               case '%':
362                 dst[pos++] = *fmt++;
363                 break;
364
365               default:
366                 dst[pos++] = '%';
367                 break;
368             }
369         } else {
370             dst[pos++] = *fmt++;
371         }
372     }
373
374     dst[pos] = '\0';
375     if (!found) {
376         pos += m_strcpy(dst + pos, dlen - pos, " ");
377         pos += m_strcpy(dst + pos, dlen - pos, src);
378     }
379 }
380
381 /* return 0 on success, -1 on abort, 1 on error */
382 int mutt_check_overwrite (const char *attname, const char *path,
383                           char *fname, ssize_t flen, int *append,
384                           char **directory)
385 {
386   int rc = 0;
387   char tmp[_POSIX_PATH_MAX];
388   struct stat st;
389
390   m_strcpy(fname, flen, path);
391   if (access (fname, F_OK) != 0)
392     return 0;
393   if (stat (fname, &st) != 0)
394     return -1;
395   if (S_ISDIR (st.st_mode)) {
396     if (directory) {
397       switch (mutt_multi_choice
398               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
399                _("yna"))) {
400       case 3:                  /* all */
401         m_strreplace(directory, fname);
402         break;
403       case 1:                  /* yes */
404         p_delete(directory);
405         break;
406       case -1:                 /* abort */
407         p_delete(directory);
408         return -1;
409       case 2:                  /* no */
410         p_delete(directory);
411         return 1;
412       }
413     }
414     else
415       if ((rc =
416            mutt_yesorno (_("File is a directory, save under it?"),
417                          M_YES)) != M_YES)
418       return (rc == M_NO) ? 1 : -1;
419
420     if (!attname || !attname[0]) {
421       tmp[0] = 0;
422       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
423                           M_FILE | M_CLEAR) != 0 || !tmp[0])
424         return (-1);
425       mutt_concat_path(fname, flen, path, tmp);
426     }
427     else
428       mutt_concat_path(fname, flen, path, mutt_basename(attname));
429   }
430
431   if (*append == 0 && access (fname, F_OK) == 0) {
432     switch (mutt_multi_choice
433             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
434     {
435     case -1:                   /* abort */
436       return -1;
437     case 3:                    /* cancel */
438       return 1;
439
440     case 2:                    /* append */
441       *append = M_SAVE_APPEND;
442       break;
443     case 1:                    /* overwrite */
444       *append = M_SAVE_OVERWRITE;
445       break;
446     }
447   }
448   return 0;
449 }
450
451 void mutt_save_path(char *d, ssize_t dsize, address_t *a)
452 {
453     if (a && a->mailbox) {
454         m_strcpy(d, dsize, a->mailbox);
455
456         if (!option(OPTSAVEADDRESS)) {
457             char *p = strpbrk(d, "%@");
458             if (p)
459                 *p = '\0';
460         }
461         m_strtolower(d);
462     } else {
463         *d = '\0';
464     }
465 }
466
467 void mutt_safe_path(char *s, ssize_t l, address_t *a)
468 {
469     mutt_save_path(s, l, a);
470
471     while (*s) {
472         if (*s == '/' || ISSPACE(*s) || !isprint((unsigned char)*s))
473             *s = '_';
474         s++;
475     }
476 }
477
478 void mutt_FormatString (char *dest,     /* output buffer */
479                         ssize_t destlen, /* output buffer len */
480                         const char *src,        /* template string */
481                         format_t * callback,    /* callback for processing */
482                         unsigned long data,     /* callback data */
483                         format_flag flags)
484 {                               /* callback flags */
485   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
486   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
487   ssize_t wlen, wid, count, col, len;
488
489   prefix[0] = '\0';
490   destlen--;                    /* save room for the terminal \0 */
491   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
492   col = wlen;
493
494   while (*src && wlen < destlen) {
495     if (*src == '%') {
496       if (*++src == '%') {
497         *wptr++ = '%';
498         wlen++;
499         col++;
500         src++;
501         continue;
502       }
503
504       if (*src == '?') {
505         flags |= M_FORMAT_OPTIONAL;
506         src++;
507       }
508       else {
509         flags &= ~M_FORMAT_OPTIONAL;
510
511         /* eat the format string */
512         cp = prefix;
513         count = 0;
514         while (count < ssizeof (prefix) &&
515                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
516         {
517           *cp++ = *src++;
518           count++;
519         }
520         *cp = 0;
521       }
522
523       if (!*src)
524         break;                  /* bad format */
525
526       ch = *src++;              /* save the character to switch on */
527
528       if (flags & M_FORMAT_OPTIONAL) {
529         if (*src != '?')
530           break;                /* bad format */
531         src++;
532
533         /* eat the `if' part of the string */
534         cp = ifstring;
535         count = 0;
536         while (count < ssizeof (ifstring) && *src && *src != '?'
537                && *src != '&') {
538           *cp++ = *src++;
539           count++;
540         }
541         *cp = 0;
542
543         /* eat the `else' part of the string (optional) */
544         if (*src == '&')
545           src++;                /* skip the & */
546         cp = elsestring;
547         count = 0;
548         while (count < ssizeof (elsestring) && *src && *src != '?') {
549           *cp++ = *src++;
550           count++;
551         }
552         *cp = 0;
553
554         if (!*src)
555           break;                /* bad format */
556
557         src++;                  /* move past the trailing `?' */
558       }
559
560       /* handle generic cases first */
561       if (ch == '>') {
562         /* right justify to EOL */
563         ch = *src++;            /* pad char */
564         /* calculate space left on line.  if we've already written more data
565            than will fit on the line, ignore the rest of the line */
566         if (DrawFullLine || option (OPTSTATUSONTOP))
567           count = (COLS < destlen ? COLS : destlen);
568         else
569           count = ((COLS - SW) < destlen ? (COLS - SW) : destlen);
570         if (count > col) {
571           count -= col;         /* how many columns left on this line */
572           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
573           wid = m_strlen(buf);
574           if (count > wid) {
575             count -= wid;       /* how many chars to pad */
576             memset (wptr, ch, count);
577             wptr += count;
578             col += count;
579           }
580           if (wid + wlen > destlen)
581             len = destlen - wlen;
582           else
583             len = wid;
584           memcpy (wptr, buf, len);
585           wptr += len;
586           wlen += len;
587           col += mutt_strwidth (buf);
588         }
589         break;                  /* skip rest of input */
590       }
591       else if (ch == '|') {
592         /* pad to EOL */
593         ch = *src++;
594         if (destlen > COLS)
595           destlen = COLS;
596         if (destlen > wlen) {
597           count = destlen - wlen;
598           memset (wptr, ch, count);
599           wptr += count;
600         }
601         break;                  /* skip rest of input */
602       }
603       else {
604         short lower = 0;
605         short nodots = 0;
606
607         while (ch == '_' || ch == ':') {
608           if (ch == '_')
609             lower = 1;
610           else if (ch == ':')
611             nodots = 1;
612
613           ch = *src++;
614         }
615
616         /* use callback function to handle this case */
617         src =
618           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
619                     data, flags);
620
621         if (lower)
622           m_strtolower(buf);
623         if (nodots) {
624           char *p = buf;
625
626           for (; *p; p++)
627             if (*p == '.')
628               *p = '_';
629         }
630
631         if ((len = m_strlen(buf)) + wlen > destlen)
632           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
633
634         memcpy (wptr, buf, len);
635         wptr += len;
636         wlen += len;
637         col += mutt_strwidth (buf);
638       }
639     }
640     else if (*src == '\\') {
641       if (!*++src)
642         break;
643       switch (*src) {
644       case 'n':
645         *wptr = '\n';
646         break;
647       case 't':
648         *wptr = '\t';
649         break;
650       case 'r':
651         *wptr = '\r';
652         break;
653       case 'f':
654         *wptr = '\f';
655         break;
656       case 'v':
657         *wptr = '\v';
658         break;
659       default:
660         *wptr = *src;
661         break;
662       }
663       src++;
664       wptr++;
665       wlen++;
666       col++;
667     }
668     else {
669       unsigned int bar = strcspn(src, "%\\");
670       char *bar2 = p_dupstr(src, bar);
671
672       while (bar--) {
673         *wptr++ = *src++;
674         wlen++;
675       }
676       col += mutt_strwidth (bar2);
677       p_delete(&bar2);
678     }
679   }
680   *wptr = 0;
681
682 #if 0
683   if (flags & M_FORMAT_MAKEPRINT) {
684     /* Make sure that the string is printable by changing all non-printable
685        chars to dots, or spaces for non-printable whitespace */
686     for (cp = dest; *cp; cp++)
687       if (!isprint(*cp) && !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
688         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
689   }
690 #endif
691 }
692
693 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
694 int mutt_save_confirm (const char *s, struct stat *st)
695 {
696   char tmp[_POSIX_PATH_MAX];
697   int ret = 0;
698   int rc;
699   int magic = 0;
700
701   magic = mx_get_magic (s);
702
703   if (magic == M_POP) {
704     mutt_error _("Can't save message to POP mailbox.");
705
706     return 1;
707   }
708
709 #ifdef USE_NNTP
710   if (magic == M_NNTP) {
711     mutt_error _("Can't save message to newsserver.");
712
713     return 0;
714   }
715 #endif
716
717   if (magic > 0 && !mx_access (s, W_OK)) {
718     if (option (OPTCONFIRMAPPEND) &&
719         (!TrashPath || (m_strcmp(s, TrashPath) != 0))) {
720       /* if we're appending to the trash, there's no point in asking */
721       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
722       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
723         ret = 1;
724       else if (rc == -1)
725         ret = -1;
726     }
727   }
728
729   if (stat (s, st) != -1) {
730     if (magic == -1) {
731       mutt_error (_("%s is not a mailbox!"), s);
732       return 1;
733     }
734   }
735   else {
736     if (magic != M_IMAP)
737     {
738       st->st_mtime = 0;
739       st->st_atime = 0;
740
741       if (errno == ENOENT) {
742         if (option (OPTCONFIRMCREATE)) {
743           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
744           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
745             ret = 1;
746           else if (rc == -1)
747             ret = -1;
748         }
749       }
750       else {
751         mutt_perror (s);
752         return 1;
753       }
754     }
755   }
756
757   CLEARLINE (LINES - 1);
758   return (ret);
759 }
760
761 void mutt_sleep (short s)
762 {
763     sleep(MAX(s, SleepTime));
764 }
765
766 /* Decrease a file's modification time by 1 second */
767 time_t mutt_decrease_mtime (const char *f, struct stat *st)
768 {
769   struct utimbuf utim;
770   struct stat _st;
771   time_t mtime;
772
773   if (!st) {
774     if (stat (f, &_st) == -1)
775       return -1;
776     st = &_st;
777   }
778
779   if ((mtime = st->st_mtime) == time (NULL)) {
780     mtime -= 1;
781     utim.actime = mtime;
782     utim.modtime = mtime;
783     utime (f, &utim);
784   }
785
786   return mtime;
787 }
788
789 const char *mutt_make_version (int full)
790 {
791   static char vstring[STRING];
792
793   if (full)
794     snprintf (vstring, sizeof (vstring),
795               "Madmutt/%s-r%s (based on Mutt 1.5.11)",
796               MUTT_VERSION, MUTT_REVISION);
797   else
798     snprintf (vstring, sizeof (vstring), "Madmutt/%s-%s",
799               MUTT_VERSION, MUTT_REVISION);
800   return vstring;
801 }
802
803 void mutt_free_spam_list (SPAM_LIST ** list)
804 {
805   SPAM_LIST *p;
806
807   if (!list)
808     return;
809   while (*list) {
810     p = *list;
811     *list = (*list)->next;
812     rx_delete(&p->rx);
813     p_delete(&p->template);
814     p_delete(&p);
815   }
816 }
817
818 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
819 {
820   static regmatch_t *pmatch = NULL;
821   static int nmatch = 0;
822   int i, n, tlen;
823   char *p;
824
825   if (!s)
826     return 0;
827
828   tlen = 0;
829
830   for (; l; l = l->next) {
831     /* If this pattern needs more matches, expand pmatch. */
832     if (l->nmatch > nmatch) {
833       p_realloc(&pmatch, l->nmatch);
834       nmatch = l->nmatch;
835     }
836
837     /* Does this pattern match? */
838     if (regexec(l->rx->rx, s, l->nmatch, (regmatch_t *)pmatch, (int) 0) == 0)
839     {
840       /* Copy template into text, with substitutions. */
841       for (p = l->template; *p;) {
842         if (*p == '%') {
843           n = atoi (++p);       /* find pmatch index */
844           while (isdigit ((unsigned char) *p))
845             ++p;                /* skip subst token */
846           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
847             text[tlen++] = s[i];
848         }
849         else {
850           text[tlen++] = *p++;
851         }
852       }
853       text[tlen] = '\0';
854       return 1;
855     }
856   }
857
858   return 0;
859 }
860
861 /* return 1 if address lists are strictly identical */
862 static int mutt_cmp_addr (const address_t * a, const address_t * b)
863 {
864   while (a && b) {
865     if (m_strcmp(a->mailbox, b->mailbox) ||
866         m_strcmp(a->personal, b->personal))
867       return (0);
868
869     a = a->next;
870     b = b->next;
871   }
872   if (a || b)
873     return (0);
874
875   return (1);
876 }
877
878 static int mutt_cmp_list (const string_list_t * a, const string_list_t * b)
879 {
880   while (a && b) {
881     if (m_strcmp(a->data, b->data))
882       return (0);
883
884     a = a->next;
885     b = b->next;
886   }
887   if (a || b)
888     return (0);
889
890   return (1);
891 }
892
893 static int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
894 {
895   if (e1 && e2) {
896     if (m_strcmp(e1->message_id, e2->message_id) ||
897         m_strcmp(e1->subject, e2->subject) ||
898         !mutt_cmp_list (e1->references, e2->references) ||
899         !mutt_cmp_addr (e1->from, e2->from) ||
900         !mutt_cmp_addr (e1->sender, e2->sender) ||
901         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
902         !mutt_cmp_addr (e1->to, e2->to) ||
903         !mutt_cmp_addr (e1->cc, e2->cc) ||
904         !mutt_cmp_addr (e1->return_path, e2->return_path))
905       return (0);
906     else
907       return (1);
908   }
909   else {
910     if (e1 == NULL && e2 == NULL)
911       return (1);
912     else
913       return (0);
914   }
915 }
916
917 static int mutt_cmp_body (const BODY * b1, const BODY * b2)
918 {
919   if (b1->type != b2->type ||
920       b1->encoding != b2->encoding ||
921       m_strcmp(b1->subtype, b2->subtype) ||
922       m_strcmp(b1->description, b2->description) ||
923       !parameter_equal(b1->parameter, b2->parameter) ||
924       b1->length != b2->length)
925     return (0);
926   return (1);
927 }
928 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
929   if (h1 && h2) {
930     if (h1->received != h2->received ||
931         h1->date_sent != h2->date_sent ||
932         h1->content->length != h2->content->length ||
933         h1->lines != h2->lines ||
934         h1->zhours != h2->zhours ||
935         h1->zminutes != h2->zminutes ||
936         h1->zoccident != h2->zoccident ||
937         h1->mime != h2->mime ||
938         !mutt_cmp_env (h1->env, h2->env) ||
939         !mutt_cmp_body (h1->content, h2->content))
940       return (0);
941     else
942       return (1);
943   }
944   else {
945     if (h1 == NULL && h2 == NULL)
946       return (1);
947     else
948       return (0);
949   }
950 }
951
952
953 int mutt_extract_token(BUFFER *dest, BUFFER *tok, int flags)
954 {
955     char ch;
956     char qc = 0;                  /* quote char */
957     char *pc;
958
959     /* reset the destination pointer to the beginning of the buffer */
960     dest->dptr = dest->data;
961
962     tok->dptr = vskipspaces(tok->dptr);
963     while ((ch = *tok->dptr)) {
964         if (!qc) {
965             if ((ISSPACE(ch) && !(flags & M_TOKEN_SPACE))
966             || (ch == '#' && !(flags & M_TOKEN_COMMENT))
967             || (ch == '=' && (flags & M_TOKEN_EQUAL))
968             || (ch == ';' && !(flags & M_TOKEN_SEMICOLON))
969             || ((flags & M_TOKEN_PATTERN) && strchr("~=!|", ch)))
970             {
971                 break;
972             }
973         }
974
975         tok->dptr++;
976
977         if (ch == qc) {
978             qc = 0;                     /* end of quote */
979         } else
980         if (!qc && (ch == '\'' || ch == '"') && !(flags & M_TOKEN_QUOTE)) {
981             qc = ch;
982         } else
983         if (ch == '\\' && qc != '\'') {
984             if (!*tok->dptr)
985                 return -1;              /* premature end of token */
986
987             switch (ch = *tok->dptr++) {
988               case 'c':
989               case 'C':
990                 if (!*tok->dptr)
991                     return -1;          /* premature end of token */
992                 mutt_buffer_addch(dest,
993                                   (ascii_toupper(*tok->dptr) - 'A' + 1) & 0x7f);
994                 tok->dptr++;
995                 break;
996               case 'r':
997                 mutt_buffer_addch(dest, '\r');
998                 break;
999               case 'n':
1000                 mutt_buffer_addch(dest, '\n');
1001                 break;
1002               case 't':
1003                 mutt_buffer_addch(dest, '\t');
1004                 break;
1005               case 'f':
1006                 mutt_buffer_addch(dest, '\f');
1007                 break;
1008               case 'e':
1009                 mutt_buffer_addch(dest, '\033');
1010                 break;
1011               default:
1012                 if (isdigit((unsigned char)ch)
1013                 &&  isdigit((unsigned char)*tok->dptr)
1014                 &&  isdigit((unsigned char)*(tok->dptr + 1)))
1015                 {
1016                     mutt_buffer_addch(dest, (ch << 6) + (*tok->dptr << 3) +
1017                                             *(tok->dptr + 1) - 3504);
1018                     tok->dptr += 2;
1019                 } else {
1020                     mutt_buffer_addch(dest, ch);
1021                 }
1022             }
1023         } else
1024         if (ch == '^' && (flags & M_TOKEN_CONDENSE)) {
1025             if (!*tok->dptr)
1026                 return -1;              /* premature end of token */
1027             ch = *tok->dptr++;
1028             if (ch == '^') {
1029                 mutt_buffer_addch(dest, ch);
1030             } else
1031             if (ch == '[') {
1032                 mutt_buffer_addch(dest, '\033');
1033             } else
1034             if (isalpha((unsigned char)ch)) {
1035                 mutt_buffer_addch(dest, ascii_toupper(ch) - 'A' + 1);
1036             } else {
1037                 mutt_buffer_addch(dest, '^');
1038                 mutt_buffer_addch(dest, ch);
1039             }
1040         } else
1041         if (ch == '`' && (!qc || qc == '"')) {
1042             FILE *fp;
1043             pid_t pid;
1044             char *cmd, *ptr;
1045             ssize_t expnlen;
1046             BUFFER expn;
1047             int line = 0;
1048
1049             pc = tok->dptr;
1050             do {
1051                 if ((pc = strpbrk(pc, "\\`"))) {
1052                     /* skip any quoted chars */
1053                     if (*pc == '\\')
1054                         pc += 2;
1055                 }
1056             } while (pc && *pc != '`');
1057             if (!pc) {
1058                 return (-1);
1059             }
1060
1061             cmd = p_dupstr(tok->dptr, pc - tok->dptr);
1062             if ((pid = mutt_create_filter(cmd, NULL, &fp, NULL)) < 0) {
1063                 p_delete(&cmd);
1064                 return -1;
1065             }
1066             p_delete(&cmd);
1067
1068             tok->dptr = pc + 1;
1069
1070             /* read line */
1071             p_clear(&expn, 1);
1072             expn.data = mutt_read_line(NULL, &expn.dsize, fp, &line);
1073             fclose(fp);
1074             mutt_wait_filter(pid);
1075
1076             /* if we got output, make a new string consiting of the shell ouptput
1077                plus whatever else was left on the original line */
1078             /* BUT: If this is inside a quoted string, directly add output to 
1079              * the token */
1080             if (expn.data && qc) {
1081                 mutt_buffer_addstr(dest, expn.data);
1082                 p_delete(&expn.data);
1083             } else
1084             if (expn.data) {
1085                 expnlen = m_strlen(expn.data);
1086                 tok->dsize = expnlen + m_strlen(tok->dptr) + 1;
1087                 ptr = xmalloc(tok->dsize);
1088                 memcpy(ptr, expn.data, expnlen);
1089                 strcpy(ptr + expnlen, tok->dptr);      /* __STRCPY_CHECKED__ */
1090                 if (tok->destroy)
1091                     p_delete(&tok->data);
1092                 tok->data = ptr;
1093                 tok->dptr = ptr;
1094                 tok->destroy = 1;       /* mark that the caller should destroy this data */
1095                 ptr = NULL;
1096                 p_delete(&expn.data);
1097             }
1098         } else
1099         if (ch == '$' && (!qc || qc == '"')
1100         && (*tok->dptr == '{' || isalpha((unsigned char)*tok->dptr)))
1101         {
1102             char *env = NULL, *var = NULL;
1103
1104             if (*tok->dptr == '{') {
1105                 tok->dptr++;
1106                 if ((pc = strchr (tok->dptr, '}'))) {
1107                     var = p_dupstr(tok->dptr, pc - tok->dptr);
1108                     tok->dptr = pc + 1;
1109                 }
1110             } else {
1111                 for (pc = tok->dptr; isalnum((unsigned char)*pc) || *pc == '_';
1112                      pc++);
1113                 var = p_dupstr(tok->dptr, pc - tok->dptr);
1114                 tok->dptr = pc;
1115             }
1116             if (var) {
1117                 char tmp[STRING];
1118                 if ((env = getenv (var))
1119                 || (mutt_option_value (var, tmp, sizeof (tmp)) && (env = tmp)))
1120                 {
1121                     mutt_buffer_addstr (dest, env);
1122                 }
1123             }
1124             p_delete(&var);
1125         } else {
1126             mutt_buffer_addch(dest, ch);
1127         }
1128     }
1129     mutt_buffer_addch(dest, 0);  /* terminate the string */
1130     tok->dptr = vskipspaces(tok->dptr);
1131     return 0;
1132 }