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