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