Rocco Rutte:
[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 "mutt.h"
16 #include "ascii.h"
17 #include "buffer.h"
18 #include "enter.h"
19 #include "mutt_curses.h"
20 #include "mime.h"
21 #include "mx.h"
22 #include "url.h"
23 #include "attach.h"
24
25 #include "reldate.h"
26 #include "version.h"
27
28 #ifdef USE_IMAP
29 #include "imap.h"
30 #include "imap/mx_imap.h"
31 #endif
32
33 #include "mutt_crypt.h"
34
35 #include "lib/mem.h"
36 #include "lib/intl.h"
37 #include "lib/str.h"
38 #include "lib/debug.h"
39
40 #include <string.h>
41 #include <ctype.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <sys/wait.h>
45 #include <errno.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #include <time.h>
49 #include <sys/types.h>
50 #include <utime.h>
51
52 BODY *mutt_new_body (void)
53 {
54   BODY *p = (BODY *) mem_calloc (1, sizeof (BODY));
55
56   p->disposition = DISPATTACH;
57   p->use_disp = 1;
58   return (p);
59 }
60
61
62 /* Modified by blong to accept a "suggestion" for file name.  If
63  * that file exists, then construct one with unique name but 
64  * keep any extension.  This might fail, I guess.
65  * Renamed to mutt_adv_mktemp so I only have to change where it's
66  * called, and not all possible cases.
67  */
68 void mutt_adv_mktemp (const char* dir, char *s, size_t l)
69 {
70   char buf[_POSIX_PATH_MAX];
71   char tmp[_POSIX_PATH_MAX];
72   char *period;
73   size_t sl;
74   struct stat sb;
75
76   strfcpy (buf, dir && *dir ? dir : NONULL (Tempdir), sizeof (buf));
77   mutt_expand_path (buf, sizeof (buf));
78   if (s[0] == '\0') {
79     snprintf (s, l, "%s/muttXXXXXX", buf);
80     mktemp (s);
81   }
82   else {
83     strfcpy (tmp, s, sizeof (tmp));
84     mutt_sanitize_filename (tmp, 1);
85     snprintf (s, l, "%s/%s", buf, tmp);
86     if (lstat (s, &sb) == -1 && errno == ENOENT)
87       return;
88     if ((period = strrchr (tmp, '.')) != NULL)
89       *period = 0;
90     snprintf (s, l, "%s/%s.XXXXXX", buf, tmp);
91     mktemp (s);
92     if (period != NULL) {
93       *period = '.';
94       sl = str_len (s);
95       strfcpy (s + sl, period, l - sl);
96     }
97   }
98 }
99
100 /* create a send-mode duplicate from a receive-mode body */
101
102 int mutt_copy_body (FILE * fp, BODY ** tgt, BODY * src)
103 {
104   char tmp[_POSIX_PATH_MAX];
105   BODY *b;
106
107   PARAMETER *par, **ppar;
108
109   short use_disp;
110
111   if (src->filename) {
112     use_disp = 1;
113     strfcpy (tmp, src->filename, sizeof (tmp));
114   }
115   else {
116     use_disp = 0;
117     tmp[0] = '\0';
118   }
119
120   mutt_adv_mktemp (NULL, tmp, sizeof (tmp));
121   if (mutt_save_attachment (fp, src, tmp, 0, NULL) == -1)
122     return -1;
123
124   *tgt = mutt_new_body ();
125   b = *tgt;
126
127   memcpy (b, src, sizeof (BODY));
128   b->parts = NULL;
129   b->next = NULL;
130
131   b->filename = str_dup (tmp);
132   b->use_disp = use_disp;
133   b->unlink = 1;
134
135   if (mutt_is_text_part (b))
136     b->noconv = 1;
137
138   b->xtype = str_dup (b->xtype);
139   b->subtype = str_dup (b->subtype);
140   b->form_name = str_dup (b->form_name);
141   b->filename = str_dup (b->filename);
142   b->d_filename = str_dup (b->d_filename);
143   b->description = str_dup (b->description);
144
145   /* 
146    * we don't seem to need the HEADER structure currently.
147    * XXX - this may change in the future
148    */
149
150   if (b->hdr)
151     b->hdr = NULL;
152
153   /* copy parameters */
154   for (par = b->parameter, ppar = &b->parameter; par;
155        ppar = &(*ppar)->next, par = par->next) {
156     *ppar = mutt_new_parameter ();
157     (*ppar)->attribute = str_dup (par->attribute);
158     (*ppar)->value = str_dup (par->value);
159   }
160
161   mutt_stamp_attachment (b);
162
163   return 0;
164 }
165
166
167
168 void mutt_free_body (BODY ** p)
169 {
170   BODY *a = *p, *b;
171
172   while (a) {
173     b = a;
174     a = a->next;
175
176     if (b->parameter)
177       mutt_free_parameter (&b->parameter);
178     if (b->unlink && b->filename) {
179       debug_print (1, ("unlinking %s.\n", b->filename));
180       unlink (b->filename);
181     }
182     else if (b->filename)
183       debug_print (1, ("not unlinking %s.\n", b->filename));
184
185     mem_free (&b->filename);
186     mem_free (&b->content);
187     mem_free (&b->xtype);
188     mem_free (&b->subtype);
189     mem_free (&b->description);
190     mem_free (&b->form_name);
191
192     if (b->hdr) {
193       /* Don't free twice (b->hdr->content = b->parts) */
194       b->hdr->content = NULL;
195       mutt_free_header (&b->hdr);
196     }
197
198     if (b->parts)
199       mutt_free_body (&b->parts);
200
201     mem_free (&b);
202   }
203
204   *p = 0;
205 }
206
207 void mutt_free_parameter (PARAMETER ** p)
208 {
209   PARAMETER *t = *p;
210   PARAMETER *o;
211
212   while (t) {
213     mem_free (&t->attribute);
214     mem_free (&t->value);
215     o = t;
216     t = t->next;
217     mem_free (&o);
218   }
219   *p = 0;
220 }
221
222 HEADER *mutt_dup_header (HEADER * h)
223 {
224   HEADER *hnew;
225
226   hnew = mutt_new_header ();
227   memcpy (hnew, h, sizeof (HEADER));
228   return hnew;
229 }
230
231 void mutt_free_header (HEADER ** h)
232 {
233   if (!h || !*h)
234     return;
235   mutt_free_envelope (&(*h)->env);
236   mutt_free_body (&(*h)->content);
237   mem_free (&(*h)->maildir_flags);
238   mem_free (&(*h)->tree);
239   mem_free (&(*h)->path);
240 #ifdef MIXMASTER
241   mutt_free_list (&(*h)->chain);
242 #endif
243 #if defined USE_POP || defined USE_IMAP || defined USE_NNTP
244   mem_free (&(*h)->data);
245 #endif
246   mem_free (h);
247 }
248
249 /* returns true if the header contained in "s" is in list "t" */
250 int mutt_matches_ignore (const char *s, LIST * t)
251 {
252   for (; t; t = t->next) {
253     if (!ascii_strncasecmp (s, t->data, str_len (t->data))
254         || *t->data == '*')
255       return 1;
256   }
257   return 0;
258 }
259
260 /* prepend the path part of *path to *link */
261 void mutt_expand_link (char *newpath, const char *path, const char *link)
262 {
263   const char *lb = NULL;
264   size_t len;
265
266   /* link is full path */
267   if (*link == '/') {
268     strfcpy (newpath, link, _POSIX_PATH_MAX);
269     return;
270   }
271
272   if ((lb = strrchr (path, '/')) == NULL) {
273     /* no path in link */
274     strfcpy (newpath, link, _POSIX_PATH_MAX);
275     return;
276   }
277
278   len = lb - path + 1;
279   memcpy (newpath, path, len);
280   strfcpy (newpath + len, link, _POSIX_PATH_MAX - len);
281 }
282
283 char *mutt_expand_path (char *s, size_t slen)
284 {
285   return _mutt_expand_path (s, slen, 0);
286 }
287
288 char *_mutt_expand_path (char *s, size_t slen, int rx)
289 {
290   char p[_POSIX_PATH_MAX] = "";
291   char q[_POSIX_PATH_MAX] = "";
292   char tmp[_POSIX_PATH_MAX];
293   char *t;
294
295   char *tail = "";
296
297   int recurse = 0;
298
299   do {
300     recurse = 0;
301
302     switch (*s) {
303     case '~':
304       {
305         if (*(s + 1) == '/' || *(s + 1) == 0) {
306           strfcpy (p, NONULL (Homedir), sizeof (p));
307           tail = s + 1;
308         }
309         else {
310           struct passwd *pw;
311
312           if ((t = strchr (s + 1, '/')))
313             *t = 0;
314
315           if ((pw = getpwnam (s + 1))) {
316             strfcpy (p, pw->pw_dir, sizeof (p));
317             if (t) {
318               *t = '/';
319               tail = t;
320             }
321             else
322               tail = "";
323           }
324           else {
325             /* user not found! */
326             if (t)
327               *t = '/';
328             *p = '\0';
329             tail = s;
330           }
331         }
332       }
333       break;
334
335     case '=':
336     case '+':
337       {
338 #ifdef USE_IMAP
339         /* if folder = imap[s]://host/: don't append slash */
340         if (imap_is_magic (NONULL (Maildir), NULL) == M_IMAP && 
341             Maildir[str_len (Maildir) - 1] == '/')
342           strfcpy (p, NONULL (Maildir), sizeof (p));
343         else
344 #endif
345           snprintf (p, sizeof (p), "%s/", NONULL (Maildir));
346
347         tail = s + 1;
348       }
349       break;
350
351       /* elm compatibility, @ expands alias to user name */
352
353     case '@':
354       {
355         HEADER *h;
356         ADDRESS *alias;
357
358         if ((alias = mutt_lookup_alias (s + 1))) {
359           h = mutt_new_header ();
360           h->env = mutt_new_envelope ();
361           h->env->from = h->env->to = alias;
362           mutt_default_save (p, sizeof (p), h);
363           h->env->from = h->env->to = NULL;
364           mutt_free_header (&h);
365           /* Avoid infinite recursion if the resulting folder starts with '@' */
366           if (*p != '@')
367             recurse = 1;
368
369           tail = "";
370         }
371       }
372       break;
373
374     case '>':
375       {
376         strfcpy (p, NONULL (Inbox), sizeof (p));
377         tail = s + 1;
378       }
379       break;
380
381     case '<':
382       {
383         strfcpy (p, NONULL (Outbox), sizeof (p));
384         tail = s + 1;
385       }
386       break;
387
388     case '!':
389       {
390         if (*(s + 1) == '!') {
391           strfcpy (p, NONULL (LastFolder), sizeof (p));
392           tail = s + 2;
393         }
394         else {
395           strfcpy (p, NONULL (Spoolfile), sizeof (p));
396           tail = s + 1;
397         }
398       }
399       break;
400
401     case '-':
402       {
403         strfcpy (p, NONULL (LastFolder), sizeof (p));
404         tail = s + 1;
405       }
406       break;
407
408     case '^':
409       {
410         strfcpy (p, NONULL (CurrentFolder), sizeof (p));
411         tail = s + 1;
412       }
413       break;
414
415     default:
416       {
417         *p = '\0';
418         tail = s;
419       }
420     }
421
422     if (rx && *p && !recurse) {
423       mutt_rx_sanitize_string (q, sizeof (q), p);
424       snprintf (tmp, sizeof (tmp), "%s%s", q, tail);
425     }
426     else
427       snprintf (tmp, sizeof (tmp), "%s%s", p, tail);
428
429     strfcpy (s, tmp, slen);
430   }
431   while (recurse);
432
433   return (s);
434 }
435
436 /* Extract the real name from /etc/passwd's GECOS field.
437  * When set, honor the regular expression in GecosMask,
438  * otherwise assume that the GECOS field is a 
439  * comma-separated list.
440  * Replace "&" by a capitalized version of the user's login
441  * name.
442  */
443
444 char *mutt_gecos_name (char *dest, size_t destlen, struct passwd *pw)
445 {
446   regmatch_t pat_match[1];
447   size_t pwnl;
448   int idx;
449   char *p;
450
451   if (!pw || !pw->pw_gecos)
452     return NULL;
453
454   memset (dest, 0, destlen);
455
456   if (GecosMask.rx) {
457     if (regexec (GecosMask.rx, pw->pw_gecos, 1, pat_match, 0) == 0)
458       strfcpy (dest, pw->pw_gecos + pat_match[0].rm_so,
459                MIN (pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen));
460   }
461   else if ((p = strchr (pw->pw_gecos, ',')))
462     strfcpy (dest, pw->pw_gecos, MIN (destlen, p - pw->pw_gecos + 1));
463   else
464     strfcpy (dest, pw->pw_gecos, destlen);
465
466   pwnl = str_len (pw->pw_name);
467
468   for (idx = 0; dest[idx]; idx++) {
469     if (dest[idx] == '&') {
470       memmove (&dest[idx + pwnl], &dest[idx + 1],
471                MAX (destlen - idx - pwnl - 1, 0));
472       memcpy (&dest[idx], pw->pw_name, MIN (destlen - idx - 1, pwnl));
473       dest[idx] = toupper ((unsigned char) dest[idx]);
474     }
475   }
476
477   return dest;
478 }
479
480
481 char *mutt_get_parameter (const char *s, PARAMETER * p)
482 {
483   for (; p; p = p->next)
484     if (ascii_strcasecmp (s, p->attribute) == 0)
485       return (p->value);
486
487   return NULL;
488 }
489
490 void mutt_set_parameter (const char *attribute, const char *value,
491                          PARAMETER ** p)
492 {
493   PARAMETER *q;
494
495   if (!value) {
496     mutt_delete_parameter (attribute, p);
497     return;
498   }
499
500   for (q = *p; q; q = q->next) {
501     if (ascii_strcasecmp (attribute, q->attribute) == 0) {
502       str_replace (&q->value, value);
503       return;
504     }
505   }
506
507   q = mutt_new_parameter ();
508   q->attribute = str_dup (attribute);
509   q->value = str_dup (value);
510   q->next = *p;
511   *p = q;
512 }
513
514 void mutt_delete_parameter (const char *attribute, PARAMETER ** p)
515 {
516   PARAMETER *q;
517
518   for (q = *p; q; p = &q->next, q = q->next) {
519     if (ascii_strcasecmp (attribute, q->attribute) == 0) {
520       *p = q->next;
521       q->next = NULL;
522       mutt_free_parameter (&q);
523       return;
524     }
525   }
526 }
527
528 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
529 int mutt_needs_mailcap (BODY * m)
530 {
531   switch (m->type) {
532   case TYPETEXT:
533
534     if (!ascii_strcasecmp ("plain", m->subtype) ||
535         !ascii_strcasecmp ("rfc822-headers", m->subtype) ||
536         !ascii_strcasecmp ("enriched", m->subtype))
537       return 0;
538     break;
539
540   case TYPEAPPLICATION:
541     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (m))
542       return 0;
543     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (m))
544       return 0;
545     break;
546
547   case TYPEMULTIPART:
548   case TYPEMESSAGE:
549     return 0;
550   }
551
552   return 1;
553 }
554
555 int mutt_is_text_part (BODY * b)
556 {
557   int t = b->type;
558   char *s = b->subtype;
559
560   if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
561     return 0;
562
563   if (t == TYPETEXT)
564     return 1;
565
566   if (t == TYPEMESSAGE) {
567     if (!ascii_strcasecmp ("delivery-status", s))
568       return 1;
569   }
570
571   if ((WithCrypto & APPLICATION_PGP) && t == TYPEAPPLICATION) {
572     if (!ascii_strcasecmp ("pgp-keys", s))
573       return 1;
574   }
575
576   return 0;
577 }
578
579 void mutt_free_envelope (ENVELOPE ** p)
580 {
581   if (!*p)
582     return;
583   rfc822_free_address (&(*p)->return_path);
584   rfc822_free_address (&(*p)->from);
585   rfc822_free_address (&(*p)->to);
586   rfc822_free_address (&(*p)->cc);
587   rfc822_free_address (&(*p)->bcc);
588   rfc822_free_address (&(*p)->sender);
589   rfc822_free_address (&(*p)->reply_to);
590   rfc822_free_address (&(*p)->mail_followup_to);
591
592   mem_free (&(*p)->list_post);
593   mem_free (&(*p)->subject);
594   /* real_subj is just an offset to subject and shouldn't be freed */
595   mem_free (&(*p)->message_id);
596   mem_free (&(*p)->supersedes);
597   mem_free (&(*p)->date);
598   mem_free (&(*p)->x_label);
599   mem_free (&(*p)->organization);
600 #ifdef USE_NNTP
601   mem_free (&(*p)->newsgroups);
602   mem_free (&(*p)->xref);
603   mem_free (&(*p)->followup_to);
604   mem_free (&(*p)->x_comment_to);
605 #endif
606
607   mutt_buffer_free (&(*p)->spam);
608   mutt_free_list (&(*p)->references);
609   mutt_free_list (&(*p)->in_reply_to);
610   mutt_free_list (&(*p)->userhdrs);
611   mem_free (p);
612 }
613
614 /* move all the headers from extra not present in base into base */
615 void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
616 {
617   /* copies each existing element if necessary, and sets the element
618   * to NULL in the source so that mutt_free_envelope doesn't leave us
619   * with dangling pointers. */
620 #define MOVE_ELEM(h) if (!base->h) { base->h = (*extra)->h; (*extra)->h = NULL; }
621   MOVE_ELEM(return_path);
622   MOVE_ELEM(from);
623   MOVE_ELEM(to);
624   MOVE_ELEM(cc);
625   MOVE_ELEM(bcc);
626   MOVE_ELEM(sender);
627   MOVE_ELEM(reply_to);
628   MOVE_ELEM(mail_followup_to);
629   MOVE_ELEM(list_post);
630   MOVE_ELEM(message_id);
631   MOVE_ELEM(supersedes);
632   MOVE_ELEM(date);
633   MOVE_ELEM(x_label);
634   if (!base->refs_changed) {
635     MOVE_ELEM(references);
636   }
637   if (!base->irt_changed) {
638     MOVE_ELEM(in_reply_to);
639   }
640   /* real_subj is subordinate to subject */
641   if (!base->subject) {
642     base->subject = (*extra)->subject;
643     base->real_subj = (*extra)->real_subj;
644     (*extra)->subject = NULL;
645     (*extra)->real_subj = NULL;
646   }
647   /* spam and user headers should never be hashed, and the new envelope may
648    * have better values. Use new versions regardless. */
649   mutt_buffer_free (&base->spam);
650   mutt_free_list (&base->userhdrs);
651   MOVE_ELEM(spam);
652   MOVE_ELEM(userhdrs);
653 #undef MOVE_ELEM
654   
655   mutt_free_envelope(extra);
656 }
657
658 void _mutt_mktemp (char *s, const char *src, int line)
659 {
660
661   snprintf (s, _POSIX_PATH_MAX, "%s/muttng-%s-%d-%d-%d-%x%x", NONULL (Tempdir),
662             NONULL (Hostname), (int) getuid (), (int) getpid (), Counter++, 
663             (unsigned int) rand(), (unsigned int) rand());
664   debug_print (1, ("%s:%d: mutt_mktemp returns \"%s\".\n", src, line, s));
665   unlink (s);
666 }
667
668 void mutt_free_alias (ALIAS ** p)
669 {
670   ALIAS *t;
671
672   while (*p) {
673     t = *p;
674     *p = (*p)->next;
675     mem_free (&t->name);
676     rfc822_free_address (&t->addr);
677     mem_free (&t);
678   }
679 }
680
681 /* collapse the pathname using ~ or = when possible */
682 void mutt_pretty_mailbox (char *s)
683 {
684   char *p = s, *q = s;
685   size_t len;
686   url_scheme_t scheme;
687
688   scheme = url_check_scheme (s);
689
690 #ifdef USE_IMAP
691   if (scheme == U_IMAP || scheme == U_IMAPS) {
692     imap_pretty_mailbox (s);
693     return;
694   }
695 #endif
696
697   /* if s is an url, only collapse path component */
698   if (scheme != U_UNKNOWN) {
699     p = strchr (s, ':') + 1;
700     if (!strncmp (p, "//", 2))
701       q = strchr (p + 2, '/');
702     if (!q)
703       q = strchr (p, '\0');
704     p = q;
705   }
706
707   /* first attempt to collapse the pathname */
708   while (*p) {
709     if (*p == '/' && p[1] == '/') {
710       *q++ = '/';
711       p += 2;
712     }
713     else if (p[0] == '/' && p[1] == '.' && p[2] == '/') {
714       *q++ = '/';
715       p += 3;
716     }
717     else
718       *q++ = *p++;
719   }
720   *q = 0;
721
722   if (str_ncmp (s, Maildir, (len = str_len (Maildir))) == 0 &&
723       s[len] == '/') {
724     *s++ = '=';
725     memmove (s, s + len, str_len (s + len) + 1);
726   }
727   else if (str_ncmp (s, Homedir, (len = str_len (Homedir))) == 0 &&
728            s[len] == '/') {
729     *s++ = '~';
730     memmove (s, s + len - 1, str_len (s + len - 1) + 1);
731   }
732 }
733
734 void mutt_pretty_size (char *s, size_t len, long n)
735 {
736   if (n == 0)
737     strfcpy (s, "0K", len);
738   else if (n < 10189)           /* 0.1K - 9.9K */
739     snprintf (s, len, "%3.1fK", (n < 103) ? 0.1 : n / 1024.0);
740   else if (n < 1023949) {       /* 10K - 999K */
741     /* 51 is magic which causes 10189/10240 to be rounded up to 10 */
742     snprintf (s, len, "%ldK", (n + 51) / 1024);
743   }
744   else if (n < 10433332)        /* 1.0M - 9.9M */
745     snprintf (s, len, "%3.1fM", n / 1048576.0);
746   else {                        /* 10M+ */
747
748     /* (10433332 + 52428) / 1048576 = 10 */
749     snprintf (s, len, "%ldM", (n + 52428) / 1048576);
750   }
751 }
752
753 void mutt_expand_file_fmt (char *dest, size_t destlen, const char *fmt,
754                            const char *src)
755 {
756   char tmp[LONG_STRING];
757
758   mutt_quote_filename (tmp, sizeof (tmp), src);
759   mutt_expand_fmt (dest, destlen, fmt, tmp);
760 }
761
762 void mutt_expand_fmt (char *dest, size_t destlen, const char *fmt,
763                       const char *src)
764 {
765   const char *p;
766   char *d;
767   size_t slen;
768   int found = 0;
769
770   slen = str_len (src);
771   destlen--;
772
773   for (p = fmt, d = dest; destlen && *p; p++) {
774     if (*p == '%') {
775       switch (p[1]) {
776       case '%':
777         *d++ = *p++;
778         destlen--;
779         break;
780       case 's':
781         found = 1;
782         strfcpy (d, src, destlen + 1);
783         d += destlen > slen ? slen : destlen;
784         destlen -= destlen > slen ? slen : destlen;
785         p++;
786         break;
787       default:
788         *d++ = *p;
789         destlen--;
790         break;
791       }
792     }
793     else {
794       *d++ = *p;
795       destlen--;
796     }
797   }
798
799   *d = '\0';
800
801   if (!found && destlen > 0) {
802     str_cat (dest, destlen, " ");
803     str_cat (dest, destlen, src);
804   }
805
806 }
807
808 /* return 0 on success, -1 on abort, 1 on error */
809 int mutt_check_overwrite (const char *attname, const char *path,
810                           char *fname, size_t flen, int *append,
811                           char **directory)
812 {
813   int rc = 0;
814   char tmp[_POSIX_PATH_MAX];
815   struct stat st;
816
817   strfcpy (fname, path, flen);
818   if (access (fname, F_OK) != 0)
819     return 0;
820   if (stat (fname, &st) != 0)
821     return -1;
822   if (S_ISDIR (st.st_mode)) {
823     if (directory) {
824       switch (mutt_multi_choice
825               (_("File is a directory, save under it? [(y)es, (n)o, (a)ll]"),
826                _("yna"))) {
827       case 3:                  /* all */
828         str_replace (directory, fname);
829         break;
830       case 1:                  /* yes */
831         mem_free (directory);
832         break;
833       case -1:                 /* abort */
834         mem_free (directory);
835         return -1;
836       case 2:                  /* no */
837         mem_free (directory);
838         return 1;
839       }
840     }
841     else
842       if ((rc =
843            mutt_yesorno (_("File is a directory, save under it?"),
844                          M_YES)) != M_YES)
845       return (rc == M_NO) ? 1 : -1;
846
847     if (!attname || !attname[0]) {
848       tmp[0] = 0;
849       if (mutt_get_field (_("File under directory: "), tmp, sizeof (tmp),
850                           M_FILE | M_CLEAR) != 0 || !tmp[0])
851         return (-1);
852       mutt_concat_path (fname, path, tmp, flen);
853     }
854     else
855       mutt_concat_path (fname, path, mutt_basename (attname), flen);
856   }
857
858   if (*append == 0 && access (fname, F_OK) == 0) {
859     switch (mutt_multi_choice
860             (_("File exists, (o)verwrite, (a)ppend, or (c)ancel?"), _("oac")))
861     {
862     case -1:                   /* abort */
863       return -1;
864     case 3:                    /* cancel */
865       return 1;
866
867     case 2:                    /* append */
868       *append = M_SAVE_APPEND;
869       break;
870     case 1:                    /* overwrite */
871       *append = M_SAVE_OVERWRITE;
872       break;
873     }
874   }
875   return 0;
876 }
877
878 void mutt_save_path (char *d, size_t dsize, ADDRESS * a)
879 {
880   if (a && a->mailbox) {
881     strfcpy (d, a->mailbox, dsize);
882     if (!option (OPTSAVEADDRESS)) {
883       char *p;
884
885       if ((p = strpbrk (d, "%@")))
886         *p = 0;
887     }
888     str_tolower (d);
889   }
890   else
891     *d = 0;
892 }
893
894 void mutt_safe_path (char *s, size_t l, ADDRESS * a)
895 {
896   char *p;
897
898   mutt_save_path (s, l, a);
899   for (p = s; *p; p++)
900     if (*p == '/' || ISSPACE (*p) || !IsPrint ((unsigned char) *p))
901       *p = '_';
902 }
903
904 /* counts how many characters in s can be skipped while none of the
905  * characters of c appears */
906 int mutt_skipchars (const char *s, const char *c)
907 {
908   int ret = 0;
909   const char *p = s;
910
911   while (s && *s) {
912     register const char *t = c;
913
914     while (t && *t) {
915       if (*t == *s)
916         return (ret);
917       t++;
918     }
919     ret++;
920     s++;
921   }
922   return (str_len (p));
923 }
924
925 void mutt_FormatString (char *dest,     /* output buffer */
926                         size_t destlen, /* output buffer len */
927                         const char *src,        /* template string */
928                         format_t * callback,    /* callback for processing */
929                         unsigned long data,     /* callback data */
930                         format_flag flags)
931 {                               /* callback flags */
932   char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch;
933   char ifstring[SHORT_STRING], elsestring[SHORT_STRING];
934   size_t wlen, count, len, col, wid;
935
936   prefix[0] = '\0';
937   destlen--;                    /* save room for the terminal \0 */
938   wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0;
939   col = wlen;
940
941   while (*src && wlen < destlen) {
942     if (*src == '%') {
943       if (*++src == '%') {
944         *wptr++ = '%';
945         wlen++;
946         col++;
947         src++;
948         continue;
949       }
950
951       if (*src == '?') {
952         flags |= M_FORMAT_OPTIONAL;
953         src++;
954       }
955       else {
956         flags &= ~M_FORMAT_OPTIONAL;
957
958         /* eat the format string */
959         cp = prefix;
960         count = 0;
961         while (count < sizeof (prefix) &&
962                (isdigit ((unsigned char) *src) || *src == '.' || *src == '-'))
963         {
964           *cp++ = *src++;
965           count++;
966         }
967         *cp = 0;
968       }
969
970       if (!*src)
971         break;                  /* bad format */
972
973       ch = *src++;              /* save the character to switch on */
974
975       if (flags & M_FORMAT_OPTIONAL) {
976         if (*src != '?')
977           break;                /* bad format */
978         src++;
979
980         /* eat the `if' part of the string */
981         cp = ifstring;
982         count = 0;
983         while (count < sizeof (ifstring) && *src && *src != '?'
984                && *src != '&') {
985           *cp++ = *src++;
986           count++;
987         }
988         *cp = 0;
989
990         /* eat the `else' part of the string (optional) */
991         if (*src == '&')
992           src++;                /* skip the & */
993         cp = elsestring;
994         count = 0;
995         while (count < sizeof (elsestring) && *src && *src != '?') {
996           *cp++ = *src++;
997           count++;
998         }
999         *cp = 0;
1000
1001         if (!*src)
1002           break;                /* bad format */
1003
1004         src++;                  /* move past the trailing `?' */
1005       }
1006
1007       /* handle generic cases first */
1008       if (ch == '>') {
1009         /* right justify to EOL */
1010         ch = *src++;            /* pad char */
1011         /* calculate space left on line.  if we've already written more data
1012            than will fit on the line, ignore the rest of the line */
1013         if (DrawFullLine || option (OPTSTATUSONTOP))
1014           count = (COLS < destlen ? COLS : destlen);
1015         else
1016           count =
1017             ((COLS - SidebarWidth) <
1018              destlen ? (COLS - SidebarWidth) : destlen);
1019         if (count > col) {
1020           count -= col;         /* how many columns left on this line */
1021           mutt_FormatString (buf, sizeof (buf), src, callback, data, flags);
1022           wid = str_len (buf);
1023           if (count > wid) {
1024             count -= wid;       /* how many chars to pad */
1025             memset (wptr, ch, count);
1026             wptr += count;
1027             col += count;
1028           }
1029           if (wid + wlen > destlen)
1030             len = destlen - wlen;
1031           else
1032             len = wid;
1033           memcpy (wptr, buf, len);
1034           wptr += len;
1035           wlen += len;
1036           col += mutt_strwidth (buf);
1037         }
1038         break;                  /* skip rest of input */
1039       }
1040       else if (ch == '|') {
1041         /* pad to EOL */
1042         ch = *src++;
1043         if (destlen > COLS)
1044           destlen = COLS;
1045         if (destlen > wlen) {
1046           count = destlen - wlen;
1047           memset (wptr, ch, count);
1048           wptr += count;
1049         }
1050         break;                  /* skip rest of input */
1051       }
1052       else {
1053         short tolower = 0;
1054         short nodots = 0;
1055
1056         while (ch == '_' || ch == ':') {
1057           if (ch == '_')
1058             tolower = 1;
1059           else if (ch == ':')
1060             nodots = 1;
1061
1062           ch = *src++;
1063         }
1064
1065         /* use callback function to handle this case */
1066         src =
1067           callback (buf, sizeof (buf), ch, src, prefix, ifstring, elsestring,
1068                     data, flags);
1069
1070         if (tolower)
1071           str_tolower (buf);
1072         if (nodots) {
1073           char *p = buf;
1074
1075           for (; *p; p++)
1076             if (*p == '.')
1077               *p = '_';
1078         }
1079
1080         if ((len = str_len (buf)) + wlen > destlen)
1081           len = (destlen - wlen > 0) ? (destlen - wlen) : 0;
1082
1083         memcpy (wptr, buf, len);
1084         wptr += len;
1085         wlen += len;
1086         col += mutt_strwidth (buf);
1087       }
1088     }
1089     else if (*src == '\\') {
1090       if (!*++src)
1091         break;
1092       switch (*src) {
1093       case 'n':
1094         *wptr = '\n';
1095         break;
1096       case 't':
1097         *wptr = '\t';
1098         break;
1099       case 'r':
1100         *wptr = '\r';
1101         break;
1102       case 'f':
1103         *wptr = '\f';
1104         break;
1105       case 'v':
1106         *wptr = '\v';
1107         break;
1108       default:
1109         *wptr = *src;
1110         break;
1111       }
1112       src++;
1113       wptr++;
1114       wlen++;
1115       col++;
1116     }
1117     else {
1118       unsigned int bar = mutt_skipchars (src, "%\\");
1119       char *bar2 = mem_malloc (bar + 1);
1120
1121       strfcpy (bar2, src, bar + 1);
1122       while (bar--) {
1123         *wptr++ = *src++;
1124         wlen++;
1125       }
1126       col += mutt_strwidth (bar2);
1127       mem_free (&bar2);
1128     }
1129   }
1130   *wptr = 0;
1131
1132 #if 0
1133   if (flags & M_FORMAT_MAKEPRINT) {
1134     /* Make sure that the string is printable by changing all non-printable
1135        chars to dots, or spaces for non-printable whitespace */
1136     for (cp = dest; *cp; cp++)
1137       if (!IsPrint (*cp) && !((flags & M_FORMAT_TREE) && (*cp <= M_TREE_MAX)))
1138         *cp = isspace ((unsigned char) *cp) ? ' ' : '.';
1139   }
1140 #endif
1141 }
1142
1143 /* This function allows the user to specify a command to read stdout from in
1144    place of a normal file.  If the last character in the string is a pipe (|),
1145    then we assume it is a commmand to run instead of a normal file. */
1146 FILE *mutt_open_read (const char *path, pid_t * thepid)
1147 {
1148   FILE *f;
1149   struct stat s;
1150
1151   int len = str_len (path);
1152
1153   if (path[len - 1] == '|') {
1154     /* read from a pipe */
1155
1156     char *s = str_dup (path);
1157
1158     s[len - 1] = 0;
1159     mutt_endwin (NULL);
1160     *thepid = mutt_create_filter (s, NULL, &f, NULL);
1161     mem_free (&s);
1162   }
1163   else {
1164     if (stat (path, &s) < 0)
1165       return (NULL);
1166     if (S_ISDIR (s.st_mode)) {
1167       errno = EINVAL;
1168       return (NULL);
1169     }
1170     f = fopen (path, "r");
1171     *thepid = -1;
1172   }
1173   return (f);
1174 }
1175
1176 /* returns 0 if OK to proceed, -1 to abort, 1 to retry */
1177 int mutt_save_confirm (const char *s, struct stat *st)
1178 {
1179   char tmp[_POSIX_PATH_MAX];
1180   int ret = 0;
1181   int rc;
1182   int magic = 0;
1183
1184   magic = mx_get_magic (s);
1185
1186 #ifdef USE_POP
1187   if (magic == M_POP) {
1188     mutt_error _("Can't save message to POP mailbox.");
1189
1190     return 1;
1191   }
1192 #endif
1193
1194 #ifdef USE_NNTP
1195   if (magic == M_NNTP) {
1196     mutt_error _("Can't save message to newsserver.");
1197
1198     return 0;
1199   }
1200 #endif
1201
1202   if (magic > 0 && !mx_access (s, W_OK)) {
1203     if (option (OPTCONFIRMAPPEND) &&
1204         (!TrashPath || (str_cmp (s, TrashPath) != 0))) {
1205       /* if we're appending to the trash, there's no point in asking */
1206       snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
1207       if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1208         ret = 1;
1209       else if (rc == -1)
1210         ret = -1;
1211     }
1212   }
1213
1214   if (stat (s, st) != -1) {
1215     if (magic == -1) {
1216       mutt_error (_("%s is not a mailbox!"), s);
1217       return 1;
1218     }
1219   }
1220   else {
1221 #ifdef USE_IMAP
1222     if (magic != M_IMAP)
1223 #endif /* execute the block unconditionally if we don't use imap */
1224     {
1225       st->st_mtime = 0;
1226       st->st_atime = 0;
1227
1228       if (errno == ENOENT) {
1229         if (option (OPTCONFIRMCREATE)) {
1230           snprintf (tmp, sizeof (tmp), _("Create %s?"), s);
1231           if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
1232             ret = 1;
1233           else if (rc == -1)
1234             ret = -1;
1235         }
1236       }
1237       else {
1238         mutt_perror (s);
1239         return 1;
1240       }
1241     }
1242   }
1243
1244   CLEARLINE (LINES - 1);
1245   return (ret);
1246 }
1247
1248 void mutt_display_sanitize (char *s)
1249 {
1250   for (; *s; s++) {
1251     if (!IsPrint (*s))
1252       *s = '?';
1253   }
1254 }
1255
1256 void mutt_sleep (short s)
1257 {
1258   if (SleepTime > s)
1259     sleep (SleepTime);
1260   else if (s)
1261     sleep (s);
1262 }
1263
1264 /* Decrease a file's modification time by 1 second */
1265 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1266 {
1267   struct utimbuf utim;
1268   struct stat _st;
1269   time_t mtime;
1270
1271   if (!st) {
1272     if (stat (f, &_st) == -1)
1273       return -1;
1274     st = &_st;
1275   }
1276
1277   if ((mtime = st->st_mtime) == time (NULL)) {
1278     mtime -= 1;
1279     utim.actime = mtime;
1280     utim.modtime = mtime;
1281     utime (f, &utim);
1282   }
1283
1284   return mtime;
1285 }
1286
1287 /* sets mtime of 'to' to mtime of 'from' */
1288 void mutt_set_mtime (const char* from, const char* to) {
1289   struct utimbuf utim;
1290   struct stat st;
1291
1292   if (stat (from, &st) != -1) {
1293     utim.actime = st.st_mtime;
1294     utim.modtime = st.st_mtime;
1295     utime (to, &utim);
1296   }
1297 }
1298
1299 const char *mutt_make_version (int full)
1300 {
1301   static char vstring[STRING];
1302
1303   if (full)
1304     snprintf (vstring, sizeof (vstring), "Mutt-ng %s-r%s (based "
1305               "on Mutt 1.5.10/%s)", MUTT_VERSION, MUTT_REVISION,
1306               ReleaseDate);
1307   else
1308     snprintf (vstring, sizeof (vstring), "mutt-ng/%s-r%s",
1309               MUTT_VERSION, MUTT_REVISION);
1310   return vstring;
1311 }
1312
1313 void mutt_free_spam_list (SPAM_LIST ** list)
1314 {
1315   SPAM_LIST *p;
1316
1317   if (!list)
1318     return;
1319   while (*list) {
1320     p = *list;
1321     *list = (*list)->next;
1322     rx_free (&p->rx);
1323     mem_free(&p->template);
1324     mem_free(&p);
1325   }
1326 }
1327
1328 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
1329 {
1330   static regmatch_t *pmatch = NULL;
1331   static int nmatch = 0;
1332   int i, n, tlen;
1333   char *p;
1334
1335   if (!s)
1336     return 0;
1337
1338   tlen = 0;
1339
1340   for (; l; l = l->next) {
1341     /* If this pattern needs more matches, expand pmatch. */
1342     if (l->nmatch > nmatch) {
1343       mem_realloc (&pmatch, l->nmatch * sizeof (regmatch_t));
1344       nmatch = l->nmatch;
1345     }
1346
1347     /* Does this pattern match? */
1348     if (regexec
1349         (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch,
1350          (int) 0) == 0) {
1351       debug_print (5, ("%s matches %s\n%d subst", s, l->rx->pattern, l->rx->rx->re_nsub));
1352
1353       /* Copy template into text, with substitutions. */
1354       for (p = l->template; *p;) {
1355         if (*p == '%') {
1356           n = atoi (++p);       /* find pmatch index */
1357           while (isdigit (*p))
1358             ++p;                /* skip subst token */
1359           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1360             text[tlen++] = s[i];
1361         }
1362         else {
1363           text[tlen++] = *p++;
1364         }
1365       }
1366       text[tlen] = '\0';
1367       debug_print (5, ("\"%s\"\n", text));
1368       return 1;
1369     }
1370   }
1371
1372   return 0;
1373 }
1374
1375 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
1376   if (h1 && h2) {
1377     if (h1->received != h2->received ||
1378         h1->date_sent != h2->date_sent ||
1379         h1->content->length != h2->content->length ||
1380         h1->lines != h2->lines ||
1381         h1->zhours != h2->zhours ||
1382         h1->zminutes != h2->zminutes ||
1383         h1->zoccident != h2->zoccident ||
1384         h1->mime != h2->mime ||
1385         !mutt_cmp_env (h1->env, h2->env) ||
1386         !mutt_cmp_body (h1->content, h2->content))
1387       return (0);
1388     else
1389       return (1);
1390   }
1391   else {
1392     if (h1 == NULL && h2 == NULL)
1393       return (1);
1394     else
1395       return (0);
1396   }
1397 }
1398
1399 /* return 1 if address lists are strictly identical */
1400 int mutt_cmp_addr (const ADDRESS * a, const ADDRESS * b)
1401 {
1402   while (a && b) {
1403     if (str_cmp (a->mailbox, b->mailbox) ||
1404         str_cmp (a->personal, b->personal))
1405       return (0);
1406
1407     a = a->next;
1408     b = b->next;
1409   }
1410   if (a || b)
1411     return (0);
1412
1413   return (1);
1414 }
1415
1416 int mutt_cmp_list (const LIST * a, const LIST * b)
1417 {
1418   while (a && b) {
1419     if (str_cmp (a->data, b->data))
1420       return (0);
1421
1422     a = a->next;
1423     b = b->next;
1424   }
1425   if (a || b)
1426     return (0);
1427
1428   return (1);
1429 }
1430
1431 int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
1432 {
1433   if (e1 && e2) {
1434     if (str_cmp (e1->message_id, e2->message_id) ||
1435         str_cmp (e1->subject, e2->subject) ||
1436         !mutt_cmp_list (e1->references, e2->references) ||
1437         !mutt_cmp_addr (e1->from, e2->from) ||
1438         !mutt_cmp_addr (e1->sender, e2->sender) ||
1439         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
1440         !mutt_cmp_addr (e1->to, e2->to) ||
1441         !mutt_cmp_addr (e1->cc, e2->cc) ||
1442         !mutt_cmp_addr (e1->return_path, e2->return_path))
1443       return (0);
1444     else
1445       return (1);
1446   }
1447   else {
1448     if (e1 == NULL && e2 == NULL)
1449       return (1);
1450     else
1451       return (0);
1452   }
1453 }
1454
1455 int mutt_cmp_param (const PARAMETER * p1, const PARAMETER * p2)
1456 {
1457   while (p1 && p2) {
1458     if (str_cmp (p1->attribute, p2->attribute) ||
1459         str_cmp (p1->value, p2->value))
1460       return (0);
1461
1462     p1 = p1->next;
1463     p2 = p2->next;
1464   }
1465   if (p1 || p2)
1466     return (0);
1467
1468   return (1);
1469 }
1470
1471 int mutt_cmp_body (const BODY * b1, const BODY * b2)
1472 {
1473   if (b1->type != b2->type ||
1474       b1->encoding != b2->encoding ||
1475       str_cmp (b1->subtype, b2->subtype) ||
1476       str_cmp (b1->description, b2->description) ||
1477       !mutt_cmp_param (b1->parameter, b2->parameter) ||
1478       b1->length != b2->length)
1479     return (0);
1480   return (1);
1481 }