Rocco Rutte:
[apps/madmutt.git] / muttlib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include "mutt.h"
16 #include "mutt_curses.h"
17 #include "mime.h"
18 #include "mx.h"
19 #include "url.h"
20
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 *) safe_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 = mutt_strlen (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 = safe_strdup (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 = safe_strdup (b->xtype);
134   b->subtype = safe_strdup (b->subtype);
135   b->form_name = safe_strdup (b->form_name);
136   b->filename = safe_strdup (b->filename);
137   b->d_filename = safe_strdup (b->d_filename);
138   b->description = safe_strdup (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 = safe_strdup (par->attribute);
153     (*ppar)->value = safe_strdup (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     FREE (&b->filename);
181     FREE (&b->content);
182     FREE (&b->xtype);
183     FREE (&b->subtype);
184     FREE (&b->description);
185     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     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     FREE (&t->attribute);
209     FREE (&t->value);
210     o = t;
211     t = t->next;
212     FREE (&o);
213   }
214   *p = 0;
215 }
216
217 LIST *mutt_add_list (LIST * head, const char *data) {
218   size_t len = mutt_strlen (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 = safe_malloc (sizeof (LIST));
229     tmp = tmp->next;
230   } else
231     head = tmp = safe_malloc (sizeof (LIST));
232
233   tmp->data = safe_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     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 = imap[s]://host/: don't append slash */
372         if (imap_is_magic (NONULL (Maildir), NULL) == M_IMAP && 
373             Maildir[mutt_strlen (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 = mutt_strlen (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 = safe_strdup (attribute);
541   q->value = safe_strdup (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   FREE (&(*p)->list_post);
625   FREE (&(*p)->subject);
626   /* real_subj is just an offset to subject and shouldn't be freed */
627   FREE (&(*p)->message_id);
628   FREE (&(*p)->supersedes);
629   FREE (&(*p)->date);
630   FREE (&(*p)->x_label);
631   FREE (&(*p)->organization);
632 #ifdef USE_NNTP
633   FREE (&(*p)->newsgroups);
634   FREE (&(*p)->xref);
635   FREE (&(*p)->followup_to);
636   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   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     FREE (&t->name);
708     rfc822_free_address (&t->addr);
709     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 (safe_strncmp (s, Maildir, (len = mutt_strlen (Maildir))) == 0 &&
755       s[len] == '/') {
756     *s++ = '=';
757     memmove (s, s + len, mutt_strlen (s + len) + 1);
758   }
759   else if (safe_strncmp (s, Homedir, (len = mutt_strlen (Homedir))) == 0 &&
760            s[len] == '/') {
761     *s++ = '~';
762     memmove (s, s + len - 1, mutt_strlen (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 = mutt_strlen (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     safe_strcat (dest, destlen, " ");
835     safe_strcat (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         FREE (directory);
864         break;
865       case -1:                 /* abort */
866         FREE (directory);
867         return -1;
868       case 2:                  /* no */
869         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 (mutt_strlen (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 = mutt_strlen (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 = mutt_strlen (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 = safe_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       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 = mutt_strlen (path);
1184
1185   if (path[len - 1] == '|') {
1186     /* read from a pipe */
1187
1188     char *s = safe_strdup (path);
1189
1190     s[len - 1] = 0;
1191     mutt_endwin (NULL);
1192     *thepid = mutt_create_filter (s, NULL, &f, NULL);
1193     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 || (mutt_strcmp (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 state_prefix_putc (char c, STATE * s)
1281 {
1282   if (s->flags & M_PENDINGPREFIX) {
1283     int i;
1284
1285     i = mutt_strlen (Quotebuf);
1286     Quotebuf[i++] = c;
1287     Quotebuf[i] = '\0';
1288     if (i == sizeof (Quotebuf) - 1 || c == '\n') {
1289       char buf[2 * SHORT_STRING];
1290       int j = 0, offset = 0;
1291       regmatch_t pmatch[1];
1292
1293       state_reset_prefix (s);
1294       while (regexec
1295              ((regex_t *) QuoteRegexp.rx, &Quotebuf[offset], 1, pmatch,
1296               0) == 0)
1297         offset += pmatch->rm_eo;
1298
1299       if (!option (OPTQUOTEEMPTY) && Quotebuf[offset] == '\n') {
1300         buf[0] = '\n';
1301         buf[1] = '\0';
1302       }
1303       else if (option (OPTQUOTEQUOTED) && offset) {
1304         for (i = 0; i < offset; i++)
1305           if (Quotebuf[i] != ' ')
1306             j = i;
1307         strncpy (buf, Quotebuf, j + 1);
1308         strcpy (buf + j + 1, Quotebuf + j);
1309       }
1310       else
1311         snprintf (buf, sizeof (buf), "%s%s", NONULL (s->prefix), Quotebuf);
1312
1313       state_puts (buf, s);
1314     }
1315   }
1316   else
1317     state_putc (c, s);
1318
1319   if (c == '\n') {
1320     state_set_prefix (s);
1321     Quotebuf[0] = '\0';
1322   }
1323 }
1324
1325 int state_printf (STATE * s, const char *fmt, ...)
1326 {
1327   int rv;
1328   va_list ap;
1329
1330   va_start (ap, fmt);
1331   rv = vfprintf (s->fpout, fmt, ap);
1332   va_end (ap);
1333
1334   return rv;
1335 }
1336
1337 void state_mark_attach (STATE * s)
1338 {
1339   if ((s->flags & M_DISPLAY) && !mutt_strcmp (Pager, "builtin"))
1340     state_puts (AttachmentMarker, s);
1341 }
1342
1343 void state_attach_puts (const char *t, STATE * s)
1344 {
1345   if (*t != '\n')
1346     state_mark_attach (s);
1347   while (*t) {
1348     state_putc (*t, s);
1349     if (*t++ == '\n' && *t)
1350       if (*t != '\n')
1351         state_mark_attach (s);
1352   }
1353 }
1354
1355 void mutt_display_sanitize (char *s)
1356 {
1357   for (; *s; s++) {
1358     if (!IsPrint (*s))
1359       *s = '?';
1360   }
1361 }
1362
1363 void mutt_sleep (short s)
1364 {
1365   if (SleepTime > s)
1366     sleep (SleepTime);
1367   else if (s)
1368     sleep (s);
1369 }
1370
1371 /*
1372  * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
1373  * just initializes. Frees anything already in the buffer.
1374  *
1375  * Disregards the 'destroy' flag, which seems reserved for caller.
1376  * This is bad, but there's no apparent protocol for it.
1377  */
1378 BUFFER *mutt_buffer_init (BUFFER * b)
1379 {
1380   if (!b) {
1381     b = safe_malloc (sizeof (BUFFER));
1382     if (!b)
1383       return NULL;
1384   }
1385   else {
1386     FREE(&b->data);
1387   }
1388   memset (b, 0, sizeof (BUFFER));
1389   return b;
1390 }
1391
1392 /*
1393  * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
1394  * just initializes. Frees anything already in the buffer. Copies in
1395  * the seed string.
1396  *
1397  * Disregards the 'destroy' flag, which seems reserved for caller.
1398  * This is bad, but there's no apparent protocol for it.
1399  */
1400 BUFFER *mutt_buffer_from (BUFFER * b, char *seed)
1401 {
1402   if (!seed)
1403     return NULL;
1404
1405   b = mutt_buffer_init (b);
1406   b->data = safe_strdup (seed);
1407   b->dsize = mutt_strlen (seed);
1408   b->dptr = (char *) b->data + b->dsize;
1409   return b;
1410 }
1411
1412 void mutt_buffer_addstr (BUFFER * buf, const char *s)
1413 {
1414   mutt_buffer_add (buf, s, mutt_strlen (s));
1415 }
1416
1417 void mutt_buffer_addch (BUFFER * buf, char c)
1418 {
1419   mutt_buffer_add (buf, &c, 1);
1420 }
1421
1422 void mutt_buffer_free (BUFFER ** p)
1423 {
1424   if (!p || !*p)
1425     return;
1426
1427   FREE (&(*p)->data);
1428   /* dptr is just an offset to data and shouldn't be freed */
1429   FREE (p);
1430 }
1431
1432 /* dynamically grows a BUFFER to accomodate s, in increments of 128 bytes.
1433  * Always one byte bigger than necessary for the null terminator, and
1434  * the buffer is always null-terminated */
1435 void mutt_buffer_add (BUFFER * buf, const char *s, size_t len)
1436 {
1437   size_t offset;
1438
1439   if (buf->dptr + len + 1 > buf->data + buf->dsize) {
1440     offset = buf->dptr - buf->data;
1441     buf->dsize += len < 128 ? 128 : len + 1;
1442     safe_realloc ((void **) &buf->data, buf->dsize);
1443     buf->dptr = buf->data + offset;
1444   }
1445   memcpy (buf->dptr, s, len);
1446   buf->dptr += len;
1447   *(buf->dptr) = '\0';
1448 }
1449
1450 /* Decrease a file's modification time by 1 second */
1451
1452 time_t mutt_decrease_mtime (const char *f, struct stat *st)
1453 {
1454   struct utimbuf utim;
1455   struct stat _st;
1456   time_t mtime;
1457
1458   if (!st) {
1459     if (stat (f, &_st) == -1)
1460       return -1;
1461     st = &_st;
1462   }
1463
1464   if ((mtime = st->st_mtime) == time (NULL)) {
1465     mtime -= 1;
1466     utim.actime = mtime;
1467     utim.modtime = mtime;
1468     utime (f, &utim);
1469   }
1470
1471   return mtime;
1472 }
1473
1474 const char *mutt_make_version (void)
1475 {
1476   static char vstring[STRING];
1477
1478   snprintf (vstring, sizeof (vstring), "Mutt-ng %s (%s) based on Mutt 1.5.9",
1479             MUTT_VERSION, ReleaseDate);
1480   return vstring;
1481 }
1482
1483 void mutt_free_spam_list (SPAM_LIST ** list)
1484 {
1485   SPAM_LIST *p;
1486
1487   if (!list)
1488     return;
1489   while (*list) {
1490     p = *list;
1491     *list = (*list)->next;
1492     rx_free (&p->rx);
1493     FREE(&p->template);
1494     FREE(&p);
1495   }
1496 }
1497
1498 int mutt_match_spam_list (const char *s, SPAM_LIST * l, char *text, int x)
1499 {
1500   static regmatch_t *pmatch = NULL;
1501   static int nmatch = 0;
1502   int i, n, tlen;
1503   char *p;
1504
1505   if (!s)
1506     return 0;
1507
1508   tlen = 0;
1509
1510   for (; l; l = l->next) {
1511     /* If this pattern needs more matches, expand pmatch. */
1512     if (l->nmatch > nmatch) {
1513       safe_realloc (&pmatch, l->nmatch * sizeof (regmatch_t));
1514       nmatch = l->nmatch;
1515     }
1516
1517     /* Does this pattern match? */
1518     if (regexec
1519         (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch,
1520          (int) 0) == 0) {
1521       debug_print (5, ("%s matches %s\n%d subst", s, l->rx->pattern, l->rx->rx->re_nsub));
1522
1523       /* Copy template into text, with substitutions. */
1524       for (p = l->template; *p;) {
1525         if (*p == '%') {
1526           n = atoi (++p);       /* find pmatch index */
1527           while (isdigit (*p))
1528             ++p;                /* skip subst token */
1529           for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
1530             text[tlen++] = s[i];
1531         }
1532         else {
1533           text[tlen++] = *p++;
1534         }
1535       }
1536       text[tlen] = '\0';
1537       debug_print (5, ("\"%s\"\n", text));
1538       return 1;
1539     }
1540   }
1541
1542   return 0;
1543 }
1544
1545 int mutt_cmp_header (const HEADER * h1, const HEADER * h2) {
1546   if (h1 && h2) {
1547     if (h1->received != h2->received ||
1548         h1->date_sent != h2->date_sent ||
1549         h1->content->length != h2->content->length ||
1550         h1->lines != h2->lines ||
1551         h1->zhours != h2->zhours ||
1552         h1->zminutes != h2->zminutes ||
1553         h1->zoccident != h2->zoccident ||
1554         h1->mime != h2->mime ||
1555         !mutt_cmp_env (h1->env, h2->env) ||
1556         !mutt_cmp_body (h1->content, h2->content))
1557       return (0);
1558     else
1559       return (1);
1560   }
1561   else {
1562     if (h1 == NULL && h2 == NULL)
1563       return (1);
1564     else
1565       return (0);
1566   }
1567 }
1568
1569 /* return 1 if address lists are strictly identical */
1570 int mutt_cmp_addr (const ADDRESS * a, const ADDRESS * b)
1571 {
1572   while (a && b) {
1573     if (mutt_strcmp (a->mailbox, b->mailbox) ||
1574         mutt_strcmp (a->personal, b->personal))
1575       return (0);
1576
1577     a = a->next;
1578     b = b->next;
1579   }
1580   if (a || b)
1581     return (0);
1582
1583   return (1);
1584 }
1585
1586 int mutt_cmp_list (const LIST * a, const LIST * b)
1587 {
1588   while (a && b) {
1589     if (mutt_strcmp (a->data, b->data))
1590       return (0);
1591
1592     a = a->next;
1593     b = b->next;
1594   }
1595   if (a || b)
1596     return (0);
1597
1598   return (1);
1599 }
1600
1601 int mutt_cmp_env (const ENVELOPE * e1, const ENVELOPE * e2)
1602 {
1603   if (e1 && e2) {
1604     if (mutt_strcmp (e1->message_id, e2->message_id) ||
1605         mutt_strcmp (e1->subject, e2->subject) ||
1606         !mutt_cmp_list (e1->references, e2->references) ||
1607         !mutt_cmp_addr (e1->from, e2->from) ||
1608         !mutt_cmp_addr (e1->sender, e2->sender) ||
1609         !mutt_cmp_addr (e1->reply_to, e2->reply_to) ||
1610         !mutt_cmp_addr (e1->to, e2->to) ||
1611         !mutt_cmp_addr (e1->cc, e2->cc) ||
1612         !mutt_cmp_addr (e1->return_path, e2->return_path))
1613       return (0);
1614     else
1615       return (1);
1616   }
1617   else {
1618     if (e1 == NULL && e2 == NULL)
1619       return (1);
1620     else
1621       return (0);
1622   }
1623 }
1624
1625 int mutt_cmp_param (const PARAMETER * p1, const PARAMETER * p2)
1626 {
1627   while (p1 && p2) {
1628     if (mutt_strcmp (p1->attribute, p2->attribute) ||
1629         mutt_strcmp (p1->value, p2->value))
1630       return (0);
1631
1632     p1 = p1->next;
1633     p2 = p2->next;
1634   }
1635   if (p1 || p2)
1636     return (0);
1637
1638   return (1);
1639 }
1640
1641 int mutt_cmp_body (const BODY * b1, const BODY * b2)
1642 {
1643   if (b1->type != b2->type ||
1644       b1->encoding != b2->encoding ||
1645       mutt_strcmp (b1->subtype, b2->subtype) ||
1646       mutt_strcmp (b1->description, b2->description) ||
1647       !mutt_cmp_param (b1->parameter, b2->parameter) ||
1648       b1->length != b2->length)
1649     return (0);
1650   return (1);
1651 }