tools: Add modules for MIME parsing and creating.
authorWerner Koch <wk@gnupg.org>
Wed, 29 Jun 2016 09:02:36 +0000 (11:02 +0200)
committerWerner Koch <wk@gnupg.org>
Wed, 29 Jun 2016 10:04:11 +0000 (12:04 +0200)
* tools/mime-maker.c: New.
* tools/mime-maker.h: New.
* tools/mime-parser.c: New.
* tools/mime-parser.h: New.

Signed-off-by: Werner Koch <wk@gnupg.org>
tools/mime-maker.c [new file with mode: 0644]
tools/mime-maker.h [new file with mode: 0644]
tools/mime-parser.c [new file with mode: 0644]
tools/mime-parser.h [new file with mode: 0644]
tools/rfc822parse.h

diff --git a/tools/mime-maker.c b/tools/mime-maker.c
new file mode 100644 (file)
index 0000000..88f9d5f
--- /dev/null
@@ -0,0 +1,624 @@
+/* mime-maker.c - Create MIME structures
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+#include "zb32.h"
+#include "mime-maker.h"
+
+
+/* An object to store an header.  Also used for a list of headers.  */
+struct header_s
+{
+  struct header_s *next;
+  char *value;   /* Malloced value.  */
+  char name[1]; /* Name.  */
+};
+typedef struct header_s *header_t;
+
+
+/* An object to store a MIME part.  A part is the header plus the
+ * content (body). */
+struct part_s
+{
+  struct part_s *next;  /* Next part in the current container.  */
+  struct part_s *child; /* Child container.  */
+  char *mediatype;      /* Mediatype of the container (malloced). */
+  char *boundary;       /* Malloced boundary string.  */
+  header_t headers;     /* List of headers.  */
+  header_t *headers_tail;/* Address of last header in chain.  */
+  size_t bodylen;       /* Length of BODY.   */
+  char *body;           /* Malloced buffer with the body.  This is the
+                         * non-encoded value.  */
+};
+typedef struct part_s *part_t;
+
+
+
+/* Definition of the mime parser object.  */
+struct mime_maker_context_s
+{
+  void *cookie;                /* Cookie passed to all callbacks.  */
+
+  unsigned int verbose:1;      /* Enable verbose mode.  */
+  unsigned int debug:1;        /* Enable debug mode.  */
+
+  part_t mail;                 /* The MIME tree.  */
+  part_t current_part;
+
+  int boundary_counter;  /* Used to create easy to read boundaries.  */
+  char *boundary_suffix; /* Random string used in the boundaries.  */
+
+  struct b64state *b64state;     /* NULL or malloced Base64 decoder state.  */
+
+  /* Helper to convey the output stream to recursive functions. */
+  estream_t outfp;
+};
+
+
+/* Create a new mime make object.  COOKIE is a values woich will be
+ * used as first argument for all callbacks registered with this
+ * object.  */
+gpg_error_t
+mime_maker_new (mime_maker_t *r_maker, void *cookie)
+{
+  mime_maker_t ctx;
+
+  *r_maker = NULL;
+
+  ctx = xtrycalloc (1, sizeof *ctx);
+  if (!ctx)
+    return gpg_error_from_syserror ();
+  ctx->cookie = cookie;
+
+  *r_maker = ctx;
+  return 0;
+}
+
+
+static void
+release_parts (part_t part)
+{
+  while (part)
+    {
+      part_t partnext = part->next;
+      while (part->headers)
+        {
+          header_t hdrnext = part->headers->next;
+          xfree (part->headers);
+          part->headers = hdrnext;
+        }
+      release_parts (part->child);
+      xfree (part->mediatype);
+      xfree (part->boundary);
+      xfree (part->body);
+      xfree (part);
+      part = partnext;
+    }
+}
+
+
+/* Release a mime maker object.  */
+void
+mime_maker_release (mime_maker_t ctx)
+{
+  if (!ctx)
+    return;
+
+  release_parts (ctx->mail);
+  xfree (ctx->boundary_suffix);
+  xfree (ctx);
+}
+
+
+/* Set verbose and debug mode.  */
+void
+mime_maker_set_verbose (mime_maker_t ctx, int level)
+{
+  if (!level)
+    {
+      ctx->verbose = 0;
+      ctx->debug = 0;
+    }
+  else
+    {
+      ctx->verbose = 1;
+      if (level > 10)
+        ctx->debug = 1;
+    }
+}
+
+
+static void
+dump_parts (part_t part, int level)
+{
+  header_t hdr;
+
+  for (; part; part = part->next)
+    {
+      log_debug ("%*s[part]\n", level*2, "");
+      for (hdr = part->headers; hdr; hdr = hdr->next)
+        {
+          log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value);
+        }
+      log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen);
+      if (part->child)
+        {
+          log_debug ("%*s[container]\n", level*2, "");
+          dump_parts (part->child, level+1);
+        }
+    }
+}
+
+
+/* Dump the mime tree for debugging.  */
+void
+mime_maker_dump_tree (mime_maker_t ctx)
+{
+  dump_parts (ctx->mail, 0);
+}
+
+
+/* Find the parent node for NEEDLE starting at ROOT.  */
+static part_t
+find_parent (part_t root, part_t needle)
+{
+  part_t node, n;
+
+  for (node = root->child; node; node = node->next)
+    {
+      if (node == needle)
+        return root;
+      if ((n = find_parent (node, needle)))
+        return n;
+    }
+  return NULL;
+}
+
+
+/* Create a boundary string.  Outr codes is aware of the general
+ * structure of that string (gebins with "=-=") so that
+ * it can protect against accidently used boundaries within the
+ * content.   */
+static char *
+generate_boundary (mime_maker_t ctx)
+{
+  if (!ctx->boundary_suffix)
+    {
+      char buffer[12];
+
+      gcry_create_nonce (buffer, sizeof buffer);
+      ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer);
+      if (!ctx->boundary_suffix)
+        return NULL;
+    }
+
+  ctx->boundary_counter++;
+  return es_bsprintf ("=-=%02d-%s=-=",
+                      ctx->boundary_counter, ctx->boundary_suffix);
+}
+
+
+/* Ensure that the context has a MAIL and CURRENT_PART object and
+ * return the parent object if available  */
+static gpg_error_t
+ensure_part (mime_maker_t ctx, part_t *r_parent)
+{
+  if (!ctx->mail)
+    {
+      ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
+      if (!ctx->mail)
+        return gpg_error_from_syserror ();
+      log_assert (!ctx->current_part);
+      ctx->current_part = ctx->mail;
+      ctx->current_part->headers_tail = &ctx->current_part->headers;
+    }
+  log_assert (ctx->current_part);
+  if (r_parent)
+    *r_parent = find_parent (ctx->mail, ctx->current_part);
+
+  return 0;
+}
+
+
+/* Transform a header name into a standard capitalized format.
+ * "Content-Type".  Conversion stops at the colon. */
+static void
+capitalize_header_name (char *name)
+{
+  unsigned char *p = name;
+  int first = 1;
+
+  /* Special cases first.  */
+  if (!ascii_strcasecmp (name, "MIME-Version"))
+    {
+      strcpy (name, "MIME-Version");
+      return;
+    }
+
+  /* Regular cases.  */
+  for (; *p && *p != ':'; p++)
+    {
+      if (*p == '-')
+        first = 1;
+      else if (first)
+        {
+          if (*p >= 'a' && *p <= 'z')
+            *p = *p - 'a' + 'A';
+          first = 0;
+        }
+      else if (*p >= 'A' && *p <= 'Z')
+        *p = *p - 'A' + 'a';
+    }
+}
+
+
+/* Check whether a header with NAME has already been set into PART.
+ * NAME must be in canonical capitalized format.  Return true or
+ * false. */
+static int
+have_header (part_t part, const char *name)
+{
+  header_t hdr;
+
+  for (hdr = part->headers; hdr; hdr = hdr->next)
+    if (!strcmp (hdr->name, name))
+      return 1;
+  return 0;
+}
+
+
+/* Helper to add a header to a part.  */
+static gpg_error_t
+add_header (part_t part, const char *name, const char *value)
+{
+  gpg_error_t err;
+  header_t hdr;
+
+  hdr = xtrymalloc (sizeof *hdr + strlen (name));
+  if (!hdr)
+    return gpg_error_from_syserror ();
+  hdr->next = NULL;
+  strcpy (hdr->name, name);
+  capitalize_header_name (hdr->name);
+  hdr->value = xtrystrdup (value);
+  if (!hdr->value)
+    {
+      err = gpg_error_from_syserror ();
+      xfree (hdr);
+      return err;
+    }
+  *part->headers_tail = hdr;
+  part->headers_tail = &hdr->next;
+
+  return 0;
+}
+
+
+/* Add a header with NAME and VALUE to the current mail.  A LF in the
+ * VALUE will be handled automagically.  If no container has been
+ * added, the header will be used for the regular mail headers and not
+ * for a MIME part.  If the current part is in a container and a body
+ * has been added, we append a new part to the current container.
+ * Thus for a non-MIME mail the caller needs to call this function
+ * followed by a call to add a body.  When adding a Content-Type the
+ * boundary parameter must not be included.
+ */
+gpg_error_t
+mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value)
+{
+  gpg_error_t err;
+  part_t part, parent;
+
+  err = ensure_part (ctx, &parent);
+  if (err)
+    return err;
+  part = ctx->current_part;
+
+  if (part->body && !parent)
+    {
+      /* We already have a body but no parent.  Adding another part is
+       * thus not possible.  */
+      return gpg_error (GPG_ERR_CONFLICT);
+    }
+  if (part->body)
+    {
+      /* We already have a body and there is a parent.  We now append
+       * a new part to the current container.  */
+      part = xtrycalloc (1, sizeof *part);
+      if (!part)
+        return gpg_error_from_syserror ();
+      part->headers_tail = &part->headers;
+      log_assert (!ctx->current_part->next);
+      ctx->current_part->next = part;
+      ctx->current_part = part;
+    }
+
+  /* If no NAME and no VALUE has been given we do not add a header.
+   * This can be used to create a new part without any header.  */
+  if (!name && !value)
+    return 0;
+
+  /* If we add Content-Type, make sure that we have a MIME-version
+   * header first; this simply looks better.  */
+  if (!ascii_strcasecmp (name, "Content-Type")
+      && !have_header (ctx->mail, "MIME-Version"))
+    {
+      err = add_header (ctx->mail, "MIME-Version", "1.0");
+      if (err)
+        return err;
+    }
+  return add_header (part, name, value);
+}
+
+
+/* Helper for mime_maker_add_{body,stream}.  */
+static gpg_error_t
+add_body (mime_maker_t ctx, const void *data, size_t datalen)
+{
+  gpg_error_t err;
+  part_t part, parent;
+
+  err = ensure_part (ctx, &parent);
+  if (err)
+    return err;
+  part = ctx->current_part;
+  if (part->body)
+    return gpg_error (GPG_ERR_CONFLICT);
+
+  part->body = xtrymalloc (datalen? datalen : 1);
+  if (!part->body)
+    return gpg_error_from_syserror ();
+  part->bodylen = datalen;
+  if (data)
+    memcpy (part->body, data, datalen);
+
+  return 0;
+}
+
+
+/* Add STRING as body to the mail or the current MIME container.  A
+ * second call to this function is not allowed.
+ *
+ * FIXME: We may want to have an append_body to add more data to a body.
+ */
+gpg_error_t
+mime_maker_add_body (mime_maker_t ctx, const char *string)
+{
+  return add_body (ctx, string, strlen (string));
+}
+
+
+/* This is the same as mime_maker_add_body but takes a stream as
+ * argument.  As of now the stream is copied to the MIME object but
+ * eventually we may delay that and read the stream only at the time
+ * it is needed.  Note that the address of the stream object must be
+ * passed and that the ownership of the stream is transferred to this
+ * MIME object.  To indicate the latter the function will store NULL
+ * at the ADDR_STREAM so that a caller can't use that object anymore
+ * except for es_fclose which accepts a NULL pointer.  */
+gpg_error_t
+mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr)
+{
+  void *data;
+  size_t datalen;
+
+  es_rewind (*stream_addr);
+  if (es_fclose_snatch (*stream_addr, &data, &datalen))
+    return gpg_error_from_syserror ();
+  *stream_addr = NULL;
+  return add_body (ctx, data, datalen);
+}
+
+
+/* Add a new MIME container.  The caller needs to provide the media
+ * and media-subtype in MEDIATYPE.  If MEDIATYPE is NULL
+ * "multipart/mixed" is assumed.  This function will then add a
+ * Content-Type header with that media type and an approriate boundary
+ * string to the parent part.  */
+gpg_error_t
+mime_maker_add_container (mime_maker_t ctx, const char *mediatype)
+{
+  gpg_error_t err;
+  part_t part;
+
+  if (!mediatype)
+    mediatype = "multipart/mixed";
+
+  err = ensure_part (ctx, NULL);
+  if (err)
+    return err;
+  part = ctx->current_part;
+  if (part->body)
+    return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */
+  if (part->child || part->mediatype || part->boundary)
+    return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */
+
+  /* If a content type has not yet been set, do it now.  The boundary
+   * will be added while writing the headers.  */
+  if (!have_header (ctx->mail, "Content-Type"))
+    {
+      err = add_header (ctx->mail, "Content-Type", mediatype);
+      if (err)
+        return err;
+    }
+
+  /* Create a child node.  */
+  part->child = xtrycalloc (1, sizeof *part->child);
+  if (!part->child)
+    return gpg_error_from_syserror ();
+  part->child->headers_tail = &part->child->headers;
+
+  part->mediatype = xtrystrdup (mediatype);
+  if (!part->mediatype)
+    {
+      err = gpg_error_from_syserror ();
+      xfree (part->child);
+      part->child = NULL;
+      return err;
+    }
+
+  part->boundary = generate_boundary (ctx);
+  if (!part->boundary)
+    {
+      err = gpg_error_from_syserror ();
+      xfree (part->child);
+      part->child = NULL;
+      xfree (part->mediatype);
+      part->mediatype = NULL;
+      return err;
+    }
+
+  part = part->child;
+  ctx->current_part = part;
+
+  return 0;
+}
+
+
+/* Write the Content-Type header with the boundary value.  */
+static gpg_error_t
+write_ct_with_boundary (mime_maker_t ctx,
+                        const char *value, const char *boundary)
+{
+  const char *s;
+
+  if (!*value)
+    return gpg_error (GPG_ERR_INV_VALUE);  /* Empty string.  */
+
+  for (s=value + strlen (value) - 1;
+       (s >= value
+        && (*s == ' ' || *s == '\t' || *s == '\n'));
+       s--)
+    ;
+  if (!(s >= value))
+    return gpg_error (GPG_ERR_INV_VALUE);  /* Only spaces.  */
+
+  /* Fixme: We should use a dedicated header write functions which
+   * properly wraps the header.  */
+  es_fprintf (ctx->outfp, "Content-Type: %s%s\n\tboundary=\"%s\"\n",
+              value,
+              (*s == ';')? "":";",
+              boundary);
+  return 0;
+}
+
+
+/* Recursive worker for mime_maker_make.  */
+static gpg_error_t
+write_tree (mime_maker_t ctx, part_t parent, part_t part)
+{
+  gpg_error_t err;
+  header_t hdr;
+
+  for (; part; part = part->next)
+    {
+      for (hdr = part->headers; hdr; hdr = hdr->next)
+        {
+          if (part->child && !strcmp (hdr->name, "Content-Type"))
+            write_ct_with_boundary (ctx, hdr->value, part->boundary);
+          else
+            es_fprintf (ctx->outfp, "%s: %s\n", hdr->name, hdr->value);
+        }
+      es_fputc ('\n', ctx->outfp);
+      if (part->body)
+        {
+          if (es_write (ctx->outfp, part->body, part->bodylen, NULL))
+            return gpg_error_from_syserror ();
+        }
+      if (part->child)
+        {
+          log_assert (part->boundary);
+          if (es_fprintf (ctx->outfp, "\n--%s\n", part->boundary) < 0)
+            return gpg_error_from_syserror ();
+          err = write_tree (ctx, part, part->child);
+          if (err)
+            return err;
+          if (es_fprintf (ctx->outfp, "\n--%s--\n", part->boundary) < 0)
+            return gpg_error_from_syserror ();
+        }
+
+      if (part->next)
+        {
+          log_assert (parent && parent->boundary);
+          if (es_fprintf (ctx->outfp, "\n--%s\n", parent->boundary) < 0)
+            return gpg_error_from_syserror ();
+        }
+    }
+  return 0;
+}
+
+
+/* Add headers we always require.  */
+static gpg_error_t
+add_missing_headers (mime_maker_t ctx)
+{
+  gpg_error_t err;
+
+  if (!ctx->mail)
+    return gpg_error (GPG_ERR_NO_DATA);
+  if (!have_header (ctx->mail, "MIME-Version"))
+    {
+      /* Even if a Content-Type has never been set, we want to
+       * announce that we do MIME.  */
+      err = add_header (ctx->mail, "MIME-Version", "1.0");
+      if (err)
+        goto leave;
+    }
+
+  if (!have_header (ctx->mail, "Date"))
+    {
+      char *p = rfctimestamp (make_timestamp ());
+      if (!p)
+        err = gpg_error_from_syserror ();
+      else
+        err = add_header (ctx->mail, "Date", p);
+      xfree (p);
+      if (err)
+        goto leave;
+    }
+
+
+ leave:
+  return err;
+}
+
+
+/* Create message from the tree MIME and write it to FP.  Noet that
+ * the output uses only a LF and a later called sendmail(1) is
+ * expected to convert them to network line endings.  */
+gpg_error_t
+mime_maker_make (mime_maker_t ctx, estream_t fp)
+{
+  gpg_error_t err;
+
+  err = add_missing_headers (ctx);
+  if (err)
+    return err;
+
+  ctx->outfp = fp;
+  err = write_tree (ctx, NULL, ctx->mail);
+
+  ctx->outfp = NULL;
+  return err;
+}
diff --git a/tools/mime-maker.h b/tools/mime-maker.h
new file mode 100644 (file)
index 0000000..b21f7dd
--- /dev/null
@@ -0,0 +1,43 @@
+/* mime-maker.h - Create MIME structures
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GNUPG_MIME_MAKER_H
+#define GNUPG_MIME_MAKER_H
+
+struct mime_maker_context_s;
+typedef struct mime_maker_context_s *mime_maker_t;
+
+gpg_error_t mime_maker_new (mime_maker_t *r_ctx, void *cookie);
+void        mime_maker_release (mime_maker_t ctx);
+
+void mime_maker_set_verbose (mime_maker_t ctx, int level);
+
+void mime_maker_dump_tree (mime_maker_t ctx);
+
+gpg_error_t mime_maker_add_header (mime_maker_t ctx,
+                                   const char *name, const char *value);
+gpg_error_t mime_maker_add_body (mime_maker_t ctx, const char *string);
+gpg_error_t mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr);
+gpg_error_t mime_maker_add_container (mime_maker_t ctx, const char *mediatype);
+
+gpg_error_t mime_maker_make (mime_maker_t ctx, estream_t fp);
+
+
+
+#endif /*GNUPG_MIME_MAKER_H*/
diff --git a/tools/mime-parser.c b/tools/mime-parser.c
new file mode 100644 (file)
index 0000000..5f3659e
--- /dev/null
@@ -0,0 +1,772 @@
+/* mime-parser.c - Parse MIME structures (high level rfc822 parser).
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+#include "rfc822parse.h"
+#include "mime-parser.h"
+
+
+enum pgpmime_states
+  {
+    PGPMIME_NONE = 0,
+    PGPMIME_WAIT_ENCVERSION,
+    PGPMIME_IN_ENCVERSION,
+    PGPMIME_WAIT_ENCDATA,
+    PGPMIME_IN_ENCDATA,
+    PGPMIME_GOT_ENCDATA,
+    PGPMIME_WAIT_SIGNEDDATA,
+    PGPMIME_IN_SIGNEDDATA,
+    PGPMIME_WAIT_SIGNATURE,
+    PGPMIME_IN_SIGNATURE,
+    PGPMIME_GOT_SIGNATURE,
+    PGPMIME_INVALID
+  };
+
+
+/* Definition of the mime parser object.  */
+struct mime_parser_context_s
+{
+  void *cookie;                /* Cookie passed to all callbacks.  */
+
+  /* The callback to announce a new part.  */
+  gpg_error_t (*new_part) (void *cookie,
+                           const char *mediatype,
+                           const char *mediasubtype);
+  /* The callback to return data of a part.  */
+  gpg_error_t (*part_data) (void *cookie,
+                            const void *data,
+                            size_t datalen);
+  /* The callback to collect encrypted data.  */
+  gpg_error_t (*collect_encrypted) (void *cookie, const char *data);
+  /* The callback to collect signed data.  */
+  gpg_error_t (*collect_signeddata) (void *cookie, const char *data);
+  /* The callback to collect a signature.  */
+  gpg_error_t (*collect_signature) (void *cookie, const char *data);
+
+  /* Helper to convey error codes from user callbacks.  */
+  gpg_error_t err;
+
+  int nesting_level;           /* The current nesting level.  */
+  int hashing_at_level;        /* The nesting level at which we are hashing. */
+  enum pgpmime_states pgpmime; /* Current PGP/MIME state.  */
+  unsigned int delay_hashing:1;/* Helper for PGPMIME_IN_SIGNEDDATA. */
+  unsigned int want_part:1;    /* Return the current part.  */
+  unsigned int decode_part:2;  /* Decode the part.  1 = QP, 2 = Base64. */
+
+  unsigned int verbose:1;      /* Enable verbose mode.  */
+  unsigned int debug:1;        /* Enable debug mode.  */
+
+  /* Flags to help with debug output.  */
+  struct {
+    unsigned int n_skip;         /* Skip showing these number of lines.  */
+    unsigned int header:1;       /* Show the header lines.  */
+    unsigned int data:1;         /* Show the data lines.  */
+    unsigned int as_note:1;      /* Show the next data line as a note.  */
+    unsigned int boundary : 1;
+  } show;
+
+  struct b64state *b64state;     /* NULL or malloced Base64 decoder state.  */
+
+  /* A buffer for reading a mail line,  */
+  char line[5000];
+};
+
+
+/* Print the event received by the parser for debugging.  */
+static void
+show_message_parser_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_LEVEL_DOWN: s= "Level_Down"; break;
+    case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
+    case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
+    case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+    case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
+    case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
+    case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
+    default: s= "[unknown event]"; break;
+    }
+  log_debug ("*** RFC822 event %s\n", s);
+}
+
+
+/* Do in-place decoding of quoted-printable data of LENGTH in BUFFER.
+   Returns the new length of the buffer and stores true at R_SLBRK if
+   the line ended with a soft line break; false is stored if not.
+   This fucntion asssumes that a complete line is passed in
+   buffer.  */
+static size_t
+qp_decode (char *buffer, size_t length, int *r_slbrk)
+{
+  char *d, *s;
+
+  if (r_slbrk)
+    *r_slbrk = 0;
+
+  /* Fixme:  We should remove trailing white space first.  */
+  for (s=d=buffer; length; length--)
+    {
+      if (*s == '=')
+        {
+          if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2))
+            {
+              s++;
+              *(unsigned char*)d++ = xtoi_2 (s);
+              s += 2;
+              length -= 2;
+            }
+          else if (length > 2 && s[1] == '\r' && s[2] == '\n')
+            {
+              /* Soft line break.  */
+              s += 3;
+              length -= 2;
+              if (r_slbrk && length == 1)
+                *r_slbrk = 1;
+            }
+          else if (length > 1 && s[1] == '\n')
+            {
+              /* Soft line break with only a Unix line terminator. */
+              s += 2;
+              length -= 1;
+              if (r_slbrk && length == 1)
+                *r_slbrk = 1;
+            }
+          else if (length == 1)
+            {
+              /* Soft line break at the end of the line. */
+              s += 1;
+              if (r_slbrk)
+                *r_slbrk = 1;
+            }
+          else
+            *d++ = *s++;
+        }
+      else
+        *d++ = *s++;
+    }
+
+  return d - buffer;
+}
+
+
+/* This function is called by parse_mail to communicate events.  This
+ * callback communicates with the caller using a structure passed in
+ * OPAQUE.  Should return 0 on success or set ERRNO and return -1. */
+static int
+parse_message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
+{
+  mime_parser_t ctx = opaque;
+  const char *s;
+  int rc = 0;
+
+  if (ctx->debug)
+    show_message_parser_event (event);
+
+  if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
+    {
+      /* We need to check here whether to start collecting signed data
+       * because attachments might come without header lines and thus
+       * we won't see the BEGIN_HEADER event.  */
+      if (ctx->pgpmime == PGPMIME_WAIT_SIGNEDDATA)
+        {
+          if (ctx->debug)
+            log_debug ("begin_hash\n");
+          ctx->hashing_at_level = ctx->nesting_level;
+          ctx->pgpmime = PGPMIME_IN_SIGNEDDATA;
+          ctx->delay_hashing = 0;
+        }
+    }
+
+  if (event == RFC822PARSE_OPEN)
+    {
+      /* Initialize for a new message. */
+      ctx->show.header = 1;
+    }
+  else if (event == RFC822PARSE_T2BODY)
+    {
+      rfc822parse_field_t field;
+
+      ctx->want_part = 0;
+      ctx->decode_part = 0;
+      field = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (field)
+        {
+          const char *s1, *s2;
+
+          s1 = rfc822parse_query_media_type (field, &s2);
+          if (s1)
+            {
+              if (ctx->verbose)
+                log_debug ("h media: %*s%s %s\n",
+                           ctx->nesting_level*2, "", s1, s2);
+              if (ctx->pgpmime == PGPMIME_WAIT_ENCVERSION)
+                {
+                  if (!strcmp (s1, "application")
+                      && !strcmp (s2, "pgp-encrypted"))
+                    {
+                      if (ctx->debug)
+                        log_debug ("c begin_encversion\n");
+                      ctx->pgpmime = PGPMIME_IN_ENCVERSION;
+                    }
+                  else
+                    {
+                      log_error ("invalid PGP/MIME structure;"
+                                 " expected '%s', got '%s/%s'\n",
+                                 "application/pgp-encrypted", s1, s2);
+                      ctx->pgpmime = PGPMIME_INVALID;
+                    }
+                }
+              else if (ctx->pgpmime == PGPMIME_WAIT_ENCDATA)
+                {
+                  if (!strcmp (s1, "application")
+                      && !strcmp (s2, "octet-stream"))
+                    {
+                      if (ctx->debug)
+                        log_debug ("c begin_encdata\n");
+                      ctx->pgpmime = PGPMIME_IN_ENCDATA;
+                    }
+                  else
+                    {
+                      log_error ("invalid PGP/MIME structure;"
+                                 " expected '%s', got '%s/%s'\n",
+                                 "application/octet-stream", s1, s2);
+                      ctx->pgpmime = PGPMIME_INVALID;
+                    }
+                }
+              else if (ctx->pgpmime == PGPMIME_WAIT_SIGNATURE)
+                {
+                  if (!strcmp (s1, "application")
+                      && !strcmp (s2, "pgp-signature"))
+                    {
+                      if (ctx->debug)
+                        log_debug ("c begin_signature\n");
+                      ctx->pgpmime = PGPMIME_IN_SIGNATURE;
+                    }
+                  else
+                    {
+                      log_error ("invalid PGP/MIME structure;"
+                                 " expected '%s', got '%s/%s'\n",
+                                 "application/pgp-signature", s1, s2);
+                      ctx->pgpmime = PGPMIME_INVALID;
+                    }
+                }
+              else if (!strcmp (s1, "multipart")
+                       && !strcmp (s2, "encrypted"))
+                {
+                  s = rfc822parse_query_parameter (field, "protocol", 0);
+                  if (s)
+                    {
+                      if (ctx->debug)
+                        log_debug ("h encrypted.protocol: %s\n", s);
+                      if (!strcmp (s, "application/pgp-encrypted"))
+                        {
+                          if (ctx->pgpmime)
+                            log_error ("note: "
+                                       "ignoring nested PGP/MIME signature\n");
+                          else
+                            ctx->pgpmime = PGPMIME_WAIT_ENCVERSION;
+                        }
+                      else if (ctx->verbose)
+                        log_debug ("# this protocol is not supported\n");
+                    }
+                }
+              else if (!strcmp (s1, "multipart")
+                       && !strcmp (s2, "signed"))
+                {
+                  s = rfc822parse_query_parameter (field, "protocol", 1);
+                  if (s)
+                    {
+                      if (ctx->debug)
+                        log_debug ("h signed.protocol: %s\n", s);
+                      if (!strcmp (s, "application/pgp-signature"))
+                        {
+                          if (ctx->pgpmime)
+                            log_error ("note: "
+                                       "ignoring nested PGP/MIME signature\n");
+                          else
+                            ctx->pgpmime = PGPMIME_WAIT_SIGNEDDATA;
+                        }
+                      else if (ctx->verbose)
+                        log_debug ("# this protocol is not supported\n");
+                    }
+                }
+              else if (ctx->new_part)
+                {
+                  ctx->err = ctx->new_part (ctx->cookie, s1, s2);
+                  if (!ctx->err)
+                    ctx->want_part = 1;
+                  else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
+                    ctx->err = 0;
+                  else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
+                    {
+                      ctx->want_part = ctx->decode_part = 1;
+                      ctx->err = 0;
+                    }
+                }
+            }
+          else
+            {
+              if (ctx->debug)
+                log_debug ("h media: %*s none\n", ctx->nesting_level*2, "");
+              if (ctx->new_part)
+                {
+                  ctx->err = ctx->new_part (ctx->cookie, "", "");
+                  if (!ctx->err)
+                    ctx->want_part = 1;
+                  else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
+                    ctx->err = 0;
+                  else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
+                    {
+                      ctx->want_part = ctx->decode_part = 1;
+                      ctx->err = 0;
+                    }
+                }
+            }
+
+          rfc822parse_release_field (field);
+        }
+      else
+        {
+          if (ctx->verbose)
+            log_debug ("h media: %*stext plain [assumed]\n",
+                       ctx->nesting_level*2, "");
+          if (ctx->new_part)
+            {
+              ctx->err = ctx->new_part (ctx->cookie, "text", "plain");
+              if (!ctx->err)
+                ctx->want_part = 1;
+              else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
+                ctx->err = 0;
+              else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
+                {
+                  ctx->want_part = ctx->decode_part = 1;
+                  ctx->err = 0;
+                }
+            }
+        }
+
+      /* Figure out the encoding if needed.  */
+      if (ctx->decode_part)
+        {
+          char *value;
+          size_t valueoff;
+
+          ctx->decode_part = 0; /* Fallback for unknown encoding.  */
+          value = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1,
+                                         &valueoff);
+          if (value)
+            {
+              if (!stricmp (value+valueoff, "quoted-printable"))
+                ctx->decode_part = 1;
+              else if (!stricmp (value+valueoff, "base64"))
+                {
+                  ctx->decode_part = 2;
+                  if (ctx->b64state)
+                    b64dec_finish (ctx->b64state); /* Reuse state.  */
+                  else
+                    {
+                      ctx->b64state = xtrymalloc (sizeof *ctx->b64state);
+                      if (!ctx->b64state)
+                        rc = gpg_error_from_syserror ();
+                    }
+                  if (!rc)
+                    rc = b64dec_start (ctx->b64state, NULL);
+                }
+              free (value); /* Right, we need a plain free.  */
+            }
+        }
+
+      ctx->show.header = 0;
+      ctx->show.data = 1;
+      ctx->show.n_skip = 1;
+    }
+  else if (event == RFC822PARSE_PREAMBLE)
+    ctx->show.as_note = 1;
+  else if (event == RFC822PARSE_LEVEL_DOWN)
+    {
+      if (ctx->debug)
+        log_debug ("b down\n");
+      ctx->nesting_level++;
+    }
+  else if (event == RFC822PARSE_LEVEL_UP)
+    {
+      if (ctx->debug)
+        log_debug ("b up\n");
+      if (ctx->nesting_level)
+        ctx->nesting_level--;
+      else
+        log_error ("invalid structure (bad nesting level)\n");
+    }
+  else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
+    {
+      ctx->show.data = 0;
+      ctx->show.boundary = 1;
+      if (event == RFC822PARSE_BOUNDARY)
+        {
+          ctx->show.header = 1;
+          ctx->show.n_skip = 1;
+          if (ctx->debug)
+            log_debug ("b part\n");
+        }
+      else if (ctx->debug)
+        log_debug ("b last\n");
+
+      if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
+        {
+          if (ctx->debug)
+            log_debug ("c end_encdata\n");
+          ctx->pgpmime = PGPMIME_GOT_ENCDATA;
+          /* FIXME: We should assert (event == LAST_BOUNDARY).  */
+        }
+      else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA
+               && ctx->nesting_level == ctx->hashing_at_level)
+        {
+          if (ctx->debug)
+            log_debug ("c end_hash\n");
+          ctx->pgpmime = PGPMIME_WAIT_SIGNATURE;
+          if (ctx->collect_signeddata)
+            ctx->err = ctx->collect_signeddata (ctx->cookie, NULL);
+        }
+      else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
+        {
+          if (ctx->debug)
+            log_debug ("c end_signature\n");
+          ctx->pgpmime = PGPMIME_GOT_SIGNATURE;
+          /* FIXME: We should assert (event == LAST_BOUNDARY).  */
+        }
+      else if (ctx->want_part)
+        {
+          if (ctx->part_data)
+            {
+              /* FIXME: We may need to flush things.  */
+              ctx->err = ctx->part_data (ctx->cookie, NULL, 0);
+            }
+          ctx->want_part = 0;
+        }
+    }
+
+  return rc;
+}
+
+
+/* Create a new mime parser object.  COOKIE is a values which will be
+ * used as first argument for all callbacks registered with this
+ * parser object.  */
+gpg_error_t
+mime_parser_new (mime_parser_t *r_parser, void *cookie)
+{
+  mime_parser_t ctx;
+
+  *r_parser = NULL;
+
+  ctx = xtrycalloc (1, sizeof *ctx);
+  if (!ctx)
+    return gpg_error_from_syserror ();
+  ctx->cookie = cookie;
+
+  *r_parser = ctx;
+  return 0;
+}
+
+
+/* Release a mime parser object.  */
+void
+mime_parser_release (mime_parser_t ctx)
+{
+  if (!ctx)
+    return;
+
+  if (ctx->b64state)
+    {
+      b64dec_finish (ctx->b64state);
+      xfree (ctx->b64state);
+    }
+  xfree (ctx);
+}
+
+
+/* Set verbose and debug mode.  */
+void
+mime_parser_set_verbose (mime_parser_t ctx, int level)
+{
+  if (!level)
+    {
+      ctx->verbose = 0;
+      ctx->debug = 0;
+    }
+  else
+    {
+      ctx->verbose = 1;
+      if (level > 10)
+        ctx->debug = 1;
+    }
+}
+
+
+/* Set the callback used to announce a new part.  It will be called
+ * with the media type and media subtype of the part.  If no
+ * Content-type header was given both values are the empty string.
+ * The callback should return 0 on success or an error code.  The
+ * error code GPG_ERR_FALSE indicates that the caller is not
+ * interested in the part and data shall not be returned via a
+ * registered part_data callback.  The error code GPG_ERR_TRUE
+ * indicates that the parts shall be redurned in decoded format
+ * (i.e. base64 or QP encoding is removed).  */
+void
+mime_parser_set_new_part (mime_parser_t ctx,
+                          gpg_error_t (*fnc) (void *cookie,
+                                              const char *mediatype,
+                                              const char *mediasubtype))
+{
+  ctx->new_part = fnc;
+}
+
+
+/* Set the callback used to return the data of a part to the caller.
+ * The end of the part is indicated by passing NUL for DATA.  */
+void
+mime_parser_set_part_data (mime_parser_t ctx,
+                           gpg_error_t (*fnc) (void *cookie,
+                                               const void *data,
+                                               size_t datalen))
+{
+  ctx->part_data = fnc;
+}
+
+
+/* Set the callback to collect encrypted data.  A NULL passed to the
+ * callback indicates the end of the encrypted data; the callback may
+ * then decrypt the collected data.  */
+void
+mime_parser_set_collect_encrypted (mime_parser_t ctx,
+                                   gpg_error_t (*fnc) (void *cookie,
+                                                       const char *data))
+{
+  ctx->collect_encrypted = fnc;
+}
+
+
+/* Set the callback to collect signed data.  A NULL passed to the
+ * callback indicates the end of the signed data.  */
+void
+mime_parser_set_collect_signeddata (mime_parser_t ctx,
+                                    gpg_error_t (*fnc) (void *cookie,
+                                                        const char *data))
+{
+  ctx->collect_signeddata = fnc;
+}
+
+
+/* Set the callback to collect the signature.  A NULL passed to the
+ * callback indicates the end of the signature; the callback may the
+ * verify the signature.  */
+void
+mime_parser_set_collect_signature (mime_parser_t ctx,
+                                   gpg_error_t (*fnc) (void *cookie,
+                                                       const char *data))
+{
+  ctx->collect_signature = fnc;
+}
+
+
+/* Read and parse a message from FP and call the appropriate
+ * callbacks.  */
+gpg_error_t
+mime_parser_parse (mime_parser_t ctx, estream_t fp)
+{
+  gpg_error_t err;
+  rfc822parse_t msg = NULL;
+  unsigned int lineno = 0;
+  size_t length, nbytes;
+  char *line;
+
+  line = ctx->line;
+
+  msg = rfc822parse_open (parse_message_cb, ctx);
+  if (!msg)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("can't open mail parser: %s", gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Fixme: We should not use fgets because it can't cope with
+     embedded nul characters. */
+  while (es_fgets (ctx->line, sizeof (ctx->line), fp))
+    {
+      lineno++;
+      if (lineno == 1 && !strncmp (line, "From ", 5))
+        continue;  /* We better ignore a leading From line. */
+
+      length = strlen (line);
+      if (length && line[length - 1] == '\n')
+       line[--length] = 0;
+      else
+        log_error ("mail parser detected too long or"
+                   " non terminated last line (lnr=%u)\n", lineno);
+      if (length && line[length - 1] == '\r')
+       line[--length] = 0;
+
+      ctx->err = 0;
+      if (rfc822parse_insert (msg, line, length))
+        {
+          err = gpg_error_from_syserror ();
+          log_error ("mail parser failed: %s", gpg_strerror (err));
+          goto leave;
+        }
+      if (ctx->err)
+        {
+          /* Error from a callback detected.  */
+          err = ctx->err;
+          goto leave;
+        }
+
+
+      /* Debug output.  Note that the boundary is shown before n_skip
+       * is evaluated.  */
+      if (ctx->show.boundary)
+        {
+          if (ctx->debug)
+            log_debug ("# Boundary: %s\n", line);
+          ctx->show.boundary = 0;
+        }
+      if (ctx->show.n_skip)
+        ctx->show.n_skip--;
+      else if (ctx->show.data)
+        {
+          if (ctx->show.as_note)
+            {
+              if (ctx->verbose)
+                log_debug ("# Note: %s\n", line);
+              ctx->show.as_note = 0;
+            }
+          else if (ctx->debug)
+            log_debug ("# Data: %s\n", line);
+        }
+      else if (ctx->show.header && ctx->verbose)
+        log_debug ("# Header: %s\n", line);
+
+      if (ctx->pgpmime == PGPMIME_IN_ENCVERSION)
+        {
+          trim_trailing_spaces (line);
+          if (!*line)
+            ;  /* Skip empty lines.  */
+          else if (!strcmp (line, "Version: 1"))
+            ctx->pgpmime = PGPMIME_WAIT_ENCDATA;
+          else
+            {
+              log_error ("invalid PGP/MIME structure;"
+                         " garbage in pgp-encrypted part ('%s')\n", line);
+              ctx->pgpmime = PGPMIME_INVALID;
+            }
+        }
+      else if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
+        {
+          if (ctx->collect_encrypted)
+            {
+              err = ctx->collect_encrypted (ctx->cookie, line);
+              if (!err)
+                err = ctx->collect_encrypted (ctx->cookie, "\r\n");
+              if (err)
+                goto leave;
+            }
+        }
+      else if (ctx->pgpmime == PGPMIME_GOT_ENCDATA)
+        {
+          ctx->pgpmime = PGPMIME_NONE;
+          if (ctx->collect_encrypted)
+            ctx->collect_encrypted (ctx->cookie, NULL);
+        }
+      else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA)
+        {
+          /* If we are processing signed data, store the signed data.
+           * We need to delay the hashing of the CR/LF because the
+           * last line ending belongs to the next boundary.  This is
+           * the reason why we can't use the PGPMIME state as a
+           * condition.  */
+          if (ctx->debug)
+            log_debug ("# hashing %s'%s'\n",
+                       ctx->delay_hashing? "CR,LF+":"", line);
+          if (ctx->collect_signeddata)
+            {
+              if (ctx->delay_hashing)
+                ctx->collect_signeddata (ctx->cookie, "\r\n");
+              ctx->collect_signeddata (ctx->cookie, line);
+            }
+          ctx->delay_hashing = 1;
+        }
+      else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
+        {
+          if (ctx->collect_signeddata)
+            {
+              ctx->collect_signature (ctx->cookie, line);
+              ctx->collect_signature (ctx->cookie, "\r\n");
+            }
+        }
+      else if (ctx->pgpmime == PGPMIME_GOT_SIGNATURE)
+        {
+          ctx->pgpmime = PGPMIME_NONE;
+          if (ctx->collect_signeddata)
+            ctx->collect_signature (ctx->cookie, NULL);
+        }
+      else if (ctx->want_part)
+        {
+          if (ctx->part_data)
+            {
+              if (ctx->decode_part == 1)
+                {
+                  length = qp_decode (line, length, NULL);
+                }
+              else if (ctx->decode_part == 2)
+                {
+                  log_assert (ctx->b64state);
+                  err = b64dec_proc (ctx->b64state, line, length, &nbytes);
+                  if (err)
+                    goto leave;
+                  length = nbytes;
+                }
+              err = ctx->part_data (ctx->cookie, line, length);
+              if (err)
+                goto leave;
+            }
+        }
+    }
+
+  rfc822parse_close (msg);
+  msg = NULL;
+  err = 0;
+
+ leave:
+  rfc822parse_cancel (msg);
+  return err;
+}
diff --git a/tools/mime-parser.h b/tools/mime-parser.h
new file mode 100644 (file)
index 0000000..ab0d792
--- /dev/null
@@ -0,0 +1,52 @@
+/* mime-parser.h - Parse MIME structures (high level rfc822 parser).
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GNUPG_MIME_PARSER_H
+#define GNUPG_MIME_PARSER_H
+
+struct mime_parser_context_s;
+typedef struct mime_parser_context_s *mime_parser_t;
+
+gpg_error_t mime_parser_new (mime_parser_t *r_ctx, void *cookie);
+void        mime_parser_release (mime_parser_t ctx);
+
+void mime_parser_set_verbose (mime_parser_t ctx, int level);
+void mime_parser_set_new_part (mime_parser_t ctx,
+                               gpg_error_t (*fnc) (void *cookie,
+                                                   const char *mediatype,
+                                                   const char *mediasubtype));
+void mime_parser_set_part_data (mime_parser_t ctx,
+                                gpg_error_t (*fnc) (void *cookie,
+                                                    const void *data,
+                                                    size_t datalen));
+void mime_parser_set_collect_encrypted (mime_parser_t ctx,
+                                        gpg_error_t (*fnc) (void *cookie,
+                                                            const char *data));
+void mime_parser_set_collect_signeddata (mime_parser_t ctx,
+                                         gpg_error_t (*fnc) (void *cookie,
+                                                             const char *data));
+void mime_parser_set_collect_signature (mime_parser_t ctx,
+                                        gpg_error_t (*fnc) (void *cookie,
+                                                            const char *data));
+
+gpg_error_t mime_parser_parse (mime_parser_t ctx, estream_t fp);
+
+
+
+#endif /*GNUPG_MIME_PARSER_H*/
index 8bb5536..c5579fe 100644 (file)
@@ -1,6 +1,6 @@
 /* rfc822parse.h - Simple mail and MIME parser
  *     Copyright (C) 1999 Werner Koch, Duesseldorf
- *      Copyright (C) 2003, g10 Code GmbH
+ *      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 Lesser General Public License