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