Add minimalistic protected-headers support
[gpgol.git] / src / rfc822parse.c
1 /* rfc822parse.c - Simple mail and MIME parser
2  * Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
3  * Copyright (C) 2003, 2004 g10 Code GmbH
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public License
7  * as published by the Free Software Foundation; either version 2.1 of
8  * the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 /* According to RFC822 binary zeroes are allowed at many places. We do
21  * not handle this correct especially in the field parsing code.  It
22  * should be easy to fix and the API provides a interfaces which
23  * returns the length but in addition makes sure that returned strings
24  * are always ended by a \0.
25  *
26  * Furthermore, the case of field names is changed and thus it is not
27  * always a good idea to use these modified header
28  * lines (e.g. signatures may break).
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
34
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <stdarg.h>
40 #include <assert.h>
41
42 #include "common_indep.h"
43 #include "rfc822parse.h"
44
45 enum token_type
46   {
47     tSPACE,
48     tATOM,
49     tQUOTED,
50     tDOMAINLIT,
51     tSPECIAL
52   };
53
54 /* For now we directly use our TOKEN as the parse context */
55 typedef struct rfc822parse_field_context *TOKEN;
56 struct rfc822parse_field_context
57 {
58   TOKEN next;
59   enum token_type type;
60   struct {
61     unsigned int cont:1;
62     unsigned int lowered:1;
63   } flags;
64   /*TOKEN owner_pantry; */
65   char data[1];
66 };
67
68 struct hdr_line
69 {
70   struct hdr_line *next;
71   int cont;     /* This is a continuation of the previous line. */
72   unsigned char line[1];
73 };
74
75 typedef struct hdr_line *HDR_LINE;
76
77
78 struct part
79 {
80   struct part *right;     /* The next part. */
81   struct part *down;      /* A contained part. */
82   HDR_LINE hdr_lines;       /* Header lines os that part. */
83   HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */
84   char *boundary;           /* Only used in the first part. */
85 };
86 typedef struct part *part_t;
87
88 struct rfc822parse_context
89 {
90   rfc822parse_cb_t callback;
91   void *callback_value;
92   int callback_error;
93   int in_body;
94   int in_preamble;      /* Wether we are before the first boundary. */
95   part_t parts;         /* The tree of parts. */
96   part_t current_part;  /* Whom we are processing (points into parts). */
97   const char *boundary; /* Current boundary. */
98 };
99
100 static HDR_LINE find_header (rfc822parse_t msg, const char *name,
101                              int which, HDR_LINE * rprev);
102
103
104 static size_t
105 length_sans_trailing_ws (const unsigned char *line, size_t len)
106 {
107   const unsigned char *p, *mark;
108   size_t n;
109
110   for (mark=NULL, p=line, n=0; n < len; n++, p++)
111     {
112       if (strchr (" \t\r\n", *p ))
113         {
114           if( !mark )
115             mark = p;
116         }
117       else
118         mark = NULL;
119     }
120
121   if (mark)
122     return mark - line;
123   return len;
124 }
125
126
127 static void
128 lowercase_string (unsigned char *string)
129 {
130   for (; *string; string++)
131     if (*string >= 'A' && *string <= 'Z')
132       *string = *string - 'A' + 'a';
133 }
134
135 /* Transform a header name into a standard capitalized format; i.e
136    "Content-Type".  Conversion stops at the colon.  As usual we don't
137    use the localized versions of ctype.h.
138  */
139 static void
140 capitalize_header_name (unsigned char *name)
141 {
142   int first = 1;
143
144   for (; *name && *name != ':'; name++)
145     if (*name == '-')
146       first = 1;
147     else if (first)
148       {
149         if (*name >= 'a' && *name <= 'z')
150           *name = *name - 'a' + 'A';
151         first = 0;
152       }
153     else if (*name >= 'A' && *name <= 'Z')
154       *name = *name - 'A' + 'a';
155 }
156
157
158 static char *
159 my_stpcpy (char *a,const char *b)
160 {
161   while (*b)
162     *a++ = *b++;
163   *a = 0;
164
165   return (char*)a;
166 }
167
168 /* If a callback has been registerd, call it for the event of type
169    EVENT. */
170 static int
171 do_callback (rfc822parse_t msg, rfc822parse_event_t event)
172 {
173   int rc;
174
175   if (!msg->callback || msg->callback_error)
176     return 0;
177   rc = msg->callback (msg->callback_value, event, msg);
178   if (rc)
179     msg->callback_error = rc;
180   return rc;
181 }
182
183 static part_t
184 new_part (void)
185 {
186   part_t part;
187
188   part = xcalloc (1, sizeof *part);
189   if (part)
190     {
191       part->hdr_lines_tail = &part->hdr_lines;
192     }
193   return part;
194 }
195
196
197 static void
198 release_part (part_t part)
199 {
200   part_t tmp;
201   HDR_LINE hdr, hdr2;
202
203   for (; part; part = tmp)
204     {
205       tmp = part->right;
206       if (part->down)
207         release_part (part->down);
208       for (hdr = part->hdr_lines; hdr; hdr = hdr2)
209         {
210           hdr2 = hdr->next;
211           xfree (hdr);
212         }
213       xfree (part->boundary);
214       xfree (part);
215     }
216 }
217
218
219 static void
220 release_handle_data (rfc822parse_t msg)
221 {
222   release_part (msg->parts);
223   msg->parts = NULL;
224   msg->current_part = NULL;
225   msg->boundary = NULL;
226 }
227
228
229 /* Create a new parsing context for an entire rfc822 message and
230    return it.  CB and CB_VALUE may be given to callback for certain
231    events.  NULL is returned on error with errno set appropriately. */
232 rfc822parse_t
233 rfc822parse_open (rfc822parse_cb_t cb, void *cb_value)
234 {
235   rfc822parse_t msg = xcalloc (1, sizeof *msg);
236   if (msg)
237     {
238       msg->parts = msg->current_part = new_part ();
239       if (!msg->parts)
240         {
241           xfree (msg);
242           msg = NULL;
243         }
244       else
245         {
246           msg->callback = cb;
247           msg->callback_value = cb_value;
248           if (do_callback (msg, RFC822PARSE_OPEN))
249             {
250               release_handle_data (msg);
251               xfree (msg);
252               errno = 0;/* Not meaningful after the callback.  */
253               msg = NULL;
254             }
255         }
256     }
257   return msg;
258 }
259
260
261 void
262 rfc822parse_cancel (rfc822parse_t msg)
263 {
264   if (msg)
265     {
266       do_callback (msg, RFC822PARSE_CANCEL);
267       release_handle_data (msg);
268       xfree (msg);
269     }
270 }
271
272
273 void
274 rfc822parse_close (rfc822parse_t msg)
275 {
276   if (msg)
277     {
278       do_callback (msg, RFC822PARSE_CLOSE);
279       release_handle_data (msg);
280       xfree (msg);
281     }
282 }
283
284 static part_t
285 find_parent (part_t tree, part_t target)
286 {
287   part_t part;
288
289   for (part = tree->down; part; part = part->right)
290     {
291       if (part == target)
292         return tree; /* Found. */
293       if (part->down)
294         {
295           part_t tmp = find_parent (part, target);
296           if (tmp)
297             return tmp;
298         }
299     }
300   return NULL;
301 }
302
303 static void
304 set_current_part_to_parent (rfc822parse_t msg)
305 {
306   part_t parent;
307
308   assert (msg->current_part);
309   parent = find_parent (msg->parts, msg->current_part);
310   if (!parent)
311     return; /* Already at the top. */
312
313 #ifndef NDEBUG
314   {
315     part_t part;
316     for (part = parent->down; part; part = part->right)
317       if (part == msg->current_part)
318         break;
319     assert (part);
320   }
321 #endif
322   msg->current_part = parent;
323
324   parent = find_parent (msg->parts, parent);
325   msg->boundary = parent? parent->boundary: NULL;
326 }
327
328
329
330 /****************
331  * We have read in all header lines and are about to receive the body
332  * part.  The delimiter line has already been processed.
333  *
334  * FIXME: we's better return an error in case of memory failures.
335  */
336 static int
337 transition_to_body (rfc822parse_t msg)
338 {
339   rfc822parse_field_t ctx;
340   int rc;
341
342   rc = do_callback (msg, RFC822PARSE_T2BODY);
343   if (!rc)
344     {
345       /* Store the boundary if we have multipart type. */
346       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
347       if (ctx)
348         {
349           const char *s;
350
351           s = rfc822parse_query_media_type (ctx, NULL);
352           if (s && !strcmp (s,"multipart"))
353             {
354               s = rfc822parse_query_parameter (ctx, "boundary", 0);
355               if (s)
356                 {
357                   assert (!msg->current_part->boundary);
358                   msg->current_part->boundary = xmalloc (strlen (s) + 1);
359                   if (msg->current_part->boundary)
360                     {
361                       part_t part;
362
363                       strcpy (msg->current_part->boundary, s);
364                       msg->boundary = msg->current_part->boundary;
365                       part = new_part ();
366                       if (!part)
367                         {
368                           int save_errno = errno;
369                           rfc822parse_release_field (ctx);
370                           errno = save_errno;
371                           return -1;
372                         }
373                       rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN);
374                       assert (!msg->current_part->down);
375                       msg->current_part->down = part;
376                       msg->current_part = part;
377                       msg->in_preamble = 1;
378                     }
379                 }
380             }
381           rfc822parse_release_field (ctx);
382         }
383     }
384
385   return rc;
386 }
387
388 /* We have just passed a MIME boundary and need to prepare for new part.
389    headers. */
390 static int
391 transition_to_header (rfc822parse_t msg)
392 {
393   part_t part;
394
395   assert (msg->current_part);
396   assert (!msg->current_part->right);
397
398   part = new_part ();
399   if (!part)
400     return -1;
401
402   msg->current_part->right = part;
403   msg->current_part = part;
404   return 0;
405 }
406
407
408 static int
409 insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
410 {
411   HDR_LINE hdr;
412
413   assert (msg->current_part);
414   if (!length)
415     {
416       msg->in_body = 1;
417       return transition_to_body (msg);
418     }
419
420   if (!msg->current_part->hdr_lines)
421     do_callback (msg, RFC822PARSE_BEGIN_HEADER);
422
423   length = length_sans_trailing_ws (line, length);
424   hdr = xmalloc (sizeof (*hdr) + length);
425   if (!hdr)
426     return -1;
427   hdr->next = NULL;
428   hdr->cont = (*line == ' ' || *line == '\t');
429   memcpy (hdr->line, line, length);
430   hdr->line[length] = 0; /* Make it a string. */
431
432   /* Transform a field name into canonical format. */
433   if (!hdr->cont && strchr (line, ':'))
434      capitalize_header_name (hdr->line);
435
436   *msg->current_part->hdr_lines_tail = hdr;
437   msg->current_part->hdr_lines_tail = &hdr->next;
438
439   /* Lets help the caller to prevent mail loops and issue an event for
440    * every Received header. */
441   if (length >= 9 && !memcmp (line, "Received:", 9))
442      do_callback (msg, RFC822PARSE_RCVD_SEEN);
443   return 0;
444 }
445
446
447 /****************
448  * Note: We handle the body transparent to allow binary zeroes in it.
449  */
450 static int
451 insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
452 {
453   int rc = 0;
454
455   if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary)
456     {
457       size_t blen = strlen (msg->boundary);
458
459       if (length == blen + 2
460           && !memcmp (line+2, msg->boundary, blen))
461         {
462           rc = do_callback (msg, RFC822PARSE_BOUNDARY);
463           msg->in_body = 0;
464           if (!rc && !msg->in_preamble)
465             rc = transition_to_header (msg);
466           msg->in_preamble = 0;
467         }
468       else if (length == blen + 4
469           && line[length-2] =='-' && line[length-1] == '-'
470           && !memcmp (line+2, msg->boundary, blen))
471         {
472           rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY);
473           msg->boundary = NULL; /* No current boundary anymore. */
474           set_current_part_to_parent (msg);
475
476           /* Fixme: The next should actually be send right before the
477              next boundary, so that we can mark the epilogue. */
478           if (!rc)
479             rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
480         }
481     }
482   if (msg->in_preamble && !rc)
483     rc = do_callback (msg, RFC822PARSE_PREAMBLE);
484
485   return rc;
486 }
487
488 /* Insert the next line into the parser. Return 0 on success or true
489    on error with errno set appropriately. */
490 int
491 rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
492 {
493   return (msg->in_body
494           ? insert_body (msg, line, length)
495           : insert_header (msg, line, length));
496 }
497
498
499 /* Tell the parser that we have finished the message. */
500 int
501 rfc822parse_finish (rfc822parse_t msg)
502 {
503   return do_callback (msg, RFC822PARSE_FINISH);
504 }
505
506
507
508 /****************
509  * Get a copy of a header line. The line is returned as one long
510  * string with LF to separate the continuation line. Caller must free
511  * the return buffer.  WHICH may be used to enumerate over all lines.
512  * Wildcards are allowed.  This function works on the current headers;
513  * i.e. the regular mail headers or the MIME headers of the current
514  * part.
515  *
516  * WHICH gives the mode:
517  *  -1 := Take the last occurence
518  *   n := Take the n-th  one.
519  *
520  * Returns a newly allocated buffer or NULL on error.  errno is set in
521  * case of a memory failure or set to 0 if the requested field is not
522  * available.
523  *
524  * If VALUEOFF is not NULL it will receive the offset of the first non
525  * space character in the value part of the line (i.e. after the first
526  * colon).
527  */
528 char *
529 rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
530                        size_t *valueoff)
531 {
532   HDR_LINE h, h2;
533   char *buf, *p;
534   size_t n;
535
536   h = find_header (msg, name, which, NULL);
537   if (!h)
538     {
539       errno = 0;
540       return NULL; /* no such field */
541     }
542
543   n = strlen (h->line) + 1;
544   for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
545     n += strlen (h2->line) + 1;
546
547   buf = p = xmalloc (n);
548   if (buf)
549     {
550       p = my_stpcpy (p, h->line);
551       *p++ = '\n';
552       for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
553         {
554           p = my_stpcpy (p, h2->line);
555           *p++ = '\n';
556         }
557       p[-1] = 0;
558     }
559
560   if (valueoff)
561     {
562       p = strchr (buf, ':');
563       if (!p)
564         *valueoff = 0; /* Oops: should never happen. */
565       else
566         {
567           p++;
568           while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
569             p++;
570           *valueoff = p - buf;
571         }
572     }
573
574   return buf;
575 }
576
577
578 /****************
579  * Enumerate all header.  Caller has to provide the address of a pointer
580  * which has to be initialzed to NULL, the caller should then never change this
581  * pointer until he has closed the enumeration by passing again the address
582  * of the pointer but with msg set to NULL.
583  * The function returns pointers to all the header lines or NULL when
584  * all lines have been enumerated or no headers are available.
585  */
586 const char *
587 rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
588 {
589   HDR_LINE l;
590
591   if (!msg) /* Close. */
592     return NULL;
593
594   if (*context == msg || !msg->current_part)
595     return NULL;
596
597   l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines;
598
599   if (l)
600     {
601       *context = l->next ? (void *) (l->next) : (void *) msg;
602       return l->line;
603     }
604   *context = msg; /* Mark end of list. */
605   return NULL;
606 }
607
608
609
610 /****************
611  * Find a header field.  If the Name does end in an asterisk this is meant
612  * to be a wildcard.
613  *
614  *  which  -1 : Retrieve the last field
615  *         >0 : Retrieve the n-th field
616
617  * RPREV may be used to return the predecessor of the returned field;
618  * which may be NULL for the very first one. It has to be initialzed
619  * to either NULL in which case the search start at the first header line,
620  * or it may point to a headerline, where the search should start
621  */
622 static HDR_LINE
623 find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev)
624 {
625   HDR_LINE hdr, prev = NULL, mark = NULL;
626   unsigned char *p;
627   size_t namelen, n;
628   int found = 0;
629   int glob = 0;
630
631   if (!msg->current_part)
632     return NULL;
633
634   namelen = strlen (name);
635   if (namelen && name[namelen - 1] == '*')
636     {
637       namelen--;
638       glob = 1;
639     }
640
641   hdr = msg->current_part->hdr_lines;
642   if (rprev && *rprev)
643     {
644       /* spool forward to the requested starting place.
645        * we cannot simply set this as we have to return
646        * the previous list element too */
647       for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next)
648         ;
649     }
650
651   for (; hdr; prev = hdr, hdr = hdr->next)
652     {
653       if (hdr->cont)
654         continue;
655       if (!(p = strchr (hdr->line, ':')))
656         continue;               /* invalid header, just skip it. */
657       n = p - hdr->line;
658       if (!n)
659         continue;               /* invalid name */
660       if ((glob ? (namelen <= n) : (namelen == n))
661           && !memcmp (hdr->line, name, namelen))
662         {
663           found++;
664           if (which == -1)
665             mark = hdr;
666           else if (found == which)
667             {
668               if (rprev)
669                 *rprev = prev;
670               return hdr;
671             }
672         }
673     }
674   if (mark && rprev)
675     *rprev = prev;
676   return mark;
677 }
678
679
680
681 static const char *
682 skip_ws (const char *s)
683 {
684   while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
685     s++;
686   return s;
687 }
688
689
690 static void
691 release_token_list (TOKEN t)
692 {
693   while (t)
694     {
695       TOKEN t2 = t->next;
696       /* fixme: If we have owner_pantry, put the token back to
697        * this pantry so that it can be reused later */
698       xfree (t);
699       t = t2;
700     }
701 }
702
703
704 static TOKEN
705 new_token (enum token_type type, const char *buf, size_t length)
706 {
707   TOKEN t;
708
709   /* fixme: look through our pantries to find a suitable
710    * token for reuse */
711   t = xmalloc (sizeof *t + length);
712   if (t)
713     {
714       t->next = NULL;
715       t->type = type;
716       memset (&t->flags, 0, sizeof (t->flags));
717       t->data[0] = 0;
718       if (buf)
719         {
720           memcpy (t->data, buf, length);
721           t->data[length] = 0;  /* Make sure it is a C string. */
722         }
723       else
724         t->data[0] = 0;
725     }
726   return t;
727 }
728
729 static TOKEN
730 append_to_token (TOKEN old, const char *buf, size_t length)
731 {
732   size_t n = strlen (old->data);
733   TOKEN t;
734
735   t = xmalloc (sizeof *t + n + length);
736   if (t)
737     {
738       t->next = old->next;
739       t->type = old->type;
740       t->flags = old->flags;
741       memcpy (t->data, old->data, n);
742       memcpy (t->data + n, buf, length);
743       t->data[n + length] = 0;
744       old->next = NULL;
745       release_token_list (old);
746     }
747   return t;
748 }
749
750
751
752 /*
753    Parse a field into tokens as defined by rfc822.
754  */
755 static TOKEN
756 parse_field (HDR_LINE hdr)
757 {
758   static const char specials[] = "<>@.,;:\\[]\"()";
759   static const char specials2[] = "<>@.,;:";
760   static const char tspecials[] = "/?=<>@,;:\\[]\"()";
761   static const char tspecials2[] = "/?=<>@.,;:";  /* FIXME: really
762                                                      include '.'?*/
763   static struct
764   {
765     const unsigned char *name;
766     size_t namelen;
767   } tspecial_header[] = {
768     { "Content-Type", 12},
769     { "Content-Transfer-Encoding", 25},
770     { "Content-Disposition", 19},
771     { NULL, 0}
772   };
773   const char *delimiters;
774   const char *delimiters2;
775   const unsigned char *line, *s, *s2;
776   size_t n;
777   int i, invalid = 0;
778   TOKEN t, tok, *tok_tail;
779
780   errno = 0;
781   if (!hdr)
782     return NULL;
783
784   tok = NULL;
785   tok_tail = &tok;
786
787   line = hdr->line;
788   if (!(s = strchr (line, ':')))
789     return NULL; /* oops */
790
791   n = s - line;
792   if (!n)
793     return NULL; /* oops: invalid name */
794
795   delimiters = specials;
796   delimiters2 = specials2;
797   for (i = 0; tspecial_header[i].name; i++)
798     {
799       if (n == tspecial_header[i].namelen
800           && !memcmp (line, tspecial_header[i].name, n))
801         {
802           delimiters = tspecials;
803           delimiters2 = tspecials2;
804           break;
805         }
806     }
807
808   s++; /* Move over the colon. */
809   for (;;)
810     {
811       while (!*s)
812         {
813           if (!hdr->next || !hdr->next->cont)
814             return tok; /* Ready.  */
815           /* Next item is a header continuation line.  */
816           hdr = hdr->next;
817           s = hdr->line;
818         }
819
820       if (*s == '(')
821         {
822           int level = 1;
823           int in_quote = 0;
824
825           invalid = 0;
826           for (s++;; s++)
827             {
828               while (!*s)
829                 {
830                   if (!hdr->next || !hdr->next->cont)
831                     goto oparen_out;
832                   /* Next item is a header continuation line.  */
833                   hdr = hdr->next;
834                   s = hdr->line;
835                 }
836
837               if (in_quote)
838                 {
839                   if (*s == '\"')
840                     in_quote = 0;
841                   else if (*s == '\\' && s[1])  /* what about continuation? */
842                     s++;
843                 }
844               else if (*s == ')')
845                 {
846                   if (!--level)
847                     break;
848                 }
849               else if (*s == '(')
850                 level++;
851               else if (*s == '\"')
852                 in_quote = 1;
853             }
854         oparen_out:
855           if (!*s)
856             ; /* Actually this is an error, but we don't care about it. */
857           else
858             s++;
859         }
860       else if (*s == '\"' || *s == '[')
861         {
862           /* We do not check for non-allowed nesting of domainliterals */
863           int term = *s == '\"' ? '\"' : ']';
864           invalid = 0;
865           s++;
866           t = NULL;
867
868           for (;;)
869             {
870               for (s2 = s; *s2; s2++)
871                 {
872                   if (*s2 == term)
873                     break;
874                   else if (*s2 == '\\' && s2[1]) /* what about continuation? */
875                     s2++;
876                 }
877
878               t = (t
879                    ? append_to_token (t, s, s2 - s)
880                    : new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
881               if (!t)
882                 goto failure;
883
884               if (*s2 || !hdr->next || !hdr->next->cont)
885                 break;
886               /* Next item is a header continuation line.  */
887               hdr = hdr->next;
888               s = hdr->line;
889             }
890           *tok_tail = t;
891           tok_tail = &t->next;
892           s = s2;
893           if (*s)
894             s++; /* skip the delimiter */
895         }
896       else if ((s2 = strchr (delimiters2, *s)))
897         { /* Special characters which are not handled above. */
898           invalid = 0;
899           t = new_token (tSPECIAL, s, 1);
900           if (!t)
901             goto failure;
902           *tok_tail = t;
903           tok_tail = &t->next;
904           s++;
905         }
906       else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
907         {
908           invalid = 0;
909           s = skip_ws (s + 1);
910         }
911       else if (*s > 0x20 && !(*s & 128))
912         { /* Atom. */
913           invalid = 0;
914           for (s2 = s + 1; *s2 > 0x20
915                && !(*s2 & 128) && !strchr (delimiters, *s2); s2++)
916             ;
917           t = new_token (tATOM, s, s2 - s);
918           if (!t)
919             goto failure;
920           *tok_tail = t;
921           tok_tail = &t->next;
922           s = s2;
923         }
924       else
925         { /* Invalid character. */
926           if (!invalid)
927             { /* For parsing we assume only one space. */
928               t = new_token (tSPACE, NULL, 0);
929               if (!t)
930                 goto failure;
931               *tok_tail = t;
932               tok_tail = &t->next;
933               invalid = 1;
934             }
935           s++;
936         }
937     }
938   /*NOTREACHED*/
939
940  failure:
941   {
942     int save = errno;
943     release_token_list (tok);
944     errno = save;
945   }
946   return NULL;
947 }
948
949
950
951
952 /****************
953  * Find and parse a header field.
954  * WHICH indicates what to do if there are multiple instance of the same
955  * field (like "Received"); the following value are defined:
956  *  -1 := Take the last occurence
957  *   0 := Reserved
958  *   n := Take the n-th one.
959  * Returns a handle for further operations on the parse context of the field
960  * or NULL if the field was not found.
961  */
962 rfc822parse_field_t
963 rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which)
964 {
965   HDR_LINE hdr;
966
967   if (!which)
968     return NULL;
969
970   hdr = find_header (msg, name, which, NULL);
971   if (!hdr)
972     return NULL;
973   return parse_field (hdr);
974 }
975
976 void
977 rfc822parse_release_field (rfc822parse_field_t ctx)
978 {
979   if (ctx)
980     release_token_list (ctx);
981 }
982
983
984
985 /****************
986  * Check whether T points to a parameter.
987  * A parameter starts with a semicolon and it is assumed that t
988  * points to exactly this one.
989  */
990 static int
991 is_parameter (TOKEN t)
992 {
993   t = t->next;
994   if (!t || t->type != tATOM)
995     return 0;
996   t = t->next;
997   if (!t || !(t->type == tSPECIAL && t->data[0] == '='))
998     return 0;
999   t = t->next;
1000   if (!t)
1001     return 1; /* We assume that an non existing value is an empty one. */
1002   return t->type == tQUOTED || t->type == tATOM;
1003 }
1004
1005 /*
1006    Some header (Content-type) have a special syntax where attribute=value
1007    pairs are used after a leading semicolon.  The parse_field code
1008    knows about these fields and changes the parsing to the one defined
1009    in RFC2045.
1010    Returns a pointer to the value which is valid as long as the
1011    parse context is valid; NULL is returned in case that attr is not
1012    defined in the header, a missing value is represented by an empty string.
1013
1014    With LOWER_VALUE set to true, a matching field value will be
1015    lowercased.
1016
1017    Note, that ATTR should be lowercase.  If ATTR is NULL the fucntion
1018    returns the first token of the field; i.e. not the parameter but
1019    the actual value.  A CTX of NULL is allowed and will return NULL.
1020  */
1021 const char *
1022 rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr,
1023                              int lower_value)
1024 {
1025   TOKEN t, a;
1026
1027   if (!attr)
1028     {
1029       t = ctx;
1030       if (t
1031           && (t->type == tATOM || t->type == tQUOTED || t->type == tDOMAINLIT))
1032         {
1033           if ( lower_value && !t->flags.lowered )
1034             {
1035               lowercase_string (t->data);
1036               t->flags.lowered = 1;
1037             }
1038           return t->data;
1039         }
1040       return NULL;
1041     }
1042
1043   for (t = ctx; t; t = t->next)
1044     {
1045       /* skip to the next semicolon */
1046       for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next)
1047         ;
1048       if (!t)
1049         return NULL;
1050       if (is_parameter (t))
1051         { /* Look closer. */
1052           a = t->next; /* We know that this is an atom */
1053           if ( !a->flags.lowered )
1054             {
1055               lowercase_string (a->data);
1056               a->flags.lowered = 1;
1057             }
1058           if (!strcmp (a->data, attr))
1059             { /* found */
1060               t = a->next->next;
1061               /* Either T is now an atom, a quoted string or NULL in
1062                * which case we return an empty string. */
1063
1064               if ( lower_value && t && !t->flags.lowered )
1065                 {
1066                   lowercase_string (t->data);
1067                   t->flags.lowered = 1;
1068                 }
1069               return t ? t->data : "";
1070             }
1071         }
1072     }
1073   return NULL;
1074 }
1075
1076 /****************
1077  * This function may be used for the Content-Type header to figure out
1078  * the media type and subtype.  Note, that the returned strings are
1079  * guaranteed to be lowercase as required by MIME.
1080  *
1081  * Returns: a pointer to the media type and if subtype is not NULL,
1082  *          a pointer to the subtype.
1083  */
1084 const char *
1085 rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype)
1086 {
1087   TOKEN t = ctx;
1088   const char *type;
1089
1090   if (t->type != tATOM)
1091     return NULL;
1092   if (!t->flags.lowered)
1093     {
1094       lowercase_string (t->data);
1095       t->flags.lowered = 1;
1096     }
1097   type = t->data;
1098   t = t->next;
1099   if (!t || t->type != tSPECIAL || t->data[0] != '/')
1100     return NULL;
1101   t = t->next;
1102   if (!t || t->type != tATOM)
1103     return NULL;
1104
1105   if (subtype)
1106     {
1107       if (!t->flags.lowered)
1108         {
1109           lowercase_string (t->data);
1110           t->flags.lowered = 1;
1111         }
1112       *subtype = t->data;
1113     }
1114   return type;
1115 }
1116
1117
1118
1119
1120
1121 #ifdef TESTING
1122
1123 /* Internal debug function to print the structure of the message. */
1124 static void
1125 dump_structure (rfc822parse_t msg, part_t part, int indent)
1126 {
1127   if (!part)
1128     {
1129       printf ("*** Structure of this message:\n");
1130       part = msg->parts;
1131     }
1132
1133   for (; part; part = part->right)
1134     {
1135       rfc822parse_field_t ctx;
1136       part_t save_part; /* ugly hack - we should have a function to
1137                            get part inforation. */
1138       const char *s;
1139
1140       save_part = msg->current_part;
1141       msg->current_part = part;
1142       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
1143       msg->current_part = save_part;
1144       if (ctx)
1145         {
1146           const char *s1, *s2;
1147           s1 = rfc822parse_query_media_type (ctx, &s2);
1148           if (s1)
1149             printf ("***   %*s %s/%s", indent*2, "", s1, s2);
1150           else
1151             printf ("***   %*s [not found]", indent*2, "");
1152
1153           s = rfc822parse_query_parameter (ctx, "boundary", 0);
1154           if (s)
1155             printf (" (boundary=\"%s\")", s);
1156           rfc822parse_release_field (ctx);
1157         }
1158       else
1159         printf ("***   %*s text/plain [assumed]", indent*2, "");
1160       putchar('\n');
1161
1162       if (part->down)
1163         dump_structure (msg, part->down, indent + 1);
1164     }
1165
1166 }
1167
1168
1169
1170 static void
1171 show_param (rfc822parse_field_t ctx, const char *name)
1172 {
1173   const char *s;
1174
1175   if (!ctx)
1176     return;
1177   s = rfc822parse_query_parameter (ctx, name, 0);
1178   if (s)
1179     printf ("***   %s: `%s'\n", name, s);
1180 }
1181
1182
1183
1184 static void
1185 show_event (rfc822parse_event_t event)
1186 {
1187   const char *s;
1188
1189   switch (event)
1190     {
1191     case RFC822PARSE_OPEN: s= "Open"; break;
1192     case RFC822PARSE_CLOSE: s= "Close"; break;
1193     case RFC822PARSE_CANCEL: s= "Cancel"; break;
1194     case RFC822PARSE_T2BODY: s= "T2Body"; break;
1195     case RFC822PARSE_FINISH: s= "Finish"; break;
1196     case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
1197     case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
1198     case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
1199     default: s= "***invalid event***"; break;
1200     }
1201   printf ("*** got RFC822 event %s\n", s);
1202 }
1203
1204 static int
1205 msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
1206 {
1207   show_event (event);
1208   if (event == RFC822PARSE_T2BODY)
1209     {
1210       rfc822parse_field_t ctx;
1211       void *ectx;
1212       const char *line;
1213
1214       for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); )
1215         {
1216           printf ("*** HDR: %s\n", line);
1217         }
1218       rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */
1219
1220       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
1221       if (ctx)
1222         {
1223           const char *s1, *s2;
1224           s1 = rfc822parse_query_media_type (ctx, &s2);
1225           if (s1)
1226             printf ("***   media: `%s/%s'\n", s1, s2);
1227           else
1228             printf ("***   media: [not found]\n");
1229           show_param (ctx, "boundary");
1230           show_param (ctx, "protocol");
1231           rfc822parse_release_field (ctx);
1232         }
1233       else
1234         printf ("***   media: text/plain [assumed]\n");
1235
1236       ctx = rfc822parse_parse_field (msg, "Content-Disposition", -1);
1237       if (ctx)
1238         {
1239           const char *s1;
1240           TOKEN t;
1241
1242           s1 = rfc822parse_query_parameter (ctx, NULL, 1);
1243           if (s1)
1244             printf ("***   disp: type=`%s'\n", s1);
1245           s1 = rfc822parse_query_parameter (ctx, "filename", 0);
1246           if (s1)
1247             printf ("***   disp: fname=`%s'\n", s1);
1248
1249           rfc822parse_release_field (ctx);
1250         }
1251     }
1252
1253
1254   return 0;
1255 }
1256
1257
1258
1259 int
1260 main (int argc, char **argv)
1261 {
1262   char line[5000];
1263   size_t length;
1264   rfc822parse_t msg;
1265
1266   msg = rfc822parse_open (msg_cb, NULL);
1267   if (!msg)
1268     abort ();
1269
1270   while (fgets (line, sizeof (line), stdin))
1271     {
1272       length = strlen (line);
1273       if (length && line[length - 1] == '\n')
1274         line[--length] = 0;
1275       if (length && line[length - 1] == '\r')
1276         line[--length] = 0;
1277       if (rfc822parse_insert (msg, line, length))
1278         abort ();
1279     }
1280
1281   dump_structure (msg, NULL, 0);
1282
1283   rfc822parse_close (msg);
1284   return 0;
1285 }
1286 #endif
1287
1288 /*
1289 Local Variables:
1290 compile-command: "gcc -Wall -g -DTESTING -o rfc822parse rfc822parse.c"
1291 End:
1292 */