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