(parse_message): Increase buffer to 1000 to detect
authorwerner <werner>
Fri, 19 Nov 2004 12:17:20 +0000 (12:17 +0000)
committerwerner <werner>
Fri, 19 Nov 2004 12:17:20 +0000 (12:17 +0000)
EXE files with a long header before the PE mark.

ChangeLog
rfc822parse.c [new file with mode: 0644]
rfc822parse.h [new file with mode: 0644]
scrutmime.c

index b521301..3306dc3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2004-11-19  Werner Koch  <wk@g10code.com>
+
+       * scrutmime.c (parse_message): Increase buffer to 1000 to detect
+       EXE files with a long header before the PE mark.
+
 2003-12-29  Werner Koch  <wk@gnupg.org>
 
        * addrutil.c (ProcessTexOp): Implemented modifier ":N=".
diff --git a/rfc822parse.c b/rfc822parse.c
new file mode 100644 (file)
index 0000000..3379886
--- /dev/null
@@ -0,0 +1,1254 @@
+/* rfc822parse.c - Simple mail and MIME parser
+ *     Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
+ *      Copyright (C) 2003, 2004 g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+
+/* According to RFC822 binary 0 are allowed at many places. We
+ * do not handle this correct especially in the field parsing code.  It
+ * should be easy to fix and the API provides a interfcaes which returns
+ * the length but in addition makes sure that returned strings are always
+ * ended by a \0.  
+ *
+ * Furthermore, the case of field names is changed and thus it is not
+ * always a good idea to use these modified header
+ * lines (e.g. signatures may break).
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include "rfc822parse.h"
+
+enum token_type
+{
+  tSPACE,
+  tATOM,
+  tQUOTED,
+  tDOMAINLIT,
+  tSPECIAL
+};
+
+/* For now we directly use our TOKEN as the parse context */
+typedef struct rfc822parse_field_context *TOKEN;
+struct rfc822parse_field_context
+{
+  TOKEN next;
+  enum token_type type;
+  struct {
+    unsigned int cont:1;
+    unsigned int lowered:1;
+  } flags;
+  /*TOKEN owner_pantry; */
+  char data[1];
+};
+
+struct hdr_line
+{
+  struct hdr_line *next;
+  int cont;     /* This is a continuation of the previous line. */
+  unsigned char line[1];
+};
+
+typedef struct hdr_line *HDR_LINE;
+
+
+struct part
+{
+  struct part *right;     /* The next part. */
+  struct part *down;      /* A contained part. */
+  HDR_LINE hdr_lines;       /* Header lines os that part. */
+  HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */
+  char *boundary;           /* Only used in the first part. */
+};
+typedef struct part *part_t;
+
+struct rfc822parse_context
+{
+  rfc822parse_cb_t callback;
+  void *callback_value;
+  int callback_error;
+  int in_body;
+  int in_preamble;      /* Wether we are before the first boundary. */
+  part_t parts;         /* The tree of parts. */
+  part_t current_part;  /* Whom we are processing (points into parts). */
+  const char *boundary; /* Current boundary. */
+};
+
+static HDR_LINE find_header (rfc822parse_t msg, const char *name,
+                            int which, HDR_LINE * rprev);
+
+
+static size_t
+length_sans_trailing_ws (const unsigned char *line, size_t len)
+{
+  const unsigned char *p, *mark;
+  size_t n;
+  
+  for (mark=NULL, p=line, n=0; n < len; n++, p++)
+    {
+      if (strchr (" \t\r\n", *p ))
+        {
+          if( !mark )
+            mark = p;
+        }
+      else
+        mark = NULL;
+    }
+  
+  if (mark) 
+    return mark - line;
+  return len;
+}
+
+
+static void
+lowercase_string (unsigned char *string)
+{
+  for (; *string; string++)
+    if (*string >= 'A' && *string <= 'Z')
+      *string = *string - 'A' + 'a';
+}
+
+/* Transform a header name into a standard capitalized format; i.e
+   "Content-Type".  Conversion stops at the colon.  As usual we don't
+   use the localized versions of ctype.h.
+ */
+static void
+capitalize_header_name (unsigned char *name)
+{
+  int first = 1;
+
+  for (; *name && *name != ':'; name++)
+    if (*name == '-')
+      first = 1;
+    else if (first)
+      {
+        if (*name >= 'a' && *name <= 'z')
+          *name = *name - 'a' + 'A';
+        first = 0;
+      }
+    else if (*name >= 'A' && *name <= 'Z')
+      *name = *name - 'A' + 'a';
+}
+
+
+static char *
+stpcpy (char *a,const char *b)
+{
+  while (*b)
+    *a++ = *b++;
+  *a = 0;
+  
+  return (char*)a;
+}
+
+
+/* If a callback has been registerd, call it for the event of type
+   EVENT. */
+static int
+do_callback (rfc822parse_t msg, rfc822parse_event_t event)
+{
+  int rc;
+
+  if (!msg->callback || msg->callback_error)
+    return 0;
+  rc = msg->callback (msg->callback_value, event, msg);
+  if (rc)
+    msg->callback_error = rc;
+  return rc;
+}
+
+static part_t
+new_part (void)
+{
+  part_t part;
+
+  part = calloc (1, sizeof *part);
+  if (part)
+    {
+      part->hdr_lines_tail = &part->hdr_lines;
+    }
+  return part;
+}
+
+
+static void
+release_part (part_t part)
+{
+  part_t tmp;
+  HDR_LINE hdr, hdr2;
+
+  for (; part; part = tmp)
+    {
+      tmp = part->right;
+      if (part->down)
+        release_part (part->down);
+      for (hdr = part->hdr_lines; hdr; hdr = hdr2)
+        {
+          hdr2 = hdr->next;
+          free (hdr);
+        }
+      free (part->boundary);
+      free (part);
+    }
+}
+
+
+static void
+release_handle_data (rfc822parse_t msg)
+{
+  release_part (msg->parts);
+  msg->parts = NULL;
+  msg->current_part = NULL;
+  msg->boundary = NULL;
+}
+
+
+/* Create a new parsing context for an entire rfc822 message and
+   return it.  CB and CB_VALUE may be given to callback for certain
+   events.  NULL is returned on error with errno set appropriately. */
+rfc822parse_t
+rfc822parse_open (rfc822parse_cb_t cb, void *cb_value)
+{
+  rfc822parse_t msg = calloc (1, sizeof *msg);
+  if (msg)
+    {
+      msg->parts = msg->current_part = new_part ();
+      if (!msg->parts)
+        {
+          free (msg);
+          msg = NULL;
+        }
+      else
+        {
+          msg->callback = cb;
+          msg->callback_value = cb_value;
+          if (do_callback (msg, RFC822PARSE_OPEN))
+            {
+              release_handle_data (msg);
+              free (msg);
+              msg = NULL;
+            }
+        }
+    }
+  return msg;
+}
+
+
+void
+rfc822parse_cancel (rfc822parse_t msg)
+{
+  if (msg)
+    {
+      do_callback (msg, RFC822PARSE_CANCEL);
+      release_handle_data (msg);
+      free (msg);
+    }
+}
+
+
+void
+rfc822parse_close (rfc822parse_t msg)
+{
+  if (msg)
+    {
+      do_callback (msg, RFC822PARSE_CLOSE);
+      release_handle_data (msg);
+      free (msg);
+    }
+}
+
+static part_t
+find_parent (part_t tree, part_t target)
+{
+  part_t part;
+
+  for (part = tree->down; part; part = part->right)
+    {
+      if (part == target)
+        return tree; /* Found. */
+      if (part->down)
+        {
+          part_t tmp = find_parent (part, target);
+          if (tmp)
+            return tmp;
+        }
+    }
+  return NULL;
+}
+
+static void
+set_current_part_to_parent (rfc822parse_t msg)
+{
+  part_t parent;
+
+  assert (msg->current_part);
+  parent = find_parent (msg->parts, msg->current_part);
+  if (!parent)
+    return; /* Already at the top. */
+
+#ifndef NDEBUG
+  {
+    part_t part;
+    for (part = parent->down; part; part = part->right)
+      if (part == msg->current_part)
+        break;
+    assert (part);
+  }
+#endif
+  msg->current_part = parent;
+
+  parent = find_parent (msg->parts, parent);
+  msg->boundary = parent? parent->boundary: NULL;
+}
+
+
+
+/****************
+ * We have read in all header lines and are about to receive the body
+ * part.  The delimiter line has already been processed.
+ *
+ * FIXME: we's better return an error in case of memory failures.
+ */
+static int
+transition_to_body (rfc822parse_t msg)
+{
+  rfc822parse_field_t ctx;
+  int rc;
+
+  rc = do_callback (msg, RFC822PARSE_T2BODY);
+  if (!rc)
+    {
+      /* Store the boundary if we have multipart type. */
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (ctx)
+        {
+          const char *s;
+
+          s = rfc822parse_query_media_type (ctx, NULL);
+          if (s && !strcmp (s,"multipart"))
+            {
+              s = rfc822parse_query_parameter (ctx, "boundary", 0);
+              if (s)
+                {
+                  assert (!msg->current_part->boundary);
+                  msg->current_part->boundary = malloc (strlen (s) + 1);
+                  if (msg->current_part->boundary) 
+                    {
+                      part_t part;
+                  
+                      strcpy (msg->current_part->boundary, s);
+                      msg->boundary = msg->current_part->boundary;
+                      part = new_part ();
+                      if (!part)
+                        {
+                          int save_errno = errno;
+                          rfc822parse_release_field (ctx);
+                          errno = save_errno;
+                          return -1;
+                        }
+                      rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN);
+                      assert (!msg->current_part->down);
+                      msg->current_part->down = part;
+                      msg->current_part = part;
+                      msg->in_preamble = 1;
+                    }
+                }
+            }
+          rfc822parse_release_field (ctx);
+        }
+    }
+
+  return rc;
+}
+
+/* We have just passed a MIME boundary and need to prepare for new part.
+   headers. */
+static int
+transition_to_header (rfc822parse_t msg)
+{
+  part_t part;
+
+  assert (msg->current_part);
+  assert (!msg->current_part->right);
+
+  part = new_part ();
+  if (!part)
+    return -1;
+
+  msg->current_part->right = part;
+  msg->current_part = part;
+  return 0;
+}
+
+
+static int
+insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  HDR_LINE hdr;
+
+  assert (msg->current_part);
+  if (!length)
+    {
+      msg->in_body = 1;
+      return transition_to_body (msg);
+    }
+
+  if (!msg->current_part->hdr_lines)
+    do_callback (msg, RFC822PARSE_BEGIN_HEADER);
+
+  length = length_sans_trailing_ws (line, length);
+  hdr = malloc (sizeof (*hdr) + length);
+  if (!hdr)
+    return -1;
+  hdr->next = NULL;
+  hdr->cont = (*line == ' ' || *line == '\t');
+  memcpy (hdr->line, line, length);
+  hdr->line[length] = 0; /* Make it a string. */
+  
+  /* Transform a field name into canonical format. */
+  if (!hdr->cont && strchr (line, ':'))
+     capitalize_header_name (hdr->line);
+
+  *msg->current_part->hdr_lines_tail = hdr;
+  msg->current_part->hdr_lines_tail = &hdr->next;
+
+  /* Lets help the caller to prevent mail loops and issue an event for
+   * every Received header. */
+  if (length >= 9 && !memcmp (line, "Received:", 9))
+     do_callback (msg, RFC822PARSE_RCVD_SEEN);
+  return 0;
+}
+
+
+/****************
+ * Note: We handle the body transparent to allow binary zeroes in it.
+ */
+static int
+insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  int rc = 0;
+
+  if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary)
+    {
+      size_t blen = strlen (msg->boundary);
+
+      if (length == blen + 2
+          && !memcmp (line+2, msg->boundary, blen))
+        {
+          rc = do_callback (msg, RFC822PARSE_BOUNDARY);
+          msg->in_body = 0;
+          if (!rc && !msg->in_preamble)
+            rc = transition_to_header (msg);
+          msg->in_preamble = 0;
+        }
+      else if (length == blen + 4
+          && line[length-2] =='-' && line[length-1] == '-'
+          && !memcmp (line+2, msg->boundary, blen))
+        {
+          rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY);
+          msg->boundary = NULL; /* No current boundary anymore. */
+          set_current_part_to_parent (msg);
+
+          /* Fixme: The next should acctually be sent right before the
+             next boundary, so that we can mark the epilogue. */
+          if (!rc)
+            rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
+        }
+    }
+  if (msg->in_preamble && !rc)
+    rc = do_callback (msg, RFC822PARSE_PREAMBLE);
+
+  return rc;
+}
+
+/* Insert the next line into the parser. Return 0 on success or true
+   on error with errno set appropriately. */
+int
+rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  return (msg->in_body 
+          ? insert_body (msg, line, length)
+          : insert_header (msg, line, length));
+}
+
+
+/* Tell the parser that we have finished the message. */
+int
+rfc822parse_finish (rfc822parse_t msg)
+{
+  return do_callback (msg, RFC822PARSE_FINISH);
+}
+
+
+
+/****************
+ * Get a copy of a header line. The line is returned as one long
+ * string with LF to separate the continuation line. Caller must free
+ * the return buffer.  WHICH may be used to enumerate over all lines.
+ * Wildcards are allowed.  This function works on the current headers;
+ * i.e. the regular mail headers or the MIME headers of the current
+ * part.
+ *
+ * WHICH gives the mode:
+ *  -1 := Take the last occurence
+ *   n := Take the n-th  one.
+ * 
+ * Returns a newly allocated buffer or NULL on error.  errno is set in
+ * case of a memory failure or set to 0 if the requested field is not
+ * available.
+ * 
+ * If VALUEOFF is not NULL it will receive the offset of the first non
+ * space character in th value of the line.
+ */
+char *
+rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
+                       size_t *valueoff)
+{
+  HDR_LINE h, h2;
+  char *buf, *p;
+  size_t n;
+
+  h = find_header (msg, name, which, NULL);
+  if (!h)
+    {
+      errno = 0;
+      return NULL; /* no such field */
+    }
+
+  n = strlen (h->line) + 1;
+  for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
+    n += strlen (h2->line) + 1;
+
+  buf = p = malloc (n);
+  if (buf)
+    {
+      p = stpcpy (p, h->line);
+      *p++ = '\n';
+      for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
+        {
+          p = stpcpy (p, h2->line);
+          *p++ = '\n';
+        }
+      p[-1] = 0;
+    }
+
+  if (valueoff)
+    {
+      p = strchr (buf, ':');
+      if (!p)
+        *valueoff = 0; /* Oops: should never happen. */
+      else
+        {
+          p++;
+          while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
+            p++;
+          *valueoff = p - buf;
+        }
+    }
+
+  return buf;
+}
+
+
+/****************
+ * Enumerate all header.  Caller has to provide the address of a pointer
+ * which has to be initialzed to NULL, the caller should then never change this
+ * pointer until he has closed the enumeration by passing again the address
+ * of the pointer but with msg set to NULL.
+ * The function returns pointers to all the header lines or NULL when
+ * all lines have been enumerated or no headers are available.
+ */
+const char *
+rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
+{
+  HDR_LINE l;
+
+  if (!msg) /* Close. */
+    return NULL;       
+
+  if (*context == msg || !msg->current_part)
+    return NULL;
+
+  l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines;
+
+  if (l)
+    {
+      *context = l->next ? (void *) (l->next) : (void *) msg;
+      return l->line;
+    }
+  *context = msg; /* Mark end of list. */
+  return NULL;
+}
+
+
+
+/****************
+ * Find a header field.  If the Name does end in an asterisk this is meant
+ * to be a wildcard.
+ *
+ *  which  -1 : Retrieve the last field
+ *        >0 : Retrieve the n-th field
+
+ * RPREV may be used to return the predecessor of the returned field;
+ * which may be NULL for the very first one. It has to be initialzed
+ * to either NULL in which case the search start at the first header line,
+ * or it may point to a headerline, where the search should start
+ */
+static HDR_LINE
+find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev)
+{
+  HDR_LINE hdr, prev = NULL, mark = NULL;
+  unsigned char *p;
+  size_t namelen, n;
+  int found = 0;
+  int glob = 0;
+
+  if (!msg->current_part)
+    return NULL;
+
+  namelen = strlen (name);
+  if (namelen && name[namelen - 1] == '*')
+    {
+      namelen--;
+      glob = 1;
+    }
+
+  hdr = msg->current_part->hdr_lines;
+  if (rprev && *rprev)
+    {
+      /* spool forward to the requested starting place.
+       * we cannot simply set this as we have to return
+       * the previous list element too */
+      for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next)
+       ;
+    }
+
+  for (; hdr; prev = hdr, hdr = hdr->next)
+    {
+      if (hdr->cont)
+       continue;
+      if (!(p = strchr (hdr->line, ':')))
+       continue;               /* invalid header, just skip it. */
+      n = p - hdr->line;
+      if (!n)
+       continue;               /* invalid name */
+      if ((glob ? (namelen <= n) : (namelen == n))
+         && !memcmp (hdr->line, name, namelen))
+       {
+         found++;
+         if (which == -1)
+           mark = hdr;
+         else if (found == which)
+           {
+             if (rprev)
+               *rprev = prev;
+             return hdr;
+           }
+       }
+    }
+  if (mark && rprev)
+    *rprev = prev;
+  return mark;
+}
+
+
+
+static const char *
+skip_ws (const char *s)
+{
+  while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
+    s++;
+  return s;
+}
+
+
+static void
+release_token_list (TOKEN t)
+{
+  while (t)
+    {
+      TOKEN t2 = t->next;
+      /* fixme: If we have owner_pantry, put the token back to
+       * this pantry so that it can be reused later */
+      free (t);
+      t = t2;
+    }
+}
+
+
+static TOKEN
+new_token (enum token_type type, const char *buf, size_t length)
+{
+  TOKEN t;
+
+  /* fixme: look through our pantries to find a suitable
+   * token for reuse */
+  t = malloc (sizeof *t + length);
+  if (t)
+    {
+      t->next = NULL;
+      t->type = type;
+      memset (&t->flags, 0, sizeof (t->flags));
+      t->data[0] = 0;
+      if (buf)
+        {
+          memcpy (t->data, buf, length);
+          t->data[length] = 0; /* Make sure it is a C string. */
+        }
+      else
+        t->data[0] = 0;
+    }
+  return t;
+}
+
+static TOKEN
+append_to_token (TOKEN old, const char *buf, size_t length)
+{
+  size_t n = strlen (old->data);
+  TOKEN t;
+
+  t = malloc (sizeof *t + n + length);
+  if (t)
+    {
+      t->next = old->next;
+      t->type = old->type;
+      t->flags = old->flags;
+      memcpy (t->data, old->data, n);
+      memcpy (t->data + n, buf, length);
+      t->data[n + length] = 0;
+      old->next = NULL;
+      release_token_list (old);
+    }
+  return t;
+}
+
+
+
+/*
+   Parse a field into tokens as defined by rfc822.
+ */
+static TOKEN
+parse_field (HDR_LINE hdr)
+{
+  static const char specials[] = "<>@.,;:\\[]\"()";
+  static const char specials2[] = "<>@.,;:";
+  static const char tspecials[] = "/?=<>@,;:\\[]\"()";
+  static const char tspecials2[] = "/?=<>@.,;:";
+  static struct 
+  {
+    const unsigned char *name;
+    size_t namelen;
+  } tspecial_header[] = {
+    { "Content-Type", 12},
+    { "Content-Transfer-Encoding", 25},
+    { NULL, 0}
+  };
+  const char *delimiters;
+  const char *delimiters2;
+  const unsigned char *line, *s, *s2;
+  size_t n;
+  int i, invalid = 0;
+  TOKEN t, tok, *tok_tail;
+
+  errno = 0;
+  if (!hdr)
+    return NULL;
+
+  tok = NULL;
+  tok_tail = &tok;
+
+  line = hdr->line;
+  if (!(s = strchr (line, ':')))
+    return NULL; /* oops */
+
+  n = s - line;
+  if (!n)
+    return NULL; /* oops: invalid name */
+
+  delimiters = specials;
+  delimiters2 = specials2;
+  for (i = 0; tspecial_header[i].name; i++)
+    {
+      if (n == tspecial_header[i].namelen
+         && !memcmp (line, tspecial_header[i].name, n))
+       {
+         delimiters = tspecials;
+         delimiters2 = tspecials2;
+         break;
+       }
+    }
+
+  s++; /* Move over the colon. */
+  for (;;)
+    {
+      if (!*s)
+       {
+         if (!hdr->next || !hdr->next->cont)
+           break;
+         hdr = hdr->next;
+         s = hdr->line;
+       }
+
+      if (*s == '(')
+       {
+         int level = 1;
+         int in_quote = 0;
+
+         invalid = 0;
+         for (s++;; s++)
+           {
+             if (!*s)
+               {
+                 if (!hdr->next || !hdr->next->cont)
+                   break;
+                 hdr = hdr->next;
+                 s = hdr->line;
+               }
+
+             if (in_quote)
+               {
+                 if (*s == '\"')
+                   in_quote = 0;
+                 else if (*s == '\\' && s[1])  /* what about continuation? */
+                   s++;
+               }
+             else if (*s == ')')
+               {
+                 if (!--level)
+                   break;
+               }
+             else if (*s == '(')
+               level++;
+             else if (*s == '\"')
+               in_quote = 1;
+           }
+         if (!*s)
+           ; /* Actually this is an error, but we don't care about it. */
+         else
+           s++;
+       }
+      else if (*s == '\"' || *s == '[')
+       {
+         /* We do not check for non-allowed nesting of domainliterals */
+         int term = *s == '\"' ? '\"' : ']';
+         invalid = 0;
+         s++;
+         t = NULL;
+
+         for (;;)
+           {
+             for (s2 = s; *s2; s2++)
+               {
+                 if (*s2 == term)
+                   break;
+                 else if (*s2 == '\\' && s2[1]) /* what about continuation? */
+                   s2++;
+               }
+              
+             t = (t
+                   ? append_to_token (t, s, s2 - s)
+                   : new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
+              if (!t)
+                goto failure;
+                   
+             if (*s2 || !hdr->next || !hdr->next->cont)
+               break;
+             hdr = hdr->next;
+             s = hdr->line;
+           }
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s = s2;
+         if (*s)
+           s++; /* skip the delimiter */
+       }
+      else if ((s2 = strchr (delimiters2, *s)))
+       { /* Special characters which are not handled above. */
+         invalid = 0;
+         t = new_token (tSPECIAL, s, 1);
+          if (!t)
+            goto failure;
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s++;
+       }
+      else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
+       {
+         invalid = 0;
+         s = skip_ws (s + 1);
+       }
+      else if (*s > 0x20 && !(*s & 128))
+       { /* Atom. */
+         invalid = 0;
+         for (s2 = s + 1; *s2 > 0x20
+              && !(*s2 & 128) && !strchr (delimiters, *s2); s2++)
+           ;
+         t = new_token (tATOM, s, s2 - s);
+          if (!t)
+            goto failure;
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s = s2;
+       }
+      else
+       { /* Invalid character. */
+         if (!invalid)
+           { /* For parsing we assume only one space. */
+             t = new_token (tSPACE, NULL, 0);
+              if (!t)
+                goto failure;
+             *tok_tail = t;
+             tok_tail = &t->next;
+             invalid = 1;
+           }
+         s++;
+       }
+    }
+
+  return tok;
+
+ failure:
+  {
+    int save = errno;
+    release_token_list (tok);
+    errno = save;
+  }
+  return NULL;
+}
+
+
+
+
+/****************
+ * Find and parse a header field.
+ * WHICH indicates what to do if there are multiple instance of the same
+ * field (like "Received"); the following value are defined:
+ *  -1 := Take the last occurence
+ *   0 := Reserved
+ *   n := Take the n-th one.
+ * Returns a handle for further operations on the parse context of the field
+ * or NULL if the field was not found.
+ */
+rfc822parse_field_t
+rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which)
+{
+  HDR_LINE hdr;
+
+  if (!which)
+    return NULL;
+
+  hdr = find_header (msg, name, which, NULL);
+  if (!hdr)
+    return NULL;
+  return parse_field (hdr);
+}
+
+void
+rfc822parse_release_field (rfc822parse_field_t ctx)
+{
+  if (ctx)
+    release_token_list (ctx);
+}
+
+
+
+/****************
+ * Check whether T points to a parameter.
+ * A parameter starts with a semicolon and it is assumed that t
+ * points to exactly this one.
+ */
+static int
+is_parameter (TOKEN t)
+{
+  t = t->next;
+  if (!t || t->type != tATOM)
+    return 0;
+  t = t->next;
+  if (!t || !(t->type == tSPECIAL && t->data[0] == '='))
+    return 0;
+  t = t->next;
+  if (!t)
+    return 1; /* We assume that an non existing value is an empty one. */
+  return t->type == tQUOTED || t->type == tATOM;
+}
+
+/*
+   Some header (Content-type) have a special syntax where attribute=value
+   pairs are used after a leading semicolon.  The parse_field code
+   knows about these fields and changes the parsing to the one defined
+   in RFC2045.
+   Returns a pointer to the value which is valid as long as the
+   parse context is valid; NULL is returned in case that attr is not
+   defined in the header, a missing value is reppresented by an empty string.
+   With LOWER_VALUE set to true, a matching field valuebe be
+   lowercased.
+   Note, that ATTR should be lowercase.
+ */
+const char *
+rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr,
+                             int lower_value)
+{
+  TOKEN t, a;
+
+  for (t = ctx; t; t = t->next)
+    {
+      /* skip to the next semicolon */
+      for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next)
+       ;
+      if (!t)
+       return NULL;
+      if (is_parameter (t))
+       { /* Look closer. */
+         a = t->next; /* We know that this is an atom */
+          if ( !a->flags.lowered )
+            {
+              lowercase_string (a->data);
+              a->flags.lowered = 1;
+            }
+         if (!strcmp (a->data, attr))
+           { /* found */
+             t = a->next->next;
+             /* Either T is now an atom, a quoted string or NULL in
+              * which case we return an empty string. */
+
+              if ( lower_value && t && !t->flags.lowered )
+                {
+                  lowercase_string (t->data);
+                  t->flags.lowered = 1;
+                }
+             return t ? t->data : "";
+           }
+       }
+    }
+  return NULL;
+}
+
+/****************
+ * This function may be used for the Content-Type header to figure out
+ * the media type and subtype.  Note, that the returned strings are
+ * guaranteed to be lowercase as required by MIME.
+ *
+ * Returns: a pointer to the media type and if subtype is not NULL,
+ *         a pointer to the subtype.
+ */
+const char *
+rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype)
+{
+  TOKEN t = ctx;
+  const char *type;
+
+  if (t->type != tATOM)
+    return NULL;
+  if (!t->flags.lowered)
+    {
+      lowercase_string (t->data);
+      t->flags.lowered = 1;
+    }
+  type = t->data;
+  t = t->next;
+  if (!t || t->type != tSPECIAL || t->data[0] != '/')
+    return NULL;
+  t = t->next;
+  if (!t || t->type != tATOM)
+    return NULL;
+
+  if (subtype)
+    {
+      if (!t->flags.lowered)
+        {
+          lowercase_string (t->data);
+          t->flags.lowered = 1;
+        }
+      *subtype = t->data;
+    }
+  return type;
+}
+
+
+
+
+
+#ifdef TESTING
+
+/* Internal debug function to print the structure of the message. */
+static void
+dump_structure (rfc822parse_t msg, part_t part, int indent)
+{
+  if (!part)
+    {
+      printf ("*** Structure of this message:\n");
+      part = msg->parts;
+    }
+
+  for (; part; part = part->right)
+    {
+      rfc822parse_field_t ctx;
+      part_t save_part; /* ugly hack - we should have a function to
+                           get part inforation. */
+      const char *s;
+      
+      save_part = msg->current_part;
+      msg->current_part = part;
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      msg->current_part = save_part;
+      if (ctx)
+        {
+          const char *s1, *s2;
+          s1 = rfc822parse_query_media_type (ctx, &s2);
+          if (s1)
+            printf ("***   %*s %s/%s", indent*2, "", s1, s2);
+          else
+            printf ("***   %*s [not found]", indent*2, "");
+
+          s = rfc822parse_query_parameter (ctx, "boundary", 0);
+          if (s)
+            printf (" (boundary=\"%s\")", s);
+          rfc822parse_release_field (ctx);
+        }
+      else
+        printf ("***   %*s text/plain [assumed]", indent*2, "");
+      putchar('\n');
+
+      if (part->down)
+        dump_structure (msg, part->down, indent + 1);
+    }
+  
+}
+
+
+
+static void
+show_param (rfc822parse_field_t ctx, const char *name)
+{
+  const char *s;
+
+  if (!ctx)
+    return;
+  s = rfc822parse_query_parameter (ctx, name, 0);
+  if (s)
+    printf ("***   %s: `%s'\n", name, s);
+}
+
+
+
+static void
+show_event (rfc822parse_event_t event)
+{
+  const char *s;
+
+  switch (event)
+    {
+    case RFC822PARSE_OPEN: s= "Open"; break;
+    case RFC822PARSE_CLOSE: s= "Close"; break;
+    case RFC822PARSE_CANCEL: s= "Cancel"; break;
+    case RFC822PARSE_T2BODY: s= "T2Body"; break;
+    case RFC822PARSE_FINISH: s= "Finish"; break;
+    case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
+    case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
+    case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+    default: s= "***invalid event***"; break;
+    }
+  printf ("*** got RFC822 event %s\n", s);
+}
+
+static int
+msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
+{
+  show_event (event);
+  if (event == RFC822PARSE_T2BODY)
+    {
+      rfc822parse_field_t ctx;
+      void *ectx;
+      const char *line;
+
+      for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); )
+        {
+          printf ("*** HDR: %s\n", line);
+       }
+      rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */
+
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (ctx)
+        {
+          const char *s1, *s2;
+          s1 = rfc822parse_query_media_type (ctx, &s2);
+          if (s1)
+            printf ("***   media: `%s/%s'\n", s1, s2);
+          else
+            printf ("***   media: [not found]\n");
+          show_param (ctx, "boundary");
+          show_param (ctx, "protocol");
+          rfc822parse_release_field (ctx);
+        }
+      else
+        printf ("***   media: text/plain [assumed]\n");
+      
+    }
+
+
+  return 0;
+}
+
+
+
+int
+main (int argc, char **argv)
+{
+  char line[5000];
+  size_t length;
+  rfc822parse_t msg;
+
+  msg = rfc822parse_open (msg_cb, NULL);
+  if (!msg)
+    abort ();
+
+  while (fgets (line, sizeof (line), stdin))
+    {
+      length = strlen (line);
+      if (length && line[length - 1] == '\n')
+       line[--length] = 0;
+      if (length && line[length - 1] == '\r')
+       line[--length] = 0;
+      if (rfc822parse_insert (msg, line, length))
+       abort ();
+    }
+
+  dump_structure (msg, NULL, 0);
+
+  rfc822parse_close (msg);
+  return 0;
+}
+#endif
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -g -DTESTING -o rfc822parse rfc822parse.c"
+End:
+*/
diff --git a/rfc822parse.h b/rfc822parse.h
new file mode 100644 (file)
index 0000000..a7ed5b4
--- /dev/null
@@ -0,0 +1,80 @@
+/* rfc822parse.h - Simple mail and MIME parser
+ *     Copyright (C) 1999 Werner Koch, Duesseldorf
+ *      Copyright (C) 2003, g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef RFC822PARSE_H
+#define RFC822PARSE_H
+
+struct rfc822parse_context;
+typedef struct rfc822parse_context *rfc822parse_t;
+
+typedef enum 
+  {
+    RFC822PARSE_OPEN = 1,
+    RFC822PARSE_CLOSE,
+    RFC822PARSE_CANCEL,
+    RFC822PARSE_T2BODY,
+    RFC822PARSE_FINISH,
+    RFC822PARSE_RCVD_SEEN,
+    RFC822PARSE_LEVEL_DOWN,
+    RFC822PARSE_LEVEL_UP,
+    RFC822PARSE_BOUNDARY,
+    RFC822PARSE_LAST_BOUNDARY,
+    RFC822PARSE_BEGIN_HEADER,
+    RFC822PARSE_PREAMBLE,
+    RFC822PARSE_EPILOGUE
+  } 
+rfc822parse_event_t;
+
+struct rfc822parse_field_context;
+typedef struct rfc822parse_field_context *rfc822parse_field_t;
+
+
+typedef int (*rfc822parse_cb_t) (void *opaque,
+                                 rfc822parse_event_t event,
+                                 rfc822parse_t msg);
+
+
+rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value);
+
+void rfc822parse_close (rfc822parse_t msg);
+
+void rfc822parse_cancel (rfc822parse_t msg);
+int rfc822parse_finish (rfc822parse_t msg);
+
+int rfc822parse_insert (rfc822parse_t msg,
+                        const unsigned char *line, size_t length);
+
+char *rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
+                             size_t *valueoff);
+
+const char *rfc822parse_enum_header_lines (rfc822parse_t msg, void **context);
+
+rfc822parse_field_t rfc822parse_parse_field (rfc822parse_t msg,
+                                             const char *name,
+                                             int which);
+
+void rfc822parse_release_field (rfc822parse_field_t field);
+
+const char *rfc822parse_query_parameter (rfc822parse_field_t ctx,
+                                         const char *attr, int lower_value);
+
+const char *rfc822parse_query_media_type (rfc822parse_field_t ctx,
+                                          const char **subtype);
+
+#endif /*RFC822PARSE_H */
index 9fb12e0..5a07daf 100644 (file)
@@ -367,7 +367,7 @@ static void
 parse_message (FILE *fp)
 {
   char line[2000];
-  unsigned char buffer[200]; 
+  unsigned char buffer[1000]; 
   size_t buflen = 0;   
   size_t length;
   rfc822parse_t msg;
@@ -414,9 +414,13 @@ parse_message (FILE *fp)
             {
               int i;
               
-              printf ("# %d bytes base64:", (int)buflen);
+              printf ("# %4d bytes base64:", (int)buflen);
               for (i=0; i < buflen; i++)
-                printf (" %02X", buffer[i]);
+                {
+                  if (i && !(i % 16))
+                    printf ("\n#            0x%04X:", i);
+                  printf (" %02X", buffer[i]);
+                }
               putchar ('\n'); 
             }
           identify_binary (buffer, buflen);