Added MIME parser. We can now display a text/plain part.
authorWerner Koch <wk@gnupg.org>
Tue, 6 Sep 2005 14:50:21 +0000 (14:50 +0000)
committerWerner Koch <wk@gnupg.org>
Tue, 6 Sep 2005 14:50:21 +0000 (14:50 +0000)
src/ChangeLog
src/Makefile.am
src/engine-gpgme.c
src/engine.h
src/gpgmsg.cpp
src/msgcache.c
src/pgpmime.c [new file with mode: 0644]
src/pgpmime.h [new file with mode: 0644]
src/rfc822parse.c [new file with mode: 0644]
src/rfc822parse.h [new file with mode: 0644]

index c042b85..be53f83 100644 (file)
@@ -1,3 +1,14 @@
+2005-09-06  Werner Koch  <wk@g10code.com>
+
+       * engine-gpgme.c (op_decrypt_stream): Factored most code out to ..
+       (decrypt_stream): .. new.
+       (op_decrypt_stream_to_buffer): Simplified accordingly.  Fixed
+       possible buffer overflow when trying to make it a string. 
+       (op_decrypt_stream_to_gpgme): New.
+
+       * pgpmime.c, pgpmime.h: New.
+       * rfc822parse.h, rfc822parse.c: New.  Taken from GnuPG 1.9.
+
 2005-09-06  Timo Schulz  <ts@g10code.com>
 
        * config-dialog.c (get_open_file_name): Correctly terminated filter.
index a6a29d8..68f3ab6 100644 (file)
@@ -30,8 +30,10 @@ gpgol_SOURCES = \
        myexchext.h                 \
        display.cpp display.h       \
        gpgmsg.cpp gpgmsg.hh        \
+       pgpmime.c pgpmime.h         \
        msgcache.c msgcache.h       \
         engine-gpgme.c engine.h    \
+       rfc822parse.c rfc822parse.h \
         common.c intern.h          \
        passcache.c passcache.h     \
         HashTable.cpp HashTable.h   \
