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