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