index e53a393..7baa655 100644 (file)
@@ -264,10 +264,12 @@ op_encrypt (const char *inbuf, char **outbuf, gpgme_key_t *keys,
     err = check_encrypt_result (ctx, err);
   else
     {
-      size_t n = 0;    
-      *outbuf = gpgme_data_release_and_get_mem (out, &n);
-      (*outbuf)[n] = 0;
-      out = NULL;
+      /* Return the buffer but first make sure it is a string. */
+      if (gpgme_data_write (out, "", 1) == 1)
+        {
+          *outbuf = gpgme_data_release_and_get_mem (out, NULL);
+          out = NULL; 
+        }
     }
 
 
@@ -392,10 +394,12 @@ op_sign (const char *inbuf, char **outbuf, int mode,
 
   if (!err)
     {
-      size_t n = 0;
-      *outbuf = gpgme_data_release_and_get_mem (out, &n);
-      (*outbuf)[n] = 0;
-      out = NULL;
+      /* Return the buffer but first make sure it is a string. */
+      if (gpgme_data_write (out, "", 1) == 1)
+        {
+          *outbuf = gpgme_data_release_and_get_mem (out, NULL);
+          out = NULL; 
+        }
     }
 
  leave:
@@ -507,12 +511,14 @@ op_decrypt (const char *inbuf, char **outbuf, int ttl, const char *filename)
   if (!err) 
     {
       /* Decryption succeeded.  Store the result at OUTBUF. */
-      size_t n = 0;
       gpgme_verify_result_t res;
 
-      *outbuf = gpgme_data_release_and_get_mem (out, &n);
-      (*outbuf)[n] = 0; /* Make sure it is really a string. */
-      out = NULL; /* (That GPGME object is no any longer valid.) */
+      /* Return the buffer but first make sure it is a string. */
+      if (gpgme_data_write (out, "", 1) == 1)
+        {
+          *outbuf = gpgme_data_release_and_get_mem (out, NULL);
+          out = NULL; 
+        }
 
       /* Now check the state of any signature. */
       res = gpgme_op_verify_result (ctx);
@@ -551,40 +557,25 @@ leave:
   return err;
 }
 
-/* Decrypt the stream INSTREAM directly to the stream OUTSTREAM.
-   Returns 0 on success or an gpgme error code on failure.  If
-   FILENAME is not NULL it will be displayed along with status
-   outputs. */
-int
-op_decrypt_stream (LPSTREAM instream, LPSTREAM outstream, int ttl,
-                   const char *filename)
+
+/* Decrypt the GPGME data object IN into the data object OUT.  Returns
+   0 on success or an gpgme error code on failure.  If FILENAME is not
+   NULL it will be displayed along with status outputs. */
+static int
+decrypt_stream (gpgme_data_t in, gpgme_data_t out, int ttl,
+                const char *filename)
 {    
   struct decrypt_key_s dk;
-  struct gpgme_data_cbs cbs;
-  gpgme_data_t in = NULL;
-  gpgme_data_t out = NULL;    
   gpgme_ctx_t ctx = NULL;
   gpgme_error_t err;
   
-  memset (&cbs, 0, sizeof cbs);
-  cbs.read = stream_read_cb;
-  cbs.write = stream_write_cb;
-
   memset (&dk, 0, sizeof dk);
   dk.ttl = ttl;
 
-  err = gpgme_data_new_from_cbs (&in, &cbs, instream);
-  if (err)
-    goto fail;
-
   err = gpgme_new (&ctx);
   if (err)
     goto fail;
 
-  err = gpgme_data_new_from_cbs (&out, &cbs, outstream);
-  if (err)
-    goto fail;
-
   gpgme_set_passphrase_cb (ctx, passphrase_callback_box, &dk);
   dk.ctx = ctx;
   err = gpgme_op_decrypt (ctx, in, out);
@@ -623,15 +614,42 @@ op_decrypt_stream (LPSTREAM instream, LPSTREAM outstream, int ttl,
     err = gpg_error (GPG_ERR_CANCELED);
 
  fail:
+  if (ctx)
+    gpgme_release (ctx);
+  return err;
+}
+
+/* Decrypt the stream INSTREAM directly to the stream OUTSTREAM.
+   Returns 0 on success or an gpgme error code on failure.  If
+   FILENAME is not NULL it will be displayed along with status
+   outputs. */
+int
+op_decrypt_stream (LPSTREAM instream, LPSTREAM outstream, int ttl,
+                   const char *filename)
+{
+  struct gpgme_data_cbs cbs;
+  gpgme_data_t in = NULL;
+  gpgme_data_t out = NULL;    
+  gpgme_error_t err;
+  
+  memset (&cbs, 0, sizeof cbs);
+  cbs.read = stream_read_cb;
+  cbs.write = stream_write_cb;
+
+  err = gpgme_data_new_from_cbs (&in, &cbs, instream);
+  if (!err)
+    err = gpgme_data_new_from_cbs (&out, &cbs, outstream);
+  if (!err)
+    err = decrypt_stream (in, out, ttl, filename);
+
   if (in)
     gpgme_data_release (in);
   if (out)
     gpgme_data_release (out);
-  if (ctx)
-    gpgme_release (ctx);
   return err;
 }
 
+
 /* Decrypt the stream INSTREAM directly to the newly allocated buffer OUTBUF.
    Returns 0 on success or an gpgme error code on failure.  If
    FILENAME is not NULL it will be displayed along with status
@@ -640,11 +658,9 @@ int
 op_decrypt_stream_to_buffer (LPSTREAM instream, char **outbuf, int ttl,
                              const char *filename)
 {    
-  struct decrypt_key_s dk;
   struct gpgme_data_cbs cbs;
   gpgme_data_t in = NULL;
   gpgme_data_t out = NULL;    
-  gpgme_ctx_t ctx = NULL;
   gpgme_error_t err;
   
   *outbuf = NULL;
@@ -652,71 +668,50 @@ op_decrypt_stream_to_buffer (LPSTREAM instream, char **outbuf, int ttl,
   memset (&cbs, 0, sizeof cbs);
   cbs.read = stream_read_cb;
 
-  memset (&dk, 0, sizeof dk);
-  dk.ttl = ttl;
-
   err = gpgme_data_new_from_cbs (&in, &cbs, instream);
-  if (err)
-    goto fail;
-
-  err = gpgme_new (&ctx);
-  if (err)
-    goto fail;
-
-  err = gpgme_data_new (&out);
-  if (err)
-    goto fail;
-
-  gpgme_set_passphrase_cb (ctx, passphrase_callback_box, &dk);
-  dk.ctx = ctx;
-  err = gpgme_op_decrypt (ctx, in, out);
-  dk.ctx = NULL;
-  update_passphrase_cache (err, &dk);
-  /* Act upon the result of the decryption operation. */
-  if (!err) 
+  if (!err)
+    err = gpgme_data_new (&out);
+  if (!err)
+    err = decrypt_stream (in, out, ttl, filename);
+  if (!err)
     {
-      /* Decryption succeeded.  Store the result at OUTBUF. */
-      size_t n = 0;
-      gpgme_verify_result_t res;
+      /* Return the buffer but first make sure it is a string. */
+      if (gpgme_data_write (out, "", 1) == 1)
+        {
+          *outbuf = gpgme_data_release_and_get_mem (out, NULL);
+          out = NULL; 
+        }
+    }
 
-      *outbuf = gpgme_data_release_and_get_mem (out, &n);
-      (*outbuf)[n] = 0; /* Make sure it is really a string. */
-      out = NULL; /* (That GPGME object is no any longer valid.) */
+  if (in)
+    gpgme_data_release (in);
+  if (out)
+    gpgme_data_release (out);
+  return err;
+}
 
-      /* Now check the state of the signatures. */
-      res = gpgme_op_verify_result (ctx);
-      if (res && res->signatures)
-        verify_dialog_box (res, filename);
-    }
-  else if (gpgme_err_code (err) == GPG_ERR_DECRYPT_FAILED)
-    {
-      /* The decryption failed.  See whether we can determine the real
-         problem. */
-      gpgme_decrypt_result_t res;
-      res = gpgme_op_decrypt_result (ctx);
-      if (res != NULL && res->recipients != NULL &&
-          gpgme_err_code (res->recipients->status) == GPG_ERR_NO_SECKEY)
-        err = GPG_ERR_NO_SECKEY;
-      /* XXX: return the keyids */
-    }
-  else
-    {
-      /* Decryption failed for other reasons. */
-    }
 
+/* Decrypt the stream INSTREAM directly to the GPGME data object OUT.
+   Returns 0 on success or an gpgme error code on failure.  If
+   FILENAME is not NULL it will be displayed along with status
+   outputs. */
+int
+op_decrypt_stream_to_gpgme (LPSTREAM instream, gpgme_data_t out, int ttl,
+                            const char *filename)
+{
+  struct gpgme_data_cbs cbs;
+  gpgme_data_t in = NULL;
+  gpgme_error_t err;
+  
+  memset (&cbs, 0, sizeof cbs);
+  cbs.read = stream_read_cb;
 
-  /* If the callback indicated a cancel operation, set the error
-     accordingly. */
-  if (err && (dk.opts & OPT_FLAG_CANCEL))
-    err = gpg_error (GPG_ERR_CANCELED);
+  err = gpgme_data_new_from_cbs (&in, &cbs, instream);
+  if (!err)
+    err = decrypt_stream (in, out, ttl, filename);
 
- fail:
   if (in)
     gpgme_data_release (in);
-  if (out)
-    gpgme_data_release (out);
-  if (ctx)
-    gpgme_release (ctx);
   return err;
 }
 
@@ -757,12 +752,14 @@ op_verify (const char *inbuf, char **outbuf, const char *filename)
   err = gpgme_op_verify (ctx, in, NULL, out);
   if (!err)
     {
-      size_t n=0;
       if (outbuf) 
         {
-          *outbuf = gpgme_data_release_and_get_mem (out, &n);
-          (*outbuf)[n] = 0;
-          out = NULL;
+          /* Return the buffer but first make sure it is a string. */
+          if (gpgme_data_write (out, "", 1) == 1)
+            {
+              *outbuf = gpgme_data_release_and_get_mem (out, NULL);
+              out = NULL; 
+            }
        }
       res = gpgme_op_verify_result (ctx);
     }
index f7ee91e..89e2bd2 100644 (file)
@@ -62,6 +62,8 @@ int op_decrypt_stream (LPSTREAM instream, LPSTREAM outstream, int ttl,
                        const char *filename);
 int op_decrypt_stream_to_buffer (LPSTREAM instream, char **outbuf, int ttl,
                                  const char *filename);
+int op_decrypt_stream_to_gpgme (LPSTREAM instream, gpgme_data_t out, int ttl,
+                                const char *filename);
 
 int op_verify (const char *inbuf, char **outbuf, const char *filename);
 int op_verify_detached_sig (LPSTREAM data, const char *sig,
index c3ce273..3cdbf3c 100644 (file)
@@ -34,6 +34,7 @@
 #include "gpgmsg.hh"
 #include "util.h"
 #include "msgcache.h"
+#include "pgpmime.h"
 #include "engine.h"
 #include "display.h"
 
@@ -788,8 +789,8 @@ GpgMsgImpl::decrypt (HWND hwnd)
           return gpg_error (GPG_ERR_GENERAL);
         }
 
-      err = op_decrypt_stream_to_buffer (from, &plaintext,
-                                         opt.passwd_ttl, NULL);
+      err = pgpmime_decrypt (from, opt.passwd_ttl, &plaintext);
+      
       from->Release ();
       att->Release ();
       if (!err)
index bea15f4..4892771 100644 (file)
@@ -1,4 +1,4 @@
-/* msgcache.cpp - Implementation of a message cache.
+/* msgcache.c - Implementation of a message cache.
  *     Copyright (C) 2005 g10 Code GmbH
  *
  * This file is part of GPGol.
diff --git a/src/pgpmime.c b/src/pgpmime.c
new file mode 100644 (file)
index 0000000..151e46e
--- /dev/null
@@ -0,0 +1,332 @@
+/* pgpmime.c - Try to handle PGP/MIME for Outlook
+ *     Copyright (C) 2005 g10 Code GmbH
+ *
+ * This file is part of GPGol.
+ * 
+ * GPGol is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ * 
+ * GPGol 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/*
+   EXPLAIN what we are doing here.
+*/
+   
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include <windows.h>
+
+#include <gpgme.h>
+
+#include "mymapi.h"
+#include "mymapitags.h"
+
+#include "rfc822parse.h"
+#include "util.h"
+#include "pgpmime.h"
+#include "engine.h"
+
+
+/* The maximum length of a line we ar able to porcess.  RFC822 alows
+   only for 1000 bytes; thus 2000 seems to be a reasonable value. */
+#define LINEBUFSIZE 2000
+
+/* The context object we use to track information. */
+struct pgpmime_context
+{
+  rfc822parse_t msg;  /* The handle of the RFC822 parser. */
+
+  int nesting_level;  /* Current MIME nesting level. */
+  int in_data;        /* We are currently in data (body or attachment). */
+
+  
+  gpgme_data_t body;  /* NULL or a data object used to collect the
+                         body part we are going to display later. */
+  int collect_body;   /* True if we are collecting the body lines. */
+  int is_qp_encoded;  /* Current part is QP encoded. */
+
+  int line_too_long;  /* Indicates that a received line was too long. */
+  int parser_error;   /* Indicates that we encountered a error from
+                         the parser. */
+
+  /* Buffer used to constructed complete files. */
+  size_t linebufsize;   /* The allocated size of the buffer. */
+  size_t linebufpos;    /* The actual write posituion. */  
+  char linebuf[1];      /* The buffer. */
+};
+typedef struct pgpmime_context *pgpmime_context_t;
+
+
+/* Do in-place decoding of quoted-printable data of LENGTH in BUFFER.
+   Returns the new length of the buffer. */
+static size_t
+qp_decode (char *buffer, size_t length)
+{
+  char *d, *s;
+
+  for (s=d=buffer; length; length--)
+    if (*s == '=' && length > 2 && hexdigitp (s+1) && hexdigitp (s+2))
+      {
+        s++;
+        *(unsigned char*)d++ = xtoi_2 (s);
+        s += 2;
+        length -= 2;
+      }
+    else
+      *d++ = *s++;
+  
+  return d - buffer;
+}
+
+
+
+/* Print the message event EVENT. */
+static void
+debug_message_event (pgpmime_context_t ctx, 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 ("%s: ctx=%p, rfc822 event %s\n", __FILE__, ctx, s);
+}
+
+
+
+
+/* This routine gets called by the RFC822 parser for all kind of
+   events.  OPAQUE carries in our case a pgpmime context.  Should
+   return 0 on success or -1 and as well as seeting errno on
+   failure. */
+static int
+message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
+{
+  pgpmime_context_t ctx = opaque;
+
+  debug_message_event (ctx, event);
+  if (event == RFC822PARSE_T2BODY)
+    {
+      rfc822parse_field_t field;
+      const char *s1, *s2;
+      size_t off;
+      char *p;
+
+      field = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (field)
+        {
+          s1 = rfc822parse_query_media_type (field, &s2);
+          if (s1)
+            {
+              log_debug ("%s: ctx=%p, media `%s' `%s'\n",
+                         __FILE__, ctx, s1, s2);
+
+              if (!strcmp (s1, "multipart"))
+                {
+                  if (!strcmp (s2, "signed"))
+                    ;
+                  else if (!strcmp (s2, "encrypted"))
+                    ;
+                }
+              else if (!strcmp (s1, "text"))
+                {
+                  if (!ctx->body)
+                    {
+                      if (!gpgme_data_new (&ctx->body))
+                        ctx->collect_body = 1;
+                    }
+                }
+            }
+          
+          rfc822parse_release_field (field);
+        }
+      ctx->in_data = 1;
+
+      ctx->is_qp_encoded = 0;
+      p = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1, &off);
+      if (p)
+        {
+          if (!stricmp (p+off, "quoted-printable"))
+            ctx->is_qp_encoded = 1;
+          free (p);
+        }
+    }
+  else if (event == RFC822PARSE_LEVEL_DOWN)
+    {
+      ctx->nesting_level++;
+    }
+  else if (event == RFC822PARSE_LEVEL_UP)
+    {
+      if (ctx->nesting_level)
+        ctx->nesting_level--;
+      else 
+        {
+          log_error ("%s: ctx=%p, invalid structure: bad nesting level\n",
+                     __FILE__, ctx);
+          ctx->parser_error = 1;
+        }
+    }
+  else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
+    {
+      ctx->in_data = 0;
+      ctx->collect_body = 0;
+    }
+  else if (event == RFC822PARSE_BEGIN_HEADER)
+    {
+      
+    }
+
+  return 0;
+}
+
+
+/* This handler is called by GPGME with the decrypted plaintext. */
+static ssize_t
+plaintext_handler (void *handle, const void *buffer, size_t size)
+{
+  pgpmime_context_t ctx = handle;
+  const char *s;
+  size_t n, pos;
+
+  s = buffer;
+  pos = ctx->linebufpos;
+  n = size;
+  for (; n ; n--, s++)
+    {
+      if (pos >= ctx->linebufsize)
+        {
+          log_error ("%s: ctx=%p, rfc822 parser failed: line too long\n",
+                     __FILE__, ctx);
+          ctx->line_too_long = 1;
+          return 0; /* Error. */
+        }
+      if (*s != '\n')
+        ctx->linebuf[pos++] = *s;
+      else
+        { /* Got a complete line. Remove the last CR */
+          if (pos && ctx->linebuf[pos-1] == '\r')
+            pos--;
+
+          if (rfc822parse_insert (ctx->msg, ctx->linebuf, pos))
+            {
+              log_error ("%s: ctx=%p, rfc822 parser failed: %s\n",
+                         __FILE__, ctx, strerror (errno));
+              ctx->parser_error = 1;
+              return 0; /* Error. */
+            }
+
+          if (ctx->in_data && ctx->collect_body && ctx->body)
+            {
+              if (ctx->collect_body == 1)
+                ctx->collect_body = 2;
+              else
+                {
+                  if (ctx->is_qp_encoded)
+                    pos = qp_decode (ctx->linebuf, pos);
+                  gpgme_data_write (ctx->body, ctx->linebuf, pos);
+                  gpgme_data_write (ctx->body, "\r\n", 2);
+                }
+            }
+
+          /* Continue with next line. */
+          pos = 0;
+        }
+    }
+  ctx->linebufpos = pos;
+
+  return size;
+}
+
+
+/* Decrypt the PGP/MIME INSTREAM (i.e the second part of the
+   multipart/mixed) and allow saving of all attachments. On success a
+   newly allocated body will be stored at BODY. */
+int
+pgpmime_decrypt (LPSTREAM instream, int ttl, char **body)
+{
+  gpg_error_t err;
+  struct gpgme_data_cbs cbs;
+  gpgme_data_t plaintext;
+  pgpmime_context_t ctx;
+
+  *body = NULL;
+
+  memset (&cbs, 0, sizeof cbs);
+  cbs.write = plaintext_handler;
+
+  ctx = xcalloc (1, sizeof *ctx + LINEBUFSIZE);
+  ctx->linebufsize = LINEBUFSIZE;
+
+  ctx->msg = rfc822parse_open (message_cb, ctx);
+  if (!ctx->msg)
+    {
+      err = gpg_error_from_errno (errno);
+      log_error ("failed to open the RFC822 parser: %s", strerror (errno));
+      goto leave;
+    }
+
+  err = gpgme_data_new_from_cbs (&plaintext, &cbs, ctx);
+  if (err)
+    goto leave;
+
+  err = op_decrypt_stream_to_gpgme (instream, plaintext, ttl, NULL);
+  if (!err && (ctx->parser_error || ctx->line_too_long))
+    err = gpg_error (GPG_ERR_GENERAL);
+
+  if (!err)
+    {
+      if (ctx->body)
+        {
+          /* Return the buffer but first make sure it is a string. */
+          if (gpgme_data_write (ctx->body, "", 1) == 1)
+            {
+              *body = gpgme_data_release_and_get_mem (ctx->body, NULL);
+              ctx->body = NULL; 
+            }
+        }
+      else
+        *body = xstrdup ("[PGP/MIME message without plain text body]");
+    }
+
+ leave:
+  rfc822parse_close (ctx->msg);
+  if (plaintext)
+    gpgme_data_release (plaintext);
+  if (ctx && ctx->body)
+    gpgme_data_release (ctx->body);
+  xfree (ctx);
+  return err;
+}
diff --git a/src/pgpmime.h b/src/pgpmime.h
new file mode 100644 (file)
index 0000000..e36c1db
--- /dev/null
@@ -0,0 +1,38 @@
+/* pgpmime.h - PGP/MIME routines for Outlook
+ *     Copyright (C) 2005 g10 Code GmbH
+ *
+ * This file is part of GPGol.
+ * 
+ * GPGol is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ * 
+ * GPGol 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef PGPMIME_H
+#define PGPMIME_H
+
+#ifdef __cplusplus
+extern "C" {
+#if 0
+}
+#endif
+#endif
+
+int pgpmime_decrypt (LPSTREAM instream, int ttl, char **body);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /*PGPMIME_H*/
diff --git a/src/rfc822parse.c b/src/rfc822parse.c
new file mode 100644 (file)
index 0000000..622fe8d
--- /dev/null
@@ -0,0 +1,1255 @@
+/* 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 Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+
+/* According to RFC822 binary zeroes 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 interfaces 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 actually be send 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 the value part of the line (i.e. after the first
+ * colon).
+ */
+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/src/rfc822parse.h b/src/rfc822parse.h
new file mode 100644 (file)
index 0000000..8a56c51
--- /dev/null
@@ -0,0 +1,81 @@
+/* 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 Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, 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 */