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