lot of restructurations. work in progress, *but* it does not seems to
[apps/madmutt.git] / parse.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <lib-lib/mem.h>
15 #include <lib-lib/str.h>
16 #include <lib-lib/ascii.h>
17 #include <lib-lib/macros.h>
18 #include <lib-lib/buffer.h>
19
20 #include <lib-mime/mime.h>
21
22 #include "mutt.h"
23 #include "enter.h"
24 #include "recvattach.h"
25 #include "mx.h"
26 #include "rfc2047.h"
27 #include "mutt_crypt.h"
28 #include "url.h"
29
30 #include "lib/rx.h"
31 #include "lib/debug.h"
32
33 #include <string.h>
34 #include <ctype.h>
35 #include <sys/stat.h>
36 #include <stdlib.h>
37
38 /* Reads an arbitrarily long header field, and looks ahead for continuation
39  * lines.  ``line'' must point to a dynamically allocated string; it is
40  * increased if more space is required to fit the whole line.
41  */
42 char *mutt_read_rfc822_line (FILE * f, char *line, size_t * linelen)
43 {
44   char *buf = line;
45   char ch;
46   size_t offset = 0;
47
48   for (;;) {
49     if (fgets (buf, *linelen - offset, f) == NULL ||    /* end of file or */
50         (ISSPACE (*line) && !offset)) { /* end of headers */
51       *line = 0;
52       return (line);
53     }
54
55     buf += m_strlen(buf) - 1;
56     if (*buf == '\n') {
57       /* we did get a full line. remove trailing space */
58       while (ISSPACE (*buf))
59         *buf-- = 0;             /* we cannot come beyond line's beginning because
60                                  * it begins with a non-space */
61
62       /* check to see if the next line is a continuation line */
63       if ((ch = fgetc (f)) != ' ' && ch != '\t') {
64         ungetc (ch, f);
65         return (line);          /* next line is a separate header field or EOH */
66       }
67
68       /* eat tabs and spaces from the beginning of the continuation line */
69       while ((ch = fgetc (f)) == ' ' || ch == '\t');
70       ungetc (ch, f);
71       *++buf = ' ';             /* string is still terminated because we removed
72                                    at least one whitespace char above */
73     }
74
75     buf++;
76     offset = buf - line;
77     if (*linelen < offset + STRING) {
78       /* grow the buffer */
79       *linelen += STRING;
80       p_realloc(&line, *linelen);
81       buf = line + offset;
82     }
83   }
84   /* not reached */
85 }
86
87 LIST *mutt_parse_references (char *s, int in_reply_to)
88 {
89   LIST *t, *lst = NULL;
90   int m, n = 0;
91   char *o = NULL, *new, *at;
92
93   while ((s = strtok (s, " \t;")) != NULL) {
94     /*
95      * some mail clients add other garbage besides message-ids, so do a quick
96      * check to make sure this looks like a valid message-id
97      * some idiotic clients also break their message-ids between lines, deal
98      * with that too (give up if it's more than two lines, though)
99      */
100     t = NULL;
101     new = NULL;
102
103     if (*s == '<') {
104       n = m_strlen(s);
105       if (s[n - 1] != '>') {
106         o = s;
107         s = NULL;
108         continue;
109       }
110
111       new = m_strdup(s);
112     }
113     else if (o) {
114       m = m_strlen(s);
115       if (s[m - 1] == '>') {
116         new = p_new(char, n + m + 1);
117         strcpy (new, o);        /* __STRCPY_CHECKED__ */
118         strcpy (new + n, s);    /* __STRCPY_CHECKED__ */
119       }
120     }
121     if (new) {
122       /* make sure that this really does look like a message-id.
123        * it should have exactly one @, and if we're looking at
124        * an in-reply-to header, make sure that the part before
125        * the @ has more than eight characters or it's probably
126        * an email address
127        */
128       if (!(at = strchr (new, '@')) || strchr (at + 1, '@')
129           || (in_reply_to && at - new <= 8))
130         p_delete(&new);
131       else {
132         t = p_new(LIST, 1);
133         t->data = new;
134         t->next = lst;
135         lst = t;
136       }
137     }
138     o = NULL;
139     s = NULL;
140   }
141
142   return (lst);
143 }
144
145 int mutt_check_encoding (const char *c)
146 {
147   if (ascii_strncasecmp ("7bit", c, sizeof ("7bit") - 1) == 0)
148     return (ENC7BIT);
149   else if (ascii_strncasecmp ("8bit", c, sizeof ("8bit") - 1) == 0)
150     return (ENC8BIT);
151   else if (ascii_strncasecmp ("binary", c, sizeof ("binary") - 1) == 0)
152     return (ENCBINARY);
153   else
154     if (ascii_strncasecmp
155         ("quoted-printable", c, sizeof ("quoted-printable") - 1) == 0)
156     return (ENCQUOTEDPRINTABLE);
157   else if (ascii_strncasecmp ("base64", c, sizeof ("base64") - 1) == 0)
158     return (ENCBASE64);
159   else if (ascii_strncasecmp ("x-uuencode", c, sizeof ("x-uuencode") - 1) ==
160            0)
161     return (ENCUUENCODED);
162 #ifdef SUN_ATTACHMENT
163   else if (ascii_strncasecmp ("uuencode", c, sizeof ("uuencode") - 1) == 0)
164     return (ENCUUENCODED);
165 #endif
166   else
167     return (ENCOTHER);
168 }
169
170 static PARAMETER *parse_parameters (const char *s)
171 {
172   PARAMETER *head = 0, *cur = 0, *new;
173   char buffer[LONG_STRING];
174   const char *p;
175   size_t i;
176
177   debug_print (2, ("`%s'\n", s));
178
179   while (*s) {
180     if ((p = strpbrk (s, "=;")) == NULL) {
181       debug_print (1, ("malformed parameter: %s\n", s));
182       goto bail;
183     }
184
185     /* if we hit a ; now the parameter has no value, just skip it */
186     if (*p != ';') {
187       i = p - s;
188
189       new = mutt_new_parameter ();
190
191       new->attribute = p_dupstr(s, i);
192
193       /* remove whitespace from the end of the attribute name */
194       while (ISSPACE (new->attribute[--i]))
195         new->attribute[i] = 0;
196
197       s = vskipspaces(p + 1);     /* skip over the = */
198
199       if (*s == '"') {
200         int state_ascii = 1;
201
202         s++;
203         for (i = 0; *s && i < sizeof (buffer) - 1; i++, s++) {
204           if (!option (OPTSTRICTMIME)) {
205             /* As iso-2022-* has a characer of '"' with non-ascii state,
206              * ignore it. */
207             if (*s == 0x1b && i < sizeof (buffer) - 2) {
208               if (s[1] == '(' && (s[2] == 'B' || s[2] == 'J'))
209                 state_ascii = 1;
210               else
211                 state_ascii = 0;
212             }
213           }
214           if (state_ascii && *s == '"')
215             break;
216           if (*s == '\\') {
217             /* Quote the next character */
218             buffer[i] = s[1];
219             if (!*++s)
220               break;
221           }
222           else
223             buffer[i] = *s;
224         }
225         buffer[i] = 0;
226         if (*s)
227           s++;                  /* skip over the " */
228       }
229       else {
230         for (i = 0; *s && *s != ' ' && *s != ';' && i < sizeof (buffer) - 1;
231              i++, s++)
232           buffer[i] = *s;
233         buffer[i] = 0;
234       }
235
236       new->value = m_strdup(buffer);
237
238       debug_print (2, ("`%s' = `%s'\n", new->attribute ? new->attribute : "",
239                   new->value ? new->value : ""));
240
241       /* Add this parameter to the list */
242       if (head) {
243         cur->next = new;
244         cur = cur->next;
245       }
246       else
247         head = cur = new;
248     }
249     else {
250       debug_print (1, ("parameter with no value: %s\n", s));
251       s = p;
252     }
253
254     /* Find the next parameter */
255     if (*s != ';' && (s = strchr (s, ';')) == NULL)
256       break;                    /* no more parameters */
257
258     do {
259       /* Move past any leading whitespace */
260       s = vskipspaces(s + 1);
261     }
262     while (*s == ';');          /* skip empty parameters */
263   }
264
265 bail:
266
267   rfc2231_decode_parameters (&head);
268   return (head);
269 }
270
271 int mutt_check_mime_type (const char *s)
272 {
273   if (ascii_strcasecmp ("text", s) == 0)
274     return TYPETEXT;
275   else if (ascii_strcasecmp ("multipart", s) == 0)
276     return TYPEMULTIPART;
277 #ifdef SUN_ATTACHMENT
278   else if (ascii_strcasecmp ("x-sun-attachment", s) == 0)
279     return TYPEMULTIPART;
280 #endif
281   else if (ascii_strcasecmp ("application", s) == 0)
282     return TYPEAPPLICATION;
283   else if (ascii_strcasecmp ("message", s) == 0)
284     return TYPEMESSAGE;
285   else if (ascii_strcasecmp ("image", s) == 0)
286     return TYPEIMAGE;
287   else if (ascii_strcasecmp ("audio", s) == 0)
288     return TYPEAUDIO;
289   else if (ascii_strcasecmp ("video", s) == 0)
290     return TYPEVIDEO;
291   else if (ascii_strcasecmp ("model", s) == 0)
292     return TYPEMODEL;
293   else if (ascii_strcasecmp ("*", s) == 0)
294     return TYPEANY;
295   else if (ascii_strcasecmp (".*", s) == 0)
296     return TYPEANY;
297   else
298     return TYPEOTHER;
299 }
300
301 void mutt_parse_content_type (char *s, BODY * ct)
302 {
303   char *pc;
304   char *subtype;
305
306   p_delete(&ct->subtype);
307   mutt_free_parameter (&ct->parameter);
308
309   /* First extract any existing parameters */
310   if ((pc = strchr (s, ';')) != NULL) {
311     *pc++ = 0;
312     while (*pc && ISSPACE (*pc))
313       pc++;
314     ct->parameter = parse_parameters (pc);
315
316     /* Some pre-RFC1521 gateways still use the "name=filename" convention,
317      * but if a filename has already been set in the content-disposition,
318      * let that take precedence, and don't set it here */
319     if ((pc = mutt_get_parameter ("name", ct->parameter)) != 0
320         && !ct->filename)
321       ct->filename = m_strdup(pc);
322
323 #ifdef SUN_ATTACHMENT
324     /* this is deep and utter perversion */
325     if ((pc = mutt_get_parameter ("conversions", ct->parameter)) != 0)
326       ct->encoding = mutt_check_encoding (pc);
327 #endif
328
329   }
330
331   /* Now get the subtype */
332   if ((subtype = strchr (s, '/'))) {
333     *subtype++ = '\0';
334     for (pc = subtype; *pc && !ISSPACE (*pc) && *pc != ';'; pc++);
335     *pc = '\0';
336     ct->subtype = m_strdup(subtype);
337   }
338
339   /* Finally, get the major type */
340   ct->type = mutt_check_mime_type (s);
341
342 #ifdef SUN_ATTACHMENT
343   if (ascii_strcasecmp ("x-sun-attachment", s) == 0)
344     ct->subtype = m_strdup("x-sun-attachment");
345 #endif
346
347   if (ct->type == TYPEOTHER) {
348     ct->xtype = m_strdup(s);
349   }
350
351   if (ct->subtype == NULL) {
352     /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type
353      * field, so we can attempt to convert the type to BODY here.
354      */
355     if (ct->type == TYPETEXT)
356       ct->subtype = m_strdup("plain");
357     else if (ct->type == TYPEAUDIO)
358       ct->subtype = m_strdup("basic");
359     else if (ct->type == TYPEMESSAGE)
360       ct->subtype = m_strdup("rfc822");
361     else if (ct->type == TYPEOTHER) {
362       char buffer[SHORT_STRING];
363
364       ct->type = TYPEAPPLICATION;
365       snprintf (buffer, sizeof (buffer), "x-%s", s);
366       ct->subtype = m_strdup(buffer);
367     }
368     else
369       ct->subtype = m_strdup("x-unknown");
370   }
371
372   /* Default character set for text types. */
373   if (ct->type == TYPETEXT) {
374     if (!(pc = mutt_get_parameter ("charset", ct->parameter)))
375       mutt_set_parameter ("charset", option (OPTSTRICTMIME) ? "us-ascii" :
376                           (const char *)
377                           mutt_get_first_charset (AssumedCharset),
378                           &ct->parameter);
379   }
380
381 }
382
383 static void parse_content_disposition (char *s, BODY * ct)
384 {
385   PARAMETER *parms;
386
387   if (!ascii_strncasecmp ("inline", s, 6))
388     ct->disposition = DISPINLINE;
389   else if (!ascii_strncasecmp ("form-data", s, 9))
390     ct->disposition = DISPFORMDATA;
391   else
392     ct->disposition = DISPATTACH;
393
394   /* Check to see if a default filename was given */
395   if ((s = strchr (s, ';')) != NULL) {
396     s = vskipspaces(s + 1);
397     if ((s = mutt_get_parameter("filename",
398                                 (parms = parse_parameters (s)))) != 0)
399       str_replace (&ct->filename, s);
400     if ((s = mutt_get_parameter ("name", parms)) != 0)
401       ct->form_name = m_strdup(s);
402     mutt_free_parameter (&parms);
403   }
404 }
405
406 /* args:
407  *      fp      stream to read from
408  *
409  *      digest  1 if reading subparts of a multipart/digest, 0
410  *              otherwise
411  */
412
413 BODY *mutt_read_mime_header (FILE * fp, int digest)
414 {
415   BODY *p = mutt_new_body ();
416   char *c;
417   char *line = p_new(char, LONG_STRING);
418   size_t linelen = LONG_STRING;
419
420   p->hdr_offset = ftello (fp);
421
422   p->encoding = ENC7BIT;        /* default from RFC1521 */
423   p->type = digest ? TYPEMESSAGE : TYPETEXT;
424   p->disposition = DISPINLINE;
425
426   while (*(line = mutt_read_rfc822_line (fp, line, &linelen)) != 0) {
427     /* Find the value of the current header */
428     if ((c = strchr (line, ':'))) {
429       *c++ = 0;
430       c = vskipspaces(c);
431       if (!*c) {
432         debug_print (1, ("skipping empty header field: %s\n", line));
433         continue;
434       }
435     }
436     else {
437       debug_print (1, ("bogus MIME header: %s\n", line));
438       break;
439     }
440
441     if (!ascii_strncasecmp ("content-", line, 8)) {
442       if (!ascii_strcasecmp ("type", line + 8))
443         mutt_parse_content_type (c, p);
444       else if (!ascii_strcasecmp ("transfer-encoding", line + 8))
445         p->encoding = mutt_check_encoding (c);
446       else if (!ascii_strcasecmp ("disposition", line + 8))
447         parse_content_disposition (c, p);
448       else if (!ascii_strcasecmp ("description", line + 8)) {
449         str_replace (&p->description, c);
450         rfc2047_decode (&p->description);
451       }
452     }
453 #ifdef SUN_ATTACHMENT
454     else if (!ascii_strncasecmp ("x-sun-", line, 6)) {
455       if (!ascii_strcasecmp ("data-type", line + 6))
456         mutt_parse_content_type (c, p);
457       else if (!ascii_strcasecmp ("encoding-info", line + 6))
458         p->encoding = mutt_check_encoding (c);
459       else if (!ascii_strcasecmp ("content-lines", line + 6))
460         mutt_set_parameter ("content-lines", c, &(p->parameter));
461       else if (!ascii_strcasecmp ("data-description", line + 6)) {
462         str_replace (&p->description, c);
463         rfc2047_decode (&p->description);
464       }
465     }
466 #endif
467   }
468   p->offset = ftello (fp);       /* Mark the start of the real data */
469   if (p->type == TYPETEXT && !p->subtype)
470     p->subtype = m_strdup("plain");
471   else if (p->type == TYPEMESSAGE && !p->subtype)
472     p->subtype = m_strdup("rfc822");
473
474   p_delete(&line);
475
476   return (p);
477 }
478
479 void mutt_parse_part (FILE * fp, BODY * b)
480 {
481   char *bound = 0;
482
483   switch (b->type) {
484   case TYPEMULTIPART:
485 #ifdef SUN_ATTACHMENT
486     if (!ascii_strcasecmp (b->subtype, "x-sun-attachment"))
487       bound = "--------";
488     else
489 #endif
490       bound = mutt_get_parameter ("boundary", b->parameter);
491
492     fseeko (fp, b->offset, SEEK_SET);
493     b->parts = mutt_parse_multipart (fp, bound,
494                                      b->offset + b->length,
495                                      ascii_strcasecmp ("digest",
496                                                        b->subtype) == 0);
497     break;
498
499   case TYPEMESSAGE:
500     if (b->subtype) {
501       fseeko (fp, b->offset, SEEK_SET);
502       if (mutt_is_message_type (b->type, b->subtype))
503         b->parts = mutt_parse_messageRFC822 (fp, b);
504       else if (ascii_strcasecmp (b->subtype, "external-body") == 0)
505         b->parts = mutt_read_mime_header (fp, 0);
506       else
507         return;
508     }
509     break;
510
511   default:
512     return;
513   }
514
515   /* try to recover from parsing error */
516   if (!b->parts) {
517     b->type = TYPETEXT;
518     str_replace (&b->subtype, "plain");
519   }
520 }
521
522 /* parse a MESSAGE/RFC822 body
523  *
524  * args:
525  *      fp              stream to read from
526  *
527  *      parent          structure which contains info about the message/rfc822
528  *                      body part
529  *
530  * NOTE: this assumes that `parent->length' has been set!
531  */
532
533 BODY *mutt_parse_messageRFC822 (FILE * fp, BODY * parent)
534 {
535   BODY *msg;
536
537   parent->hdr = mutt_new_header ();
538   parent->hdr->offset = ftello (fp);
539   parent->hdr->env = mutt_read_rfc822_header (fp, parent->hdr, 0, 0);
540   msg = parent->hdr->content;
541
542   /* ignore the length given in the content-length since it could be wrong
543      and we already have the info to calculate the correct length */
544   /* if (msg->length == -1) */
545   msg->length = parent->length - (msg->offset - parent->offset);
546
547   /* if body of this message is empty, we can end up with a negative length */
548   if (msg->length < 0)
549     msg->length = 0;
550
551   mutt_parse_part (fp, msg);
552   return (msg);
553 }
554
555 /* parse a multipart structure
556  *
557  * args:
558  *      fp              stream to read from
559  *
560  *      boundary        body separator
561  *
562  *      end_off         length of the multipart body (used when the final
563  *                      boundary is missing to avoid reading too far)
564  *
565  *      digest          1 if reading a multipart/digest, 0 otherwise
566  */
567
568 BODY *mutt_parse_multipart (FILE * fp, const char *boundary, off_t end_off,
569                             int digest)
570 {
571 #ifdef SUN_ATTACHMENT
572   int lines;
573 #endif
574   int blen, len, crlf = 0;
575   char buffer[LONG_STRING];
576   BODY *head = 0, *last = 0, *new = 0;
577   int i;
578   int final = 0;                /* did we see the ending boundary? */
579
580   if (!boundary) {
581     mutt_error _("multipart message has no boundary parameter!");
582
583     return (NULL);
584   }
585
586   blen = m_strlen(boundary);
587   while (ftello (fp) < end_off && fgets (buffer, LONG_STRING, fp) != NULL) {
588     len = m_strlen(buffer);
589
590     crlf = (len > 1 && buffer[len - 2] == '\r') ? 1 : 0;
591
592     if (buffer[0] == '-' && buffer[1] == '-' &&
593         m_strncmp(buffer + 2, boundary, blen) == 0) {
594       if (last) {
595         last->length = ftello (fp) - last->offset - len - 1 - crlf;
596         if (last->parts && last->parts->length == 0)
597           last->parts->length =
598             ftello (fp) - last->parts->offset - len - 1 - crlf;
599         /* if the body is empty, we can end up with a -1 length */
600         if (last->length < 0)
601           last->length = 0;
602       }
603
604       /* Remove any trailing whitespace, up to the length of the boundary */
605       for (i = len - 1; ISSPACE (buffer[i]) && i >= blen + 2; i--)
606         buffer[i] = 0;
607
608       /* Check for the end boundary */
609       if (m_strcmp(buffer + blen + 2, "--") == 0) {
610         final = 1;
611         break;                  /* done parsing */
612       }
613       else if (buffer[2 + blen] == 0) {
614         new = mutt_read_mime_header (fp, digest);
615
616 #ifdef SUN_ATTACHMENT
617         if (mutt_get_parameter ("content-lines", new->parameter)) {
618           for (lines =
619                atoi (mutt_get_parameter ("content-lines", new->parameter));
620                lines; lines--)
621             if (ftello (fp) >= end_off
622                 || fgets (buffer, LONG_STRING, fp) == NULL)
623               break;
624         }
625 #endif
626
627         /*
628          * Consistency checking - catch
629          * bad attachment end boundaries
630          */
631
632         if (new->offset > end_off) {
633           mutt_free_body (&new);
634           break;
635         }
636         if (head) {
637           last->next = new;
638           last = new;
639         }
640         else
641           last = head = new;
642       }
643     }
644   }
645
646   /* in case of missing end boundary, set the length to something reasonable */
647   if (last && last->length == 0 && !final)
648     last->length = end_off - last->offset;
649
650   /* parse recursive MIME parts */
651   for (last = head; last; last = last->next)
652     mutt_parse_part (fp, last);
653
654   return (head);
655 }
656
657 static const char *uncomment_timezone (char *buf, size_t buflen,
658                                        const char *tz)
659 {
660   char *p;
661   size_t len;
662
663   if (*tz != '(')
664     return tz;                  /* no need to do anything */
665   tz = vskipspaces(tz + 1);
666   if ((p = strpbrk (tz, " )")) == NULL)
667     return tz;
668   len = p - tz;
669   if (len > buflen - 1)
670     len = buflen - 1;
671   memcpy (buf, tz, len);
672   buf[len] = 0;
673   return buf;
674 }
675
676 static struct tz_t {
677   char tzname[5];
678   unsigned char zhours;
679   unsigned char zminutes;
680   unsigned char zoccident;      /* west of UTC? */
681 } TimeZones[] = {
682   {
683   "aat", 1, 0, 1},              /* Atlantic Africa Time */
684   {
685   "adt", 4, 0, 0},              /* Arabia DST */
686   {
687   "ast", 3, 0, 0},              /* Arabia */
688     /*{ "ast",   4,  0, 1 }, *//* Atlantic */
689   {
690   "bst", 1, 0, 0},              /* British DST */
691   {
692   "cat", 1, 0, 0},              /* Central Africa */
693   {
694   "cdt", 5, 0, 1}, {
695   "cest", 2, 0, 0},             /* Central Europe DST */
696   {
697   "cet", 1, 0, 0},              /* Central Europe */
698   {
699   "cst", 6, 0, 1},
700     /*{ "cst",   8,  0, 0 }, *//* China */
701     /*{ "cst",   9, 30, 0 }, *//* Australian Central Standard Time */
702   {
703   "eat", 3, 0, 0},              /* East Africa */
704   {
705   "edt", 4, 0, 1}, {
706   "eest", 3, 0, 0},             /* Eastern Europe DST */
707   {
708   "eet", 2, 0, 0},              /* Eastern Europe */
709   {
710   "egst", 0, 0, 0},             /* Eastern Greenland DST */
711   {
712   "egt", 1, 0, 1},              /* Eastern Greenland */
713   {
714   "est", 5, 0, 1}, {
715   "gmt", 0, 0, 0}, {
716   "gst", 4, 0, 0},              /* Presian Gulf */
717   {
718   "hkt", 8, 0, 0},              /* Hong Kong */
719   {
720   "ict", 7, 0, 0},              /* Indochina */
721   {
722   "idt", 3, 0, 0},              /* Israel DST */
723   {
724   "ist", 2, 0, 0},              /* Israel */
725     /*{ "ist",   5, 30, 0 }, *//* India */
726   {
727   "jst", 9, 0, 0},              /* Japan */
728   {
729   "kst", 9, 0, 0},              /* Korea */
730   {
731   "mdt", 6, 0, 1}, {
732   "met", 1, 0, 0},              /* this is now officially CET */
733   {
734   "msd", 4, 0, 0},              /* Moscow DST */
735   {
736   "msk", 3, 0, 0},              /* Moscow */
737   {
738   "mst", 7, 0, 1}, {
739   "nzdt", 13, 0, 0},            /* New Zealand DST */
740   {
741   "nzst", 12, 0, 0},            /* New Zealand */
742   {
743   "pdt", 7, 0, 1}, {
744   "pst", 8, 0, 1}, {
745   "sat", 2, 0, 0},              /* South Africa */
746   {
747   "smt", 4, 0, 0},              /* Seychelles */
748   {
749   "sst", 11, 0, 1},             /* Samoa */
750     /*{ "sst",   8,  0, 0 }, *//* Singapore */
751   {
752   "utc", 0, 0, 0}, {
753   "wat", 0, 0, 0},              /* West Africa */
754   {
755   "west", 1, 0, 0},             /* Western Europe DST */
756   {
757   "wet", 0, 0, 0},              /* Western Europe */
758   {
759   "wgst", 2, 0, 1},             /* Western Greenland DST */
760   {
761   "wgt", 3, 0, 1},              /* Western Greenland */
762   {
763   "wst", 8, 0, 0},              /* Western Australia */
764 };
765
766 /* parses a date string in RFC822 format:
767  *
768  * Date: [ weekday , ] day-of-month month year hour:minute:second timezone
769  *
770  * This routine assumes that `h' has been initialized to 0.  the `timezone'
771  * field is optional, defaulting to +0000 if missing.
772  */
773 time_t mutt_parse_date (const char *s, HEADER * h)
774 {
775   int count = 0;
776   char *t;
777   int hour, min, sec;
778   struct tm tm;
779   int i;
780   int tz_offset = 0;
781   int zhours = 0;
782   int zminutes = 0;
783   int zoccident = 0;
784   const char *ptz;
785   char tzstr[SHORT_STRING];
786   char scratch[SHORT_STRING];
787
788   /* Don't modify our argument. Fixed-size buffer is ok here since
789    * the date format imposes a natural limit.
790    */
791
792   m_strcpy(scratch, sizeof(scratch), s);
793
794   /* kill the day of the week, if it exists. */
795   if ((t = strchr (scratch, ',')))
796     t++;
797   else
798     t = scratch;
799   t = vskipspaces(t);
800
801   p_clear(&tm, 1);
802
803   while ((t = strtok (t, " \t")) != NULL) {
804     switch (count) {
805     case 0:                    /* day of the month */
806       if (!isdigit ((unsigned char) *t))
807         return (-1);
808       tm.tm_mday = atoi (t);
809       if (tm.tm_mday > 31)
810         return (-1);
811       break;
812
813     case 1:                    /* month of the year */
814       if ((i = mutt_check_month (t)) < 0)
815         return (-1);
816       tm.tm_mon = i;
817       break;
818
819     case 2:                    /* year */
820       tm.tm_year = atoi (t);
821       if (tm.tm_year < 50)
822         tm.tm_year += 100;
823       else if (tm.tm_year >= 1900)
824         tm.tm_year -= 1900;
825       break;
826
827     case 3:                    /* time of day */
828       if (sscanf (t, "%d:%d:%d", &hour, &min, &sec) == 3);
829       else if (sscanf (t, "%d:%d", &hour, &min) == 2)
830         sec = 0;
831       else {
832         debug_print (1, ("could not process time format: %s\n", t));
833         return (-1);
834       }
835       tm.tm_hour = hour;
836       tm.tm_min = min;
837       tm.tm_sec = sec;
838       break;
839
840     case 4:                    /* timezone */
841       /* sometimes we see things like (MST) or (-0700) so attempt to
842        * compensate by uncommenting the string if non-RFC822 compliant
843        */
844       ptz = uncomment_timezone (tzstr, sizeof (tzstr), t);
845
846       if (*ptz == '+' || *ptz == '-') {
847         if (ptz[1] && ptz[2] && ptz[3] && ptz[4]
848             && isdigit ((unsigned char) ptz[1])
849             && isdigit ((unsigned char) ptz[2])
850             && isdigit ((unsigned char) ptz[3])
851             && isdigit ((unsigned char) ptz[4])) {
852           zhours = (ptz[1] - '0') * 10 + (ptz[2] - '0');
853           zminutes = (ptz[3] - '0') * 10 + (ptz[4] - '0');
854
855           if (ptz[0] == '-')
856             zoccident = 1;
857         }
858       }
859       else {
860         struct tz_t *tz;
861
862         tz = bsearch (ptz, TimeZones, sizeof TimeZones / sizeof (struct tz_t),
863                       sizeof (struct tz_t),
864                       (int (*)(const void *, const void *)) ascii_strcasecmp
865                       /* This is safe to do: A pointer to a struct equals
866                        * a pointer to its first element*/ );
867
868         if (tz) {
869           zhours = tz->zhours;
870           zminutes = tz->zminutes;
871           zoccident = tz->zoccident;
872         }
873
874         /* ad hoc support for the European MET (now officially CET) TZ */
875         if (ascii_strcasecmp (t, "MET") == 0) {
876           if ((t = strtok (NULL, " \t")) != NULL) {
877             if (!ascii_strcasecmp (t, "DST"))
878               zhours++;
879           }
880         }
881       }
882       tz_offset = zhours * 3600 + zminutes * 60;
883       if (!zoccident)
884         tz_offset = -tz_offset;
885       break;
886     }
887     count++;
888     t = 0;
889   }
890
891   if (count < 4) {              /* don't check for missing timezone */
892     debug_print (1, ("error parsing date format, using received time\n"));
893     return (-1);
894   }
895
896   if (h) {
897     h->zhours = zhours;
898     h->zminutes = zminutes;
899     h->zoccident = zoccident;
900   }
901
902   return (mutt_mktime (&tm, 0) + tz_offset);
903 }
904
905 /* extract the first substring that looks like a message-id */
906 static char *extract_message_id(const char *s)
907 {
908     const char *p;
909
910     if ((s = strchr(s, '<')) == NULL || (p = strchr(s, '>')) == NULL)
911         return NULL;
912     return p_dupstr(s, (p - s) + 1);
913 }
914
915 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
916 {
917   MESSAGE *msg;
918   int flags = 0;
919
920   do {
921     if (cur->content->type != TYPEMESSAGE
922         && cur->content->type != TYPEMULTIPART)
923       break;                     /* nothing to do */
924
925     if (cur->content->parts)
926       break;                     /* The message was parsed earlier. */
927
928     if ((msg = mx_open_message (ctx, cur->msgno))) {
929       mutt_parse_part (msg->fp, cur->content);
930
931       if (WithCrypto)
932         cur->security = crypt_query (cur->content);
933
934       mx_close_message (&msg);
935     }
936   } while (0);
937   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
938 }
939
940 int mutt_parse_rfc822_line (ENVELOPE * e, HEADER * hdr, char *line, char *p,
941                             short user_hdrs, short weed, short do_2047,
942                             LIST ** lastp)
943 {
944   int matched = 0;
945   LIST *last = NULL;
946
947   if (lastp)
948     last = *lastp;
949
950   switch (ascii_tolower (line[0])) {
951   case 'a':
952     if (ascii_strcasecmp (line + 1, "pparently-to") == 0) {
953       e->to = rfc822_parse_adrlist (e->to, p);
954       matched = 1;
955     }
956     else if (ascii_strcasecmp (line + 1, "pparently-from") == 0) {
957       e->from = rfc822_parse_adrlist (e->from, p);
958       matched = 1;
959     }
960     break;
961
962   case 'b':
963     if (ascii_strcasecmp (line + 1, "cc") == 0) {
964       e->bcc = rfc822_parse_adrlist (e->bcc, p);
965       matched = 1;
966     }
967     break;
968
969   case 'c':
970     if (ascii_strcasecmp (line + 1, "c") == 0) {
971       e->cc = rfc822_parse_adrlist (e->cc, p);
972       matched = 1;
973     }
974     else if (ascii_strncasecmp (line + 1, "ontent-", 7) == 0) {
975       if (ascii_strcasecmp (line + 8, "type") == 0) {
976         if (hdr)
977           mutt_parse_content_type (p, hdr->content);
978         matched = 1;
979       }
980       else if (ascii_strcasecmp (line + 8, "transfer-encoding") == 0) {
981         if (hdr)
982           hdr->content->encoding = mutt_check_encoding (p);
983         matched = 1;
984       }
985       else if (ascii_strcasecmp (line + 8, "length") == 0) {
986         if (hdr) {
987           if ((hdr->content->length = atoi (p)) < 0)
988             hdr->content->length = -1;
989         }
990         matched = 1;
991       }
992       else if (ascii_strcasecmp (line + 8, "description") == 0) {
993         if (hdr) {
994           str_replace (&hdr->content->description, p);
995           rfc2047_decode (&hdr->content->description);
996         }
997         matched = 1;
998       }
999       else if (ascii_strcasecmp (line + 8, "disposition") == 0) {
1000         if (hdr)
1001           parse_content_disposition (p, hdr->content);
1002         matched = 1;
1003       }
1004     }
1005     break;
1006
1007   case 'd':
1008     if (!ascii_strcasecmp ("ate", line + 1)) {
1009       str_replace (&e->date, p);
1010       if (hdr)
1011         hdr->date_sent = mutt_parse_date (p, hdr);
1012       matched = 1;
1013     }
1014     break;
1015
1016   case 'e':
1017     if (!ascii_strcasecmp ("xpires", line + 1) &&
1018         hdr && mutt_parse_date (p, NULL) < time (NULL))
1019       hdr->expired = 1;
1020     break;
1021
1022   case 'f':
1023     if (!ascii_strcasecmp ("rom", line + 1)) {
1024       e->from = rfc822_parse_adrlist (e->from, p);
1025       /* don't leave from info NULL if there's an invalid address (or
1026        * whatever) in From: field; mutt would just display it as empty
1027        * and mark mail/(esp.) news article as your own. aaargh! this
1028        * bothered me for _years_ */
1029       if (!e->from) {
1030         e->from = rfc822_new_address ();
1031         e->from->personal = m_strdup(p);
1032       }
1033       matched = 1;
1034     }
1035 #ifdef USE_NNTP
1036     else if (!m_strcasecmp(line + 1, "ollowup-to")) {
1037       if (!e->followup_to) {
1038         str_skip_trailws (p);
1039         e->followup_to = m_strdup(vskipspaces(p));
1040       }
1041       matched = 1;
1042     }
1043 #endif
1044     break;
1045
1046   case 'i':
1047     if (!ascii_strcasecmp (line + 1, "n-reply-to")) {
1048       mutt_free_list (&e->in_reply_to);
1049       e->in_reply_to = mutt_parse_references (p, 1);
1050       matched = 1;
1051     }
1052     break;
1053
1054   case 'l':
1055     if (!ascii_strcasecmp (line + 1, "ines")) {
1056       if (hdr) {
1057         hdr->lines = atoi (p);
1058
1059         /*
1060          * HACK - mutt has, for a very short time, produced negative
1061          * Lines header values.  Ignore them.
1062          */
1063         if (hdr->lines < 0)
1064           hdr->lines = 0;
1065       }
1066
1067       matched = 1;
1068     }
1069     else if (!ascii_strcasecmp (line + 1, "ist-Post")) {
1070       /* RFC 2369.  FIXME: We should ignore whitespace, but don't. */
1071       if (strncmp (p, "NO", 2)) {
1072         char *beg, *end;
1073
1074         for (beg = strchr (p, '<'); beg; beg = strchr (end, ',')) {
1075           ++beg;
1076           if (!(end = strchr (beg, '>')))
1077             break;
1078
1079           /* Take the first mailto URL */
1080           if (url_check_scheme (beg) == U_MAILTO) {
1081             p_delete(&e->list_post);
1082             e->list_post = str_substrdup (beg, end);
1083             break;
1084           }
1085         }
1086       }
1087       matched = 1;
1088     }
1089     break;
1090
1091   case 'm':
1092     if (!ascii_strcasecmp (line + 1, "ime-version")) {
1093       if (hdr)
1094         hdr->mime = 1;
1095       matched = 1;
1096     }
1097     else if (!ascii_strcasecmp (line + 1, "essage-id")) {
1098       /* We add a new "Message-ID:" when building a message */
1099       p_delete(&e->message_id);
1100       e->message_id = extract_message_id (p);
1101       matched = 1;
1102     }
1103     else if (!ascii_strncasecmp (line + 1, "ail-", 4)) {
1104       if (!ascii_strcasecmp (line + 5, "reply-to")) {
1105         /* override the Reply-To: field */
1106         rfc822_free_address (&e->reply_to);
1107         e->reply_to = rfc822_parse_adrlist (e->reply_to, p);
1108         matched = 1;
1109       }
1110       else if (!ascii_strcasecmp (line + 5, "followup-to")) {
1111         e->mail_followup_to = rfc822_parse_adrlist (e->mail_followup_to, p);
1112         matched = 1;
1113       }
1114     }
1115     break;
1116
1117 #ifdef USE_NNTP
1118   case 'n':
1119     if (!m_strcasecmp(line + 1, "ewsgroups")) {
1120       p_delete(&e->newsgroups);
1121       str_skip_trailws (p);
1122       e->newsgroups = m_strdup(vskipspaces(p));
1123       matched = 1;
1124     }
1125     break;
1126 #endif
1127
1128   case 'o':
1129     /* field `Organization:' saves only for pager! */
1130     if (!m_strcasecmp(line + 1, "rganization")) {
1131       if (!e->organization && m_strcasecmp(p, "unknown"))
1132         e->organization = m_strdup(p);
1133     }
1134     break;
1135
1136   case 'r':
1137     if (!ascii_strcasecmp (line + 1, "eferences")) {
1138       mutt_free_list (&e->references);
1139       e->references = mutt_parse_references (p, 0);
1140       matched = 1;
1141     }
1142     else if (!ascii_strcasecmp (line + 1, "eply-to")) {
1143       e->reply_to = rfc822_parse_adrlist (e->reply_to, p);
1144       matched = 1;
1145     }
1146     else if (!ascii_strcasecmp (line + 1, "eturn-path")) {
1147       e->return_path = rfc822_parse_adrlist (e->return_path, p);
1148       matched = 1;
1149     }
1150     else if (!ascii_strcasecmp (line + 1, "eceived")) {
1151       if (hdr && !hdr->received) {
1152         char *d = strchr (p, ';');
1153
1154         if (d)
1155           hdr->received = mutt_parse_date (d + 1, NULL);
1156       }
1157     }
1158     break;
1159
1160   case 's':
1161     if (!ascii_strcasecmp (line + 1, "ubject")) {
1162       if (!e->subject)
1163         e->subject = m_strdup(p);
1164       matched = 1;
1165     }
1166     else if (!ascii_strcasecmp (line + 1, "ender")) {
1167       e->sender = rfc822_parse_adrlist (e->sender, p);
1168       matched = 1;
1169     }
1170     else if (!ascii_strcasecmp (line + 1, "tatus")) {
1171       if (hdr) {
1172         while (*p) {
1173           switch (*p) {
1174           case 'r':
1175             hdr->replied = 1;
1176             break;
1177           case 'O':
1178             hdr->old = 1;
1179             break;
1180           case 'R':
1181             hdr->read = 1;
1182             break;
1183           }
1184           p++;
1185         }
1186       }
1187       matched = 1;
1188     }
1189     else if ((!ascii_strcasecmp ("upersedes", line + 1) ||
1190               !ascii_strcasecmp ("upercedes", line + 1)) && hdr)
1191       e->supersedes = m_strdup(p);
1192     break;
1193
1194   case 't':
1195     if (ascii_strcasecmp (line + 1, "o") == 0) {
1196       e->to = rfc822_parse_adrlist (e->to, p);
1197       matched = 1;
1198     }
1199     break;
1200
1201   case 'x':
1202     if (ascii_strcasecmp (line + 1, "-status") == 0) {
1203       if (hdr) {
1204         while (*p) {
1205           switch (*p) {
1206           case 'A':
1207             hdr->replied = 1;
1208             break;
1209           case 'D':
1210             hdr->deleted = 1;
1211             break;
1212           case 'F':
1213             hdr->flagged = 1;
1214             break;
1215           default:
1216             break;
1217           }
1218           p++;
1219         }
1220       }
1221       matched = 1;
1222     }
1223     else if (ascii_strcasecmp (line + 1, "-label") == 0) {
1224       e->x_label = m_strdup(p);
1225       matched = 1;
1226     }
1227 #ifdef USE_NNTP
1228     else if (!m_strcasecmp(line + 1, "-comment-to")) {
1229       if (!e->x_comment_to)
1230         e->x_comment_to = m_strdup(p);
1231       matched = 1;
1232     }
1233     else if (!m_strcasecmp(line + 1, "ref")) {
1234       if (!e->xref)
1235         e->xref = m_strdup(p);
1236       matched = 1;
1237     }
1238 #endif
1239
1240   default:
1241     break;
1242   }
1243
1244   /* Keep track of the user-defined headers */
1245   if (!matched && user_hdrs) {
1246     /* restore the original line */
1247     line[m_strlen(line)] = ':';
1248
1249     if (weed && option (OPTWEED) && mutt_matches_ignore (line, Ignore)
1250         && !mutt_matches_ignore (line, UnIgnore))
1251       goto done;
1252
1253     if (last) {
1254       last->next = mutt_new_list ();
1255       last = last->next;
1256     }
1257     else
1258       last = e->userhdrs = mutt_new_list ();
1259     last->data = m_strdup(line);
1260     if (do_2047)
1261       rfc2047_decode (&last->data);
1262   }
1263
1264 done:
1265
1266   *lastp = last;
1267   return matched;
1268 }
1269
1270
1271 /* mutt_read_rfc822_header() -- parses a RFC822 header
1272  *
1273  * Args:
1274  *
1275  * f            stream to read from
1276  *
1277  * hdr          header structure of current message (optional).
1278  *
1279  * user_hdrs    If set, store user headers.  Used for recall-message and
1280  *              postpone modes.
1281  *
1282  * weed         If this parameter is set and the user has activated the
1283  *              $weed option, honor the header weed list for user headers.
1284  *              Used for recall-message.
1285  *
1286  * Returns:     newly allocated envelope structure.  You should free it by
1287  *              mutt_free_envelope() when envelope stay unneeded.
1288  */
1289 ENVELOPE *mutt_read_rfc822_header (FILE * f, HEADER * hdr, short user_hdrs,
1290                                    short weed)
1291 {
1292   ENVELOPE *e = mutt_new_envelope ();
1293   LIST *last = NULL;
1294   char *line = p_new(char, LONG_STRING);
1295   char *p;
1296   off_t loc;
1297   int matched;
1298   size_t linelen = LONG_STRING;
1299   char buf[LONG_STRING + 1];
1300
1301   if (hdr) {
1302     if (hdr->content == NULL) {
1303       hdr->content = mutt_new_body ();
1304
1305       /* set the defaults from RFC1521 */
1306       hdr->content->type = TYPETEXT;
1307       hdr->content->subtype = m_strdup("plain");
1308       hdr->content->encoding = ENC7BIT;
1309       hdr->content->length = -1;
1310
1311       /* RFC 2183 says this is arbitrary */
1312       hdr->content->disposition = DISPINLINE;
1313     }
1314   }
1315
1316   while ((loc = ftello (f)),
1317          *(line = mutt_read_rfc822_line (f, line, &linelen)) != 0) {
1318     matched = 0;
1319
1320     if ((p = strpbrk (line, ": \t")) == NULL || *p != ':') {
1321       char return_path[LONG_STRING];
1322       time_t t;
1323
1324       /* some bogus MTAs will quote the original "From " line */
1325       if (m_strncmp(">From ", line, 6) == 0)
1326         continue;               /* just ignore */
1327       else if (is_from (line, return_path, sizeof (return_path), &t)) {
1328         /* MH somtimes has the From_ line in the middle of the header! */
1329         if (hdr && !hdr->received)
1330           hdr->received = t - mutt_local_tz (t);
1331         continue;
1332       }
1333
1334       fseeko (f, loc, 0);
1335       break;                    /* end of header */
1336     }
1337
1338     *buf = '\0';
1339
1340     if (mutt_match_spam_list (line, SpamList, buf, sizeof (buf))) {
1341       if (!rx_list_match (NoSpamList, line)) {
1342
1343         /* if spam tag already exists, figure out how to amend it */
1344         if (e->spam && *buf) {
1345           /* If SpamSep defined, append with separator */
1346           if (SpamSep) {
1347             mutt_buffer_addstr (e->spam, SpamSep);
1348             mutt_buffer_addstr (e->spam, buf);
1349           }
1350
1351           /* else overwrite */
1352           else {
1353             e->spam->dptr = e->spam->data;
1354             *e->spam->dptr = '\0';
1355             mutt_buffer_addstr (e->spam, buf);
1356           }
1357         }
1358
1359         /* spam tag is new, and match expr is non-empty; copy */
1360         else if (!e->spam && *buf) {
1361           e->spam = mutt_buffer_from (NULL, buf);
1362         }
1363
1364         /* match expr is empty; plug in null string if no existing tag */
1365         else if (!e->spam) {
1366           e->spam = mutt_buffer_from (NULL, "");
1367         }
1368
1369         if (e->spam && e->spam->data)
1370           debug_print (5, ("spam = %s\n", e->spam->data));
1371       }
1372     }
1373
1374     *p++ = 0;
1375     p = vskipspaces(p);
1376     if (!*p)
1377       continue;                 /* skip empty header fields */
1378
1379     matched =
1380       mutt_parse_rfc822_line (e, hdr, line, p, user_hdrs, weed, 1, &last);
1381
1382   }
1383
1384   p_delete(&line);
1385
1386   if (hdr) {
1387     hdr->content->hdr_offset = hdr->offset;
1388     hdr->content->offset = ftello (f);
1389     rfc2047_decode_envelope (e);
1390     /* check for missing or invalid date */
1391     if (hdr->date_sent <= 0) {
1392       debug_print (1, ("no date found, using received "
1393                        "time from msg separator\n"));
1394       hdr->date_sent = hdr->received;
1395     }
1396   }
1397
1398   return (e);
1399 }
1400
1401 ADDRESS *mutt_parse_adrlist (ADDRESS * p, const char *s)
1402 {
1403   const char *q;
1404
1405   /* check for a simple whitespace separated list of addresses */
1406   if ((q = strpbrk (s, "\"<>():;,\\")) == NULL) {
1407     char tmp[HUGE_STRING];
1408     char *r;
1409
1410     m_strcpy(tmp, sizeof(tmp), s);
1411     r = tmp;
1412     while ((r = strtok (r, " \t")) != NULL) {
1413       p = rfc822_parse_adrlist (p, r);
1414       r = NULL;
1415     }
1416   }
1417   else
1418     p = rfc822_parse_adrlist (p, s);
1419
1420   return p;
1421 }
1422
1423
1424 /* Compares mime types to the ok and except lists */
1425 int count_body_parts_check(LIST **checklist, BODY *b, int dflt) {
1426   LIST *type;
1427   ATTACH_MATCH *a;
1428
1429   /* If list is null, use default behavior. */
1430   if (! *checklist) {
1431     /*return dflt;*/
1432     return 0;
1433   }
1434
1435   for (type = *checklist; type; type = type->next) {
1436     a = (ATTACH_MATCH *)type->data;
1437     debug_print(5, ("cbpc: %s %d/%s ?? %s/%s [%d]... ",
1438                dflt ? "[OK] " : "[EXCL] ",
1439                b->type, b->subtype, a->major, a->minor, a->major_int));
1440     if ((a->major_int == TYPEANY || a->major_int == b->type) &&
1441         !regexec(&a->minor_rx, b->subtype, 0, NULL, 0)) {
1442       debug_print(5, ("yes\n"));
1443       return 1;
1444     } else {
1445       debug_print(5, ("no\n"));
1446     }
1447   }
1448   return 0;
1449 }
1450
1451 #define AT_COUNT(why) { shallcount = 1; }
1452 #define AT_NOCOUNT(why) { shallcount = 0; }
1453
1454 int count_body_parts (BODY *body, int flags) {
1455   int count = 0;
1456   int shallcount, shallrecurse;
1457   BODY *bp;
1458
1459   if (body == NULL)
1460     return 0;
1461
1462   for (bp = body; bp != NULL; bp = bp->next) {
1463     /* Initial disposition is to count and not to recurse this part. */
1464     AT_COUNT("default");
1465     shallrecurse = 0;
1466
1467     debug_print(5, ("bp: desc=\"%s\"; fn=\"%s\", type=\"%d/%s\"\n",
1468                bp->description ? bp->description : ("none"),
1469                bp->filename ? bp->filename :
1470                bp->d_filename ? bp->d_filename : "(none)",
1471                bp->type, bp->subtype ? bp->subtype : "*"));
1472
1473     if (bp->type == TYPEMESSAGE) {
1474       shallrecurse = 1;
1475
1476       /* If it's an external body pointer, don't recurse it. */
1477       if (!ascii_strcasecmp (bp->subtype, "external-body"))
1478         shallrecurse = 0;
1479
1480       /* Don't count containers if they're top-level. */
1481       if (flags & M_PARTS_TOPLEVEL)
1482         AT_NOCOUNT("top-level message/*");
1483     } else if (bp->type == TYPEMULTIPART) {
1484       /* Always recurse multiparts, except multipart/alternative. */
1485       shallrecurse = 1;
1486       if (!m_strcasecmp(bp->subtype, "alternative"))
1487         shallrecurse = 0;
1488
1489       /* Don't count containers if they're top-level. */
1490       if (flags & M_PARTS_TOPLEVEL)
1491         AT_NOCOUNT("top-level multipart");
1492     }
1493
1494     if (bp->disposition == DISPINLINE &&
1495         bp->type != TYPEMULTIPART && bp->type != TYPEMESSAGE && bp == body)
1496       AT_NOCOUNT("ignore fundamental inlines");
1497
1498     /* If this body isn't scheduled for enumeration already, don't bother
1499      * profiling it further. */
1500
1501     if (shallcount) {
1502       /* Turn off shallcount if message type is not in ok list,
1503        * or if it is in except list. Check is done separately for
1504        * inlines vs. attachments.
1505        */
1506
1507       if (bp->disposition == DISPATTACH) {
1508         if (!count_body_parts_check(&AttachAllow, bp, 1))
1509           AT_NOCOUNT("attach not allowed");
1510         if (count_body_parts_check(&AttachExclude, bp, 0))
1511           AT_NOCOUNT("attach excluded");
1512       } else {
1513         if (!count_body_parts_check(&InlineAllow, bp, 1))
1514           AT_NOCOUNT("inline not allowed");
1515         if (count_body_parts_check(&InlineExclude, bp, 0))
1516           AT_NOCOUNT("excluded");
1517       }
1518     }
1519
1520     if (shallcount)
1521       count++;
1522     bp->attach_qualifies = shallcount ? 1 : 0;
1523
1524     debug_print(5, ("cbp: %p shallcount = %d\n", bp, shallcount));
1525
1526     if (shallrecurse) {
1527       debug_print(5, ("cbp: %p pre count = %d\n", bp, count));
1528       bp->attach_count = count_body_parts(bp->parts, flags & ~M_PARTS_TOPLEVEL);
1529       count += bp->attach_count;
1530       debug_print(5, ("cbp: %p post count = %d\n", bp, count));
1531     }
1532   }
1533
1534   debug_print(5, ("bp: return %d\n", count < 0 ? 0 : count));
1535   return count < 0 ? 0 : count;
1536 }
1537
1538 int mutt_count_body_parts (HEADER *hdr, int flags) {
1539   if (!option (OPTCOUNTATTACH))
1540     return (0);
1541   if (hdr->attach_valid && !(flags & M_PARTS_RECOUNT))
1542     return hdr->attach_total;
1543
1544   if (AttachAllow || AttachExclude || InlineAllow || InlineExclude)
1545     hdr->attach_total = count_body_parts(hdr->content, flags | M_PARTS_TOPLEVEL);
1546   else
1547     hdr->attach_total = 0;
1548
1549   hdr->attach_valid = 1;
1550   return hdr->attach_total;
1551 }