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