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