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;
+ }
}
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:
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);
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);
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
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;
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;
}
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);
}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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:
+*/