summaryrefslogtreecommitdiffstats
path: root/rfc2045
diff options
context:
space:
mode:
Diffstat (limited to 'rfc2045')
-rw-r--r--rfc2045/.gitignore13
-rw-r--r--rfc2045/Makefile.am82
-rw-r--r--rfc2045/base64.c137
-rw-r--r--rfc2045/base64.h39
-rw-r--r--rfc2045/configure.in93
-rw-r--r--rfc2045/headercheck.c48
-rw-r--r--rfc2045/makemime.c1111
-rw-r--r--rfc2045/makemime.sgml592
-rw-r--r--rfc2045/reformime.c1228
-rw-r--r--rfc2045/reformime.sgml463
-rw-r--r--rfc2045/rfc2045.c1375
-rw-r--r--rfc2045/rfc2045.h658
-rw-r--r--rfc2045/rfc2045.sgml578
-rw-r--r--rfc2045/rfc2045_fromfd.c65
-rw-r--r--rfc2045/rfc2045acchk.c145
-rw-r--r--rfc2045/rfc2045acprep.c106
-rw-r--r--rfc2045/rfc2045appendurl.c129
-rw-r--r--rfc2045/rfc2045cdecode.c214
-rw-r--r--rfc2045/rfc2045charset.h.in9
-rw-r--r--rfc2045/rfc2045decode.c40
-rw-r--r--rfc2045/rfc2045decodemimesection.c47
-rw-r--r--rfc2045/rfc2045decodemimesectionu.c68
-rw-r--r--rfc2045/rfc2045decodemsgtoutf8.c148
-rw-r--r--rfc2045/rfc2045enomem.c9
-rw-r--r--rfc2045/rfc2045find.c49
-rw-r--r--rfc2045/rfc2045header.c287
-rw-r--r--rfc2045/rfc2045mkboundary.c72
-rw-r--r--rfc2045/rfc2045reply.c1667
-rw-r--r--rfc2045/rfc2045rewrite.c441
-rw-r--r--rfc2045/rfc2045searchcontenttype.c41
-rw-r--r--rfc2045/rfc2045src.h2
-rw-r--r--rfc2045/rfc2045tryboundary.c119
-rw-r--r--rfc2045/rfc2045xdump.c47
-rw-r--r--rfc2045/rfc2231.c375
-rw-r--r--rfc2045/rfc2231encode.c147
-rw-r--r--rfc2045/rfc3676parser.c1005
-rw-r--r--rfc2045/rfc3676parser.h198
-rw-r--r--rfc2045/rfc3676parsercpp.C117
-rw-r--r--rfc2045/testrfc3676parser.c70
-rw-r--r--rfc2045/testrfc3676parsersuite152
-rw-r--r--rfc2045/testrfc3676parsersuite.txt113
-rw-r--r--rfc2045/testsuite227
-rw-r--r--rfc2045/testsuite.txt.idn332
-rw-r--r--rfc2045/testsuite.txt.noidn332
-rw-r--r--rfc2045/testsuitemm57
-rw-r--r--rfc2045/testsuitemm.txt73
46 files changed, 13320 insertions, 0 deletions
diff --git a/rfc2045/.gitignore b/rfc2045/.gitignore
new file mode 100644
index 0000000..2fefef8
--- /dev/null
+++ b/rfc2045/.gitignore
@@ -0,0 +1,13 @@
+/headercheck
+/makemime
+/makemime.1
+/makemime.html
+/reformime
+/reformime.1
+/reformime.html
+/rfc2045.3
+/rfc2045.html
+/rfc2045_config.h
+/rfc2045_config.h.in
+/rfc2045charset.h
+/testrfc3676parser
diff --git a/rfc2045/Makefile.am b/rfc2045/Makefile.am
new file mode 100644
index 0000000..ee75b2e
--- /dev/null
+++ b/rfc2045/Makefile.am
@@ -0,0 +1,82 @@
+#
+# Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+# distribution information.
+
+noinst_LTLIBRARIES=librfc2045.la
+noinst_PROGRAMS=reformime makemime headercheck testrfc3676parser
+
+BUILT_SOURCES=reformime.html reformime.1 makemime.html makemime.1 \
+ rfc2045.html rfc2045.3
+
+EXTRA_DIST=$(BUILT_SOURCES) testsuite testsuite.txt.idn testsuite.txt.noidn \
+ testsuitemm testsuitemm.txt \
+ testrfc3676parsersuite testrfc3676parsersuite.txt
+
+noinst_DATA=$(BUILT_SOURCES)
+
+librfc2045_la_SOURCES=rfc2045.c rfc2045.h rfc2045src.h \
+ rfc2045acchk.c rfc2045acprep.c \
+ rfc2045appendurl.c rfc2045cdecode.c rfc2045decode.c \
+ rfc2045enomem.c \
+ rfc2045_fromfd.c \
+ rfc2045find.c rfc2045mkboundary.c rfc2045rewrite.c \
+ rfc2045tryboundary.c rfc2045xdump.c \
+ rfc2045searchcontenttype.c rfc2045decodemimesection.c \
+ rfc2045decodemimesectionu.c rfc2045header.c \
+ rfc2045reply.c \
+ rfc2045decodemsgtoutf8.c \
+ rfc2231.c rfc2231encode.c \
+ rfc3676parser.h rfc3676parser.c rfc3676parsercpp.C \
+ base64.c base64.h
+
+reformime_SOURCES=reformime.c
+reformime_LDADD = librfc2045.la ../rfc822/libencode.la ../rfc822/librfc822.la \
+ ../unicode/libunicode.la ../numlib/libnumlib.la
+reformime_DEPENDENCIES = $(reformime_LDADD)
+reformime_LDFLAGS=-static
+
+makemime_SOURCES=makemime.c
+makemime_LDADD = ../rfc822/libencode.la ../rfc822/librfc822.la librfc2045.la ../unicode/libunicode.la ../numlib/libnumlib.la ../unicode/libunicode.la
+makemime_DEPENDENCIES=$(makemime_LDADD)
+makemime_LDFLAGS=-static
+
+headercheck_SOURCES=headercheck.c
+headercheck_LDADD=librfc2045.la ../rfc822/librfc822.la ../numlib/libnumlib.la \
+ ../unicode/libunicode.la
+headercheck_DEPENDENCIES=$(headercheck_LDADD)
+headercheck_LDFLAGS=-static
+
+testrfc3676parser_SOURCES=testrfc3676parser.c
+testrfc3676parser_LDADD=librfc2045.la ../unicode/libunicode.la
+testrfc3676parser_DEPENDENCIES=$(testrfc3676parser_LDADD)
+
+if HAVE_SGML
+reformime.html: reformime.sgml ../docbook/sgml2html
+ ../docbook/sgml2html reformime.sgml reformime.html
+
+reformime.1: reformime.sgml ../docbook/sgml2html
+ ../docbook/sgml2man reformime.sgml reformime.1
+
+makemime.html: makemime.sgml ../docbook/sgml2html
+ ../docbook/sgml2html makemime.sgml makemime.html
+
+makemime.1: makemime.sgml ../docbook/sgml2html
+ ../docbook/sgml2man makemime.sgml makemime.1
+
+rfc2045.html: rfc2045.sgml ../docbook/sgml2html
+ ../docbook/sgml2html rfc2045.sgml rfc2045.html
+
+rfc2045.3: rfc2045.sgml ../docbook/sgml2html
+ ../docbook/sgml2man rfc2045.sgml rfc2045.3
+endif
+
+if HAVE_LIBIDN
+TESTSUITE=testsuite.txt.idn
+else
+TESTSUITE=testsuite.txt.noidn
+endif
+
+check-am:
+ @SHELL@ $(srcdir)/testsuite | cmp -s - $(srcdir)/$(TESTSUITE)
+ @SHELL@ $(srcdir)/testsuitemm | cmp -s - $(srcdir)/testsuitemm.txt
+ @SHELL@ $(srcdir)/testrfc3676parsersuite | diff -U 3 $(srcdir)/testrfc3676parsersuite.txt -
diff --git a/rfc2045/base64.c b/rfc2045/base64.c
new file mode 100644
index 0000000..bf676fd
--- /dev/null
+++ b/rfc2045/base64.c
@@ -0,0 +1,137 @@
+/*
+** Copyright 2002 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "base64.h"
+
+#include <string.h>
+#include <stdio.h>
+
+void base64_decode_init(struct base64decode *b,
+ int (*f)(const char *, int, void *),
+ void *a)
+{
+ b->workbuflen=0;
+ b->decode_func=f;
+ b->decode_func_arg=a;
+}
+
+static int doflush(struct base64decode *);
+
+static const char base64tab[]=
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int base64_decode(struct base64decode *b, const char *p, int l)
+{
+ while (l)
+ {
+ char c;
+
+ if (b->workbuflen >= sizeof(b->workbuf))
+ {
+ int rc=doflush(b);
+
+ if (rc)
+ return (rc);
+ }
+
+
+ if (*p == '=')
+ {
+ c=100;
+ }
+ else
+ {
+ char *s=strchr(base64tab, *p);
+
+ if (s == NULL)
+ {
+ ++p;
+ --l;
+ continue;
+ }
+
+ c= s-base64tab;
+ }
+ b->workbuf[b->workbuflen++]=c;
+ ++p;
+ --l;
+ }
+ return (0);
+}
+
+int base64_decode_end(struct base64decode *b)
+{
+ return (doflush(b));
+}
+
+static int doflush(struct base64decode *p)
+{
+ int i=p->workbuflen / 4;
+ int j;
+ int k=0;
+
+ i= i * 4;
+
+ for (j=0; j<i; j += 4)
+ {
+ char a,b,c;
+
+ int w=p->workbuf[j];
+ int x=p->workbuf[j+1];
+ int y=p->workbuf[j+2];
+ int z=p->workbuf[j+3];
+
+ a= (w << 2) | (x >> 4);
+ b= (x << 4) | (y >> 2);
+ c= (y << 6) | z;
+ p->workbuf[k++]=a;
+ if ( y != 100)
+ p->workbuf[k++]=b;
+ if ( z != 100)
+ p->workbuf[k++]=c;
+ }
+
+ j= (*p->decode_func)(p->workbuf, k, p->decode_func_arg);
+
+ k=0;
+ while (i < p->workbuflen)
+ {
+ p->workbuf[k]=p->workbuf[i];
+ ++k;
+ ++i;
+ }
+ p->workbuflen=k;
+ return (j);
+}
+
+/* ---- */
+
+static int save_str(const char *p, int l, void *vp)
+{
+ memcpy(*(char **)vp, p, l);
+
+ *(char **)vp += l;
+ return (0);
+}
+
+char *base64_decode_str(const char *s)
+{
+ struct base64decode b;
+
+ char *p=strdup(s);
+ char *pp;
+
+ if (!p)
+ return (NULL);
+
+ pp= p;
+
+ base64_decode_init(&b, save_str, &pp);
+ base64_decode(&b, s, strlen(s));
+ base64_decode_end(&b);
+ *pp=0;
+ return p;
+}
diff --git a/rfc2045/base64.h b/rfc2045/base64.h
new file mode 100644
index 0000000..ef112aa
--- /dev/null
+++ b/rfc2045/base64.h
@@ -0,0 +1,39 @@
+#ifndef base64_h
+#define base64_h
+
+#include "rfc2045/rfc2045_config.h"
+
+
+/*
+** Copyright 2002 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is an attempt to write a portable base64 decoder */
+
+struct base64decode {
+
+ char workbuf[256];
+ int workbuflen;
+
+ int (*decode_func)(const char *, int, void *);
+ void *decode_func_arg;
+} ;
+
+void base64_decode_init(struct base64decode *,
+ int (*)(const char *, int, void *),
+ void *);
+int base64_decode(struct base64decode *, const char *, int);
+int base64_decode_end(struct base64decode *);
+
+char *base64_decode_str(const char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/rfc2045/configure.in b/rfc2045/configure.in
new file mode 100644
index 0000000..2e4d857
--- /dev/null
+++ b/rfc2045/configure.in
@@ -0,0 +1,93 @@
+dnl
+dnl Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+dnl distribution information.
+AC_INIT(rfc2045lib, 0.20, [courier-maildrop@lists.sourceforge.net])
+
+>confdefs.h # Kill PACKAGE_ macros
+
+AC_CONFIG_SRCDIR(rfc2045.c)
+AC_CONFIG_AUX_DIR(../..)
+AM_INIT_AUTOMAKE([foreign no-define])
+AM_CONFIG_HEADER(rfc2045_config.h)
+
+dnl Checks for programs.
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_CC
+AC_PROG_CXX
+AC_LIBTOOL_DLOPEN
+AM_PROG_LIBTOOL
+
+if test "$GCC" = yes ; then
+ CXXFLAGS="$CXXFLAGS -Wall"
+ CFLAGS="$CFLAGS -Wall"
+fi
+
+dnl Checks for libraries.
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(unistd.h sys/wait.h strings.h locale.h)
+
+AC_CACHE_CHECK([for missing gethostname prototype],rfc2045_cv_SYS_GETHOSTNAME,
+
+AC_TRY_COMPILE([
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+extern int gethostname(int,int);
+],[
+],rfc2045_cv_SYS_GETHOSTNAME=yes,rfc2045_cv_SYS_GETHOSTNAME=no
+)
+
+)
+
+if test $rfc2045_cv_SYS_GETHOSTNAME = "no"
+then
+ AC_DEFINE_UNQUOTED(HAS_GETHOSTNAME,1,
+ [ Whether unistd.h includes a prototype for gethostname() ])
+fi
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_OFF_T
+AC_TYPE_SIZE_T
+AC_TYPE_PID_T
+AC_SYS_LARGEFILE
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(setlocale)
+AC_CHECK_FUNC(strncasecmp,,CFLAGS="$CFLAGS -Dstrncasecmp=strnicmp")
+AC_CHECK_FUNC(strcasecmp,,CFLAGS="$CFLAGS -Dstrcasecmp=stricmp")
+
+AC_ARG_WITH(package, [], package="$withval",
+ [
+ package="$PACKAGE"
+ ac_configure_args="$ac_configure_args --with-package=$PACKAGE"
+ ])
+
+AC_ARG_WITH(version, [], version="$withval",
+ [
+ version="$VERSION"
+ ac_configure_args="$ac_configure_args --with-version=$VERSION"
+ ])
+
+AC_DEFINE_UNQUOTED(RFC2045PKG,"$package", [ Package that uses librfc2045.a ])
+AC_DEFINE_UNQUOTED(RFC2045VER,"$version", [ Package that uses librfc2045.a ])
+
+AC_ARG_ENABLE(mimecharset,
+ [ --enable-mimecharset=charset Default MIME charset to set on new messages],
+ RFC2045CHARSET="$enableval", RFC2045CHARSET="iso-8859-1")
+
+CFLAGS="$CFLAGS -I.. -I${srcdir}/.."
+CXXFLAGS="$CXXFLAGS -I.. -I${srcdir}/.."
+AC_SUBST(rfc822includedir)
+
+AC_SUBST(RFC2045CHARSET)
+
+AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../docbook)
+
+. ../rfc822/rfc822.config
+
+AM_CONDITIONAL(HAVE_LIBIDN, test "$libidn" = "yes")
+AC_OUTPUT(Makefile rfc2045charset.h)
diff --git a/rfc2045/headercheck.c b/rfc2045/headercheck.c
new file mode 100644
index 0000000..5bead04
--- /dev/null
+++ b/rfc2045/headercheck.c
@@ -0,0 +1,48 @@
+#include "rfc2045_config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "rfc2045.h"
+
+void rfc2045_error(const char *s)
+{
+ printf("%s\n", s);
+ exit(0);
+}
+
+int main(int argc, char **argv)
+{
+ struct rfc2045src *src;
+ struct rfc2045 *rfcp;
+ struct rfc2045headerinfo *hi;
+ char *h, *v;
+
+ if (argc < 3)
+ return (0);
+
+ rfcp=rfc2045_fromfd(0);
+
+ if (!rfcp)
+ return (0);
+
+ if (argv[2][0])
+ rfcp=rfc2045_find(rfcp, argv[2]);
+
+ src=rfc2045src_init_fd(0);
+
+ if (!src)
+ return (0);
+
+ hi=rfc2045header_start(src, rfcp);
+
+ if (!hi)
+ return (0);
+
+ while (rfc2045header_get(hi, &h, &v, atoi(argv[1])) == 0)
+ {
+ if (h == NULL)
+ break;
+ printf("Header: %s\n", h);
+ printf("Value: %s\n", v);
+ }
+ return (0);
+}
diff --git a/rfc2045/makemime.c b/rfc2045/makemime.c
new file mode 100644
index 0000000..e535556
--- /dev/null
+++ b/rfc2045/makemime.c
@@ -0,0 +1,1111 @@
+/*
+** Copyright 2000-2010 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include "rfc822/encode.h"
+#include "rfc2045.h"
+#include "rfc2045charset.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include "numlib/numlib.h"
+
+#if HAS_GETHOSTNAME
+#else
+int gethostname(const char *, size_t);
+#endif
+
+
+struct arg_list {
+ struct arg_list *next;
+ char *arg;
+ } ;
+
+/******************************************************************************
+
+Check if a file is a regular file.
+
+******************************************************************************/
+
+static int isreg(int fd)
+{
+ struct stat st;
+ if (fstat(fd, &st))
+ {
+ perror("fstat");
+ exit(1);
+ }
+ return S_ISREG(st.st_mode);
+}
+
+/******************************************************************************
+
+Determine the file descriptor wanted, if any.
+
+******************************************************************************/
+
+static int fd_wanted(const char *filename, const char *mode)
+{
+ if (strcmp(filename, "-") == 0) /* stdin or stdout */
+ return strcmp(mode, "r") ? 1:0;
+ if (*filename == '&')
+ return atoi(filename+1); /* file descriptor */
+ return -1; /* or a file */
+}
+
+/******************************************************************************
+
+Open some file or a pipe for reading and writing.
+
+******************************************************************************/
+
+static FILE *openfile_or_pipe(const char *filename, const char *mode)
+{
+int fd, fd_to_dup = fd_wanted(filename, mode);
+FILE *fp;
+
+ if (fd_to_dup == 0)
+ return stdin;
+
+ if (fd_to_dup >= 0)
+ fd = dup(fd_to_dup);
+ else
+ fd=open(filename, (strcmp(mode, "r") ?
+ O_WRONLY|O_CREAT|O_TRUNC:O_RDONLY), 0666);
+
+ if (fd < 0)
+ {
+ perror(filename);
+ exit(1);
+ }
+ fp=fdopen(fd, mode);
+ if (!fp)
+ {
+ perror("fdopen");
+ exit(1);
+ }
+ return (fp);
+}
+
+/******************************************************************************
+
+Open some file. If we get a pipe, open a temporary file, and drain pipe's
+contents into it.
+
+******************************************************************************/
+
+static FILE *openfile(const char *filename)
+{
+FILE *fp=openfile_or_pipe(filename, "r");
+int fd=fileno(fp);
+
+ if (!isreg(fd)) /* Must be a pipe */
+ {
+ FILE *t=tmpfile();
+ int c;
+
+ if (!t)
+ {
+ perror("tmpfile");
+ exit(1);
+ }
+
+ while ((c=getc(fp)) != EOF)
+ putc(c, t);
+ if (ferror(fp) || fflush(t)
+ || ferror(t) || fseek(t, 0L, SEEK_SET) == -1)
+ {
+ perror("write");
+ exit(1);
+ }
+ fclose(fp);
+ fp=t;
+ }
+ else
+ {
+ off_t orig_pos = lseek(fd, 0L, SEEK_CUR);
+
+ if (orig_pos == -1 ||
+ fseek(fp, orig_pos, SEEK_SET) == -1)
+ {
+ perror("fseek");
+ exit(1);
+ }
+ }
+ return (fp);
+}
+
+/******************************************************************************
+
+Build argv/argc from a file.
+
+******************************************************************************/
+
+static void read_args(int *argcp, char ***argvp, const char *file)
+{
+FILE *fp=openfile_or_pipe(file, "r");
+struct arg_list *argfirst=0, *arglast=0, *argp;
+char buffer[BUFSIZ];
+char *p;
+int c;
+
+ *argcp=0;
+ while (fgets(buffer, sizeof(buffer), fp) != 0)
+ {
+ const char *q;
+
+ if ((p=strchr(buffer, '\n')) != 0)
+ *p=0;
+ else while ((c=getc(fp)) != '\n' && c != EOF)
+ ; /* Just dump the excess */
+
+ /* Skip the filler. */
+
+ q=buffer;
+ while (*q && isspace((int)(unsigned char)*q))
+ ++q;
+ if (!*q) continue;
+ if (*q == '#') continue;
+ if (strcmp(buffer, "-") == 0)
+ {
+ if (isreg(fileno(fp)))
+ {
+ long orig_pos = ftell(fp);
+ if (orig_pos == -1 ||
+ lseek(fileno(fp), orig_pos, SEEK_SET) == -1)
+ {
+ perror("seek");
+ exit(1);
+ }
+ }
+ break;
+ }
+
+ argp=(struct arg_list *)malloc(sizeof(struct arg_list)+1+
+ strlen(q));
+ if (!argp)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ if (arglast)
+ arglast->next=argp;
+ else
+ argfirst=argp;
+ arglast=argp;
+ ++*argcp;
+ argp->next=0;
+ argp->arg=strcpy((char *)(argp+1), q);
+ }
+
+ if ((*argvp=malloc(sizeof (char *) * (*argcp+1))) == 0)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ c=0;
+ for (argp=argfirst; argp; argp=argp->next)
+ {
+ (*argvp)[c]= argp->arg;
+ ++c;
+ }
+ (*argvp)[c]=0;
+}
+
+static void usage()
+{
+ fprintf(stderr,
+"Usage:\n"
+" makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \\\n"
+" [-a \"Header: Contents\"] file\n"
+" -m [ type ] [-o file] [-e encoding] [-a \"Header: Contents\"] file\n"
+" -j [-o file] file1 file2\n"
+" @file\n"
+"\n"
+" file: filename - read or write from filename\n"
+" - - read or write from stdin or stdout\n"
+" &n - read or write from file descriptor n\n"
+" \\( opts \\) - read from child process, that generates [ opts ]\n"
+ "\n");
+
+ fprintf(stderr,
+"Options:\n"
+"\n"
+" -c type - create a new MIME section from \"file\" with this\n"
+" Content-Type: (default is application/octet-stream).\n"
+" -C charset - MIME charset of a new text/plain section.\n"
+ " -N name - MIME content name of the new mime section.\n");
+
+ fprintf(stderr,
+" -m [ type ] - create a multipart mime section from \"file\" of this\n"
+" Content-Type: (default is multipart/mixed).\n"
+" -e encoding - use the given encoding (7bit, 8bit, quoted-printable,\n"
+" or base64), instead of guessing. Omit \"-e\" and use\n"
+" -c auto to set Content-Type: to text/plain or\n"
+ " application/octet-stream based on picked encoding.\n");
+
+ fprintf(stderr,
+" -j file1 file2 - join mime section file2 to multipart section file1.\n"
+" -o file - write ther result to file, instead of stdout (not\n"
+" allowed in child processes).\n"
+" -a header - prepend an additional header to the output.\n"
+"\n"
+" @file - read all of the above options from file, one option or\n"
+" value on each line.\n"
+ );
+ exit (0);
+}
+
+/******************************************************************************
+
+The arguments are parsed into the following structure, as a tree.
+
+******************************************************************************/
+struct mimestruct {
+
+ /*
+ ** One or two input files. We initialize either file or child,
+ ** depending on the source being a file, or a child process.
+ ** Later, we open a file pointer in either case.
+ */
+
+ const char *inputfile1, *inputfile2;
+ struct mimestruct *inputchild1, *inputchild2;
+ FILE *inputfp1, *inputfp2;
+ pid_t child1, child2;
+
+ /* Output file. Defaults to "-", stdout */
+
+ const char *outputfile;
+ FILE *outputfp;
+
+ /* The handler and open functions */
+
+ void (*handler_func)(struct mimestruct *);
+ void (*open_func)(struct mimestruct *);
+
+ /* The new mime type, and encoding (-e) */
+ const char *mimetype;
+ const char *mimeencoding;
+ const char *textplaincharset;
+ const char *contentname;
+
+ /* A list of -a headers */
+ struct arg_list *a_first, *a_last;
+ } ;
+
+static void createsimplemime(struct mimestruct *);
+static void createmultipartmime(struct mimestruct *);
+static void joinmultipart(struct mimestruct *);
+
+static void opencreatesimplemime(struct mimestruct *);
+static void opencreatemultipartmime(struct mimestruct *);
+static void openjoinmultipart(struct mimestruct *);
+
+/******************************************************************************
+
+Recursively build the mimestruct tree.
+
+******************************************************************************/
+
+struct mimestruct *parseargs(int *argcp, char ***argvp)
+{
+struct mimestruct *m=malloc(sizeof(struct mimestruct));
+int argc= *argcp;
+char **argv= *argvp;
+
+ if (!m)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ memset(m, 0, sizeof(*m));
+
+ if (argc == 0 || argv[0][0] != '-') usage();
+
+ if (strncmp(argv[0], "-c", 2) == 0)
+ {
+ m->handler_func= &createsimplemime;
+ m->open_func= &opencreatesimplemime;
+ if (argv[0][2])
+ {
+ m->mimetype=argv[0]+2;
+ --argc;
+ ++argv;
+ }
+ else
+ {
+ --argc;
+ ++argv;
+ if (argc && argv[0][0] != '-' && argv[0][0] != ')')
+ {
+ m->mimetype=argv[0];
+ --argc;
+ ++argv;
+ }
+ else
+ m->mimetype="application/octet-stream";
+ }
+
+ while (isspace((int)(unsigned char)*m->mimetype))
+ ++m->mimetype;
+ }
+ else if (strncmp(argv[0], "-m", 2) == 0)
+ {
+ m->handler_func= &createmultipartmime;
+ m->open_func= &opencreatemultipartmime;
+ if (argv[0][2])
+ {
+ m->mimetype=argv[0]+2;
+ --argc;
+ ++argv;
+ }
+ else
+ {
+ --argc;
+ ++argv;
+ if (argc && argv[0][0] != '-' && argv[0][0] != ')')
+ {
+ m->mimetype=argv[0];
+ --argc;
+ ++argv;
+ }
+ else
+ m->mimetype="multipart/mixed";
+ }
+ while (isspace((int)(unsigned char)*m->mimetype))
+ ++m->mimetype;
+ }
+ else if (strncmp(argv[0], "-j", 2) == 0)
+ {
+ const char *filename;
+
+ m->handler_func= &joinmultipart;
+ m->open_func= &openjoinmultipart;
+ if (argv[0][2])
+ {
+ filename=argv[0]+2;
+ --argc;
+ ++argv;
+ }
+ else
+ {
+ --argc;
+ ++argv;
+ if (argc == 0) usage();
+ filename=argv[0];
+ --argc;
+ ++argv;
+ }
+
+ while (isspace((int)(unsigned char)*filename))
+ ++filename;
+
+ if (strcmp(filename, "(") == 0)
+ {
+ m->inputchild2=parseargs(&argc, &argv);
+ if (argc == 0 || strcmp(argv[0], ")"))
+ usage();
+ --argc;
+ ++argv;
+ }
+ else
+ m->inputfile2=filename;
+ }
+ else
+ usage();
+
+ /* Handle common options */
+
+ while (argc)
+ {
+ if (strncmp(argv[0], "-o", 2) == 0)
+ {
+ const char *f=argv[0]+2;
+
+ ++argv;
+ --argc;
+ if (*f == 0)
+ {
+ if (!argc) usage();
+ f=argv[0];
+ ++argv;
+ --argc;
+ }
+ while (isspace((int)(unsigned char)*f))
+ ++f;
+ m->outputfile=f;
+ continue;
+ }
+
+ if (strncmp(argv[0], "-C", 2) == 0)
+ {
+ char *f=argv[0]+2;
+
+ ++argv;
+ --argc;
+
+
+ if (*f == 0)
+ {
+ if (!argc) usage();
+ f=argv[0];
+ ++argv;
+ --argc;
+ }
+ while (isspace((int)(unsigned char)*f))
+ ++f;
+ m->textplaincharset=f;
+ continue;
+ }
+
+ if (strncmp(argv[0], "-N", 2) == 0)
+ {
+ char *f=argv[0]+2;
+
+ ++argv;
+ --argc;
+
+
+ if (*f == 0)
+ {
+ if (!argc) usage();
+ f=argv[0];
+ ++argv;
+ --argc;
+ }
+ while (isspace((int)(unsigned char)*f))
+ ++f;
+ m->contentname=f;
+ continue;
+ }
+
+ if (strncmp(argv[0], "-e", 2) == 0)
+ {
+ char *f=argv[0]+2, *q;
+
+ ++argv;
+ --argc;
+
+ if (*f == 0)
+ {
+ if (!argc) usage();
+ f=argv[0];
+ ++argv;
+ --argc;
+ }
+
+ for (q=f; *q; q++)
+ *q=tolower((int)(unsigned char)*q);
+
+ while (isspace((int)(unsigned char)*f))
+ ++f;
+
+ if (strcmp(f, "7bit") && strcmp(f, "8bit") &&
+ strcmp(f, "quoted-printable") &&
+ strcmp(f, "base64"))
+ usage();
+
+ m->mimeencoding=f;
+ continue;
+ }
+
+ if (strncmp(argv[0], "-a", 2) == 0)
+ {
+ char *f=argv[0]+2;
+ struct arg_list *a;
+
+ ++argv;
+ --argc;
+
+ if (*f == 0)
+ {
+ if (!argc) usage();
+ f=argv[0];
+ ++argv;
+ --argc;
+ }
+
+ while (isspace((int)(unsigned char)*f))
+ ++f;
+
+ a=malloc(sizeof(struct arg_list));
+ if (!a)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ if (m->a_last)
+ m->a_last->next=a;
+ else m->a_first=a;
+ m->a_last=a;
+ a->arg=f;
+ a->next=0;
+ continue;
+ }
+ break;
+ }
+
+ /* We must now have the input file argument */
+
+ if (!argc) usage();
+
+ if (strcmp(argv[0], "(") == 0)
+ {
+ --argc;
+ ++argv;
+ m->inputchild1=parseargs(&argc, &argv);
+ if (argc == 0 || strcmp(argv[0], ")"))
+ usage();
+ --argc;
+ ++argv;
+ }
+ else
+ {
+ m->inputfile1=argv[0];
+ --argc;
+ ++argv;
+ }
+
+ *argcp=argc;
+ *argvp=argv;
+ return (m);
+}
+
+/******************************************************************************
+
+After we're done, terminate with a zero exit code if all child processes also
+terminated with a zero exit code. Otherwise, terminate with a non-zero exit
+code thus propagating any child's non-zero exit code to parent.
+
+******************************************************************************/
+
+static void goodexit(struct mimestruct *m, int exitcode)
+{
+ if (m->outputfp && (fflush(m->outputfp) || ferror(m->outputfp)))
+ {
+ perror("makemime");
+ exit(1);
+ }
+
+ /*
+ ** Drain any leftover input, so that the child doesn't get
+ ** a SIGPIPE.
+ */
+
+ while (m->inputfp1 && !feof(m->inputfp1) && !ferror(m->inputfp1))
+ getc(m->inputfp1);
+
+ while (m->inputfp2 && !feof(m->inputfp2) && !ferror(m->inputfp2))
+ getc(m->inputfp2);
+
+ if (m->inputfp1)
+ {
+ if (ferror(m->inputfp1))
+ {
+ perror("makemime");
+ exitcode=1;
+ }
+
+ fclose(m->inputfp1);
+ }
+ if (m->inputfp2)
+ {
+ if (ferror(m->inputfp2))
+ {
+ perror("makemime");
+ exitcode=1;
+ }
+
+ fclose(m->inputfp2);
+ }
+
+ while (m->child1 > 0 && m->child2 > 0)
+ {
+ int waitstat;
+ pid_t p=wait(&waitstat);
+
+ if (p <= 0 && errno == ECHILD) break;
+
+ if (p == m->child1)
+ m->child1=0;
+ else if (p == m->child2)
+ m->child2=0;
+ else continue;
+ if (waitstat) exitcode=1;
+ }
+ exit(exitcode);
+}
+
+int main(int argc, char **argv)
+{
+struct mimestruct *m;
+
+ signal(SIGCHLD, SIG_DFL);
+ if (argc > 1 && argv[1][0] == '@')
+ read_args(&argc, &argv, argv[1]+1);
+ else if (argc > 1)
+ {
+ --argc;
+ ++argv;
+ }
+
+ m=parseargs(&argc, &argv);
+ if (argc) usage(); /* Some arguments left */
+
+ (*m->open_func)(m);
+ (*m->handler_func)(m);
+ goodexit(m, 0);
+ return (0);
+}
+
+static int encode_outfp(const char *p, size_t n, void *vp)
+{
+ if (fwrite(p, n, 1, *(FILE **)vp) != 1)
+ return -1;
+ return 0;
+}
+
+static int do_printRfc2231Attr(const char *param,
+ const char *value,
+ void *voidArg)
+{
+ fprintf( ((struct mimestruct *)voidArg)->outputfp,
+ ";\n %s=%s", param, value);
+ return 0;
+}
+
+static void createsimplemime(struct mimestruct *m)
+{
+struct arg_list *a;
+struct libmail_encode_info encode_info;
+const char *orig_charset=m->textplaincharset;
+
+ /* Determine encoding by reading the file, as follows:
+ **
+ ** Default to 7bit. Use 8bit if high-ascii bytes found. Use
+ ** quoted printable if lines more than 200 characters found.
+ ** Use base64 if a null byte is found.
+ */
+
+ if (m->mimeencoding == 0)
+ {
+ long orig_pos=ftell(m->inputfp1);
+ int binaryflag;
+
+ if (orig_pos == -1)
+ {
+ perror("ftell");
+ goodexit(m, 1);
+ }
+
+ m->mimeencoding=libmail_encode_autodetect_fpoff(m->inputfp1,
+ 0,
+ 0, -1,
+ &binaryflag);
+
+ if (ferror(m->inputfp1)
+ || fseek(m->inputfp1, orig_pos, SEEK_SET)<0)
+ {
+ perror("fseek");
+ goodexit(m, 1);
+ }
+
+ if (strcmp(m->mimetype, "auto") == 0)
+ m->mimetype=binaryflag
+ ? (orig_charset=0,
+ "application/octet-stream"):"text/plain";
+ }
+
+ for (a=m->a_first; a; a=a->next)
+ fprintf(m->outputfp, "%s\n", a->arg);
+
+ fprintf(m->outputfp, "Content-Type: %s", m->mimetype);
+ if (orig_charset && *orig_charset)
+ {
+ const char *c;
+
+ fprintf(m->outputfp, "; charset=\"");
+ for (c=orig_charset; *c; c++)
+ {
+ if (*c != '"' && *c != '\\')
+ putc(*c, m->outputfp);
+ }
+ fprintf(m->outputfp, "\"");
+ }
+
+ if (m->contentname && *m->contentname)
+ {
+ const char *chset=m->textplaincharset ? m->textplaincharset
+ : "iso-8859-1";
+
+ rfc2231_attrCreate("name", m->contentname, chset, NULL,
+ do_printRfc2231Attr, m);
+ }
+
+ fprintf(m->outputfp, "\nContent-Transfer-Encoding: %s\n\n",
+ m->mimeencoding);
+
+ libmail_encode_start(&encode_info, m->mimeencoding,
+ &encode_outfp,
+ &m->outputfp);
+ {
+ char input_buf[BUFSIZ];
+ int n;
+
+ while ((n=fread(input_buf, 1, sizeof(input_buf),
+ m->inputfp1)) > 0)
+ {
+ if ( libmail_encode(&encode_info, input_buf, n))
+ break;
+ }
+
+ libmail_encode_end(&encode_info);
+ }
+}
+
+/******************************************************************************
+
+Satisfy paranoia by making sure that the MIME boundary we picked does not
+appear in the contents of the bounded section.
+
+******************************************************************************/
+
+static int tryboundary(struct mimestruct *m, FILE *f, const char *bbuf)
+{
+char buf[BUFSIZ];
+char *p;
+int l=strlen(bbuf);
+int c;
+long orig_pos=ftell(f);
+
+ if (orig_pos == -1)
+ {
+ perror("ftell");
+ goodexit(m, 1);
+ }
+
+ while ((p=fgets(buf, sizeof(buf), f)) != 0)
+ {
+ if (p[0] == '-' && p[1] == '-' &&
+ strncmp(p+2, bbuf, l) == 0)
+ break;
+
+ if ((p=strchr(buf, '\n')) != 0)
+ *p=0;
+ else while ((c=getc(f)) != EOF && c != '\n')
+ ;
+ }
+
+ if (ferror(f) || fseek(f, orig_pos, SEEK_SET)<0)
+ {
+ perror("fseek");
+ goodexit(m, 1);
+ }
+
+ return (p ? 1:0);
+}
+
+/******************************************************************************
+
+Create a MIME boundary for some content.
+
+******************************************************************************/
+
+static const char *mkboundary(struct mimestruct *m, FILE *f)
+{
+pid_t pid=getpid();
+time_t t;
+static unsigned n=0;
+static char bbuf[NUMBUFSIZE*4];
+char buf[NUMBUFSIZE];
+
+ time(&t);
+
+ do
+ {
+ strcpy(bbuf, "=_");
+ strcat(bbuf, libmail_str_size_t(++n, buf));
+ strcat(bbuf, "_");
+ strcat(bbuf, libmail_str_time_t(t, buf));
+ strcat(bbuf, "_");
+ strcat(bbuf, libmail_str_pid_t(pid, buf));
+ } while (tryboundary(m, f, bbuf));
+ return (bbuf);
+}
+
+static void createmultipartmime(struct mimestruct *m)
+{
+const char *b=mkboundary(m, m->inputfp1);
+struct arg_list *a;
+int c;
+
+ if (m->mimeencoding == 0)
+ m->mimeencoding="8bit";
+
+ for (a=m->a_first; a; a=a->next)
+ fprintf(m->outputfp, "%s\n", a->arg);
+ fprintf(m->outputfp, "Content-Type: %s; boundary=\"%s\"\n"
+ "Content-Transfer-Encoding: %s\n\n"
+ RFC2045MIMEMSG
+ "\n--%s\n",
+ m->mimetype, b,
+ m->mimeencoding,
+ b);
+ while ((c=getc(m->inputfp1)) != EOF)
+ putc(c, m->outputfp);
+ fprintf(m->outputfp, "\n--%s--\n", b);
+}
+
+static void joinmultipart(struct mimestruct *m)
+{
+const char *new_boundary;
+char *old_boundary=0;
+int old_boundary_len=0;
+char buffer[BUFSIZ];
+char *p;
+int c;
+
+ do
+ {
+ new_boundary=mkboundary(m, m->inputfp1);
+ } while (tryboundary(m, m->inputfp2, new_boundary));
+
+ /* Copy the header */
+
+ for (;;)
+ {
+ if (fgets(buffer, sizeof(buffer), m->inputfp2) == 0)
+ {
+ buffer[0]=0;
+ break;
+ }
+
+ if (strcmp(buffer, "\r\n") == 0 ||
+ buffer[0] == '\n' || strncmp(buffer, "--", 2) == 0)
+ break;
+
+ if (strncasecmp(buffer, "content-type:", 13))
+ {
+ fprintf(m->outputfp, "%s", buffer);
+ if ((p=strchr(buffer, '\n')) != 0) continue;
+ while ((c=getc(m->inputfp2)) != EOF && c != '\n')
+ putc(c, m->outputfp);
+ continue;
+ }
+
+ if ((p=strchr(buffer, '\n')) == 0)
+ while ((c=getc(m->inputfp2)) != EOF && c != '\n')
+ ;
+
+ p=strchr(buffer+13, ';');
+ if (p) *p=0;
+ fprintf(m->outputfp, "Content-Type:%s; boundary=\"%s\"\n",
+ buffer+13, new_boundary);
+
+ for (;;)
+ {
+ c=getc(m->inputfp2);
+ if (c != EOF) ungetc(c, m->inputfp2);
+ if (c == '\n' || !isspace((int)(unsigned char)c))
+ break;
+ while ((c=getc(m->inputfp2)) != EOF && c != '\n')
+ ;
+ }
+ }
+
+ do
+ {
+ if (strncmp(buffer, "--", 2) == 0)
+ {
+ if (old_boundary == 0)
+ {
+ old_boundary=malloc(strlen(buffer)+1);
+ if (!old_boundary)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ strcpy(old_boundary, buffer);
+ if ((p=strchr(old_boundary, '\n')) != 0)
+ {
+ if (p > old_boundary && p[-1] == '\r')
+ --p;
+ *p=0;
+ }
+ p=old_boundary+strlen(old_boundary);
+ if (p >= old_boundary+4 &&
+ strcmp(p-2, "--") == 0)
+ p[-2]=0;
+ old_boundary_len=strlen(old_boundary);
+ }
+
+
+ if (strncasecmp(buffer, old_boundary,
+ old_boundary_len) == 0)
+ {
+ if ((p=strchr(buffer, '\n')) != 0)
+ *p=0;
+ else while ((c=getc(m->inputfp2)) != '\n'
+ && c != EOF)
+ ;
+
+ c=strlen(buffer);
+ if (c > 0 && buffer[c-1] == '\r')
+ buffer[--c]=0;
+
+ if (c >= 4 && strcmp(buffer+(c-2), "--") == 0)
+ break;
+ fprintf(m->outputfp, "--%s\n",
+ new_boundary);
+ continue;
+ }
+ }
+ fprintf(m->outputfp, "%s", buffer);
+ if ((p=strchr(buffer, '\n')) == 0)
+ while ((c=getc(m->inputfp2)) != '\n' && c != EOF)
+ ;
+ } while (fgets(buffer, sizeof(buffer), m->inputfp2) != 0);
+
+ fprintf(m->outputfp, "--%s\n", new_boundary);
+
+ while ((c=getc(m->inputfp1)) != EOF)
+ putc(c, m->outputfp);
+
+ fprintf(m->outputfp, "\n--%s--\n", new_boundary);
+ goodexit(m, 0);
+}
+
+/******************************************************************************
+
+Open input from a child process
+
+******************************************************************************/
+
+static FILE *openchild(struct mimestruct *parent, struct mimestruct *child,
+ pid_t *pidptr,
+ int usescratch)
+{
+int pipefd[2];
+char buf[NUMBUFSIZE];
+char buf2[NUMBUFSIZE+1];
+FILE *fp;
+
+ if (pipe(pipefd) < 0)
+ {
+ perror("pipe");
+ exit(1);
+ }
+
+ *pidptr=fork();
+
+ if (*pidptr < 0)
+ {
+ perror("fork");
+ exit(1);
+ }
+
+ if (*pidptr == 0)
+ {
+ /* Duplicate pipe on stdout */
+
+ close(pipefd[0]);
+ dup2(pipefd[1], 1);
+ close(pipefd[1]);
+
+ /* Close any input files opened by parent */
+
+ if (parent->inputfp1) fclose(parent->inputfp1);
+ if (parent->inputfp2) fclose(parent->inputfp2);
+
+ /* Open, then execute the child process */
+
+ (*child->open_func)(child);
+ (*child->handler_func)(child);
+ goodexit(child, 0);
+ }
+ close(pipefd[1]);
+
+ /*
+ ** Open the pipe by calling openfile(), automatically creating
+ ** the scratch file, if necessary.
+ */
+
+ buf[0]='&';
+ strcpy(buf+1, libmail_str_size_t(pipefd[0], buf2));
+
+ fp= usescratch ? openfile(buf):openfile_or_pipe(buf, "r");
+ close(pipefd[0]); /* fd was duped by openfile */
+ return (fp);
+}
+
+static void openoutput(struct mimestruct *m)
+{
+ if (!m->outputfile)
+ m->outputfile="-";
+
+ m->outputfp= openfile_or_pipe(m->outputfile, "w");
+}
+
+static void openjoinmultipart(struct mimestruct *m)
+{
+ /* number two is the multipart section */
+ if (m->inputchild2)
+ m->inputfp2=openchild(m, m->inputchild2, &m->child2, 1);
+ else
+ m->inputfp2=openfile(m->inputfile2);
+
+
+ if (m->inputchild1)
+ m->inputfp1=openchild(m, m->inputchild1, &m->child1, 1);
+ else
+ m->inputfp1=openfile(m->inputfile1);
+ openoutput(m);
+}
+
+static void opencreatesimplemime(struct mimestruct *m)
+{
+ if (m->inputchild1)
+ m->inputfp1=openchild(m, m->inputchild1, &m->child1,
+ m->mimeencoding ? 0:1);
+ else
+ m->inputfp1= m->mimeencoding
+ ? openfile_or_pipe(m->inputfile1, "r")
+ : openfile(m->inputfile1);
+ openoutput(m);
+}
+
+static void opencreatemultipartmime(struct mimestruct *m)
+{
+ if (m->inputchild1)
+ m->inputfp1=openchild(m, m->inputchild1, &m->child1, 1);
+ else
+ m->inputfp1=openfile_or_pipe(m->inputfile1, "r");
+ openoutput(m);
+}
+
diff --git a/rfc2045/makemime.sgml b/rfc2045/makemime.sgml
new file mode 100644
index 0000000..e32a9c4
--- /dev/null
+++ b/rfc2045/makemime.sgml
@@ -0,0 +1,592 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<!-- Copyright 2001-2007 Double Precision, Inc. See COPYING for -->
+<!-- distribution information. -->
+<refentry>
+ <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info>
+
+ <refmeta>
+ <refentrytitle>makemime</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class='manual'>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>makemime</refname>
+ <refpurpose>Create MIME-formatted messages</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">makemime</command>
+ <arg rep="repeat" choice="opt"><replaceable>options</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">makemime</command>
+ <arg choice="opt" rep="norepeat">@<replaceable>filename</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+<command moreinfo="none">makemime</command> creates MIME-formatted messages of arbitrary
+complexity.
+<command moreinfo="none">makemime</command> reads one or more individual files,
+MIME-encodes them, adds basic MIME headers, and adds any additional headers
+specified bye command line options. The result is saved to another file or
+standard output. Complex MIME-formatted messages are created by piping together
+multiple instances of <command moreinfo="none">makemime</command>.
+Advanced options direct <command moreinfo="none">makemime</command> to
+<function moreinfo="none">fork</function>() itself, and handle the details of setting up all
+the pipelines.</para>
+
+ <para>
+In most cases, options for <command moreinfo="none">makemime</command> come directly from the
+command line. @<filename moreinfo="none">filename</filename> reads the options from a file.
+"<literal moreinfo="none">@&amp;<replaceable>n</replaceable></literal>" reads options from a pipe
+on file descriptor #<replaceable>n</replaceable>.
+"<literal moreinfo="none">@-</literal>" is a shortcut for
+"<literal moreinfo="none">@&amp;0</literal>", which reads options from standard input.</para>
+
+ <para>
+When options are read from a file or a pipe, each option must be on a
+line by itself. If an option requires an argument, the argument must follow
+on the next line.</para>
+
+ <para>
+For readability, leading whitespace is deleted when options
+are read from a file or a pipe. Empty lines are also ignored, as well as lines
+that begin with the '<token>#</token>' character.</para>
+
+ <para>
+Options and their arguments may contain characters that are special
+characters to the shell, such as '<token>(</token>' and '<token>)</token>'.
+These characters must be backslashed when specified on the command line, to
+avoid their special meaning to the shell.
+These characters MUST NOT be backslashed when options are read
+from a file or a pipe. Similarly, the contents of most headers nearly always
+include spaces. Therefore they must be quoted when specified on the command
+line. Header contents MUST NOT be quoted when options come from a file or a
+pipe.</para>
+
+ <para><command moreinfo="none">makemime</command> reads the content to be formatted
+as a MIME message from some other file.
+The files can also be a pipe. It is possible to supply both the
+options and a file from the same pipe, by terminating the options list with
+a line containing the single character "-". The remainder of the pipe will be
+available to be used as an input file (which must be explicitly specified
+by one of the options). Of course, only one input file can come from a single
+pipe.</para>
+
+ <refsect2>
+<title>MIME overview</title>
+
+ <para>
+A MIME-formatted message contains one or several MIME sections. MIME headers
+specify how multiple MIME sections are to be interpreted as a whole (whether
+they are attached together; whether they are alternative representations of
+the same content; or something even more esoteric). This manual page gives a
+very brief, terse, overview of basic MIME concepts. The description is biased
+towards describing the functionality of the <command moreinfo="none">makemime</command>
+utility.
+See
+<ulink url="http://www.rfc-editor.org/rfc/rfc2045.txt">RFC 2045</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2046.txt">RFC 2046</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2047.txt">RFC 2047</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2048.txt">RFC 2048</ulink>, and
+<ulink url="http://www.rfc-editor.org/rfc/rfc2048.txt">RFC 2049</ulink>
+for a formal definition of MIME-formatted messages.</para>
+
+ <para>
+Each file in a MIME message is encoded as a single MIME section. A MIME
+section consists of at least one header line,
+"<literal moreinfo="none">Content-Type:</literal>".
+The "<literal moreinfo="none">Content-Type:</literal>" header gives the type of the data
+ontained in the file. Other header lines may also be present.
+Their relative order does not matter. MIME
+headers are followed by a blank line, then the contents of the file, encoded
+appropriately.
+All MIME sections generated by <command moreinfo="none">makemime</command> will always
+contain another header,
+"<literal moreinfo="none">Content-Transfer-Encoding:</literal>". This header gives the
+encoding method used for the file; it is an optional header, but
+<command moreinfo="none">makemime</command> always creates it.</para>
+
+ <para>
+The MIME encoding method defaults to
+"<literal moreinfo="none">7bit</literal>" if this header is absent.
+<literal moreinfo="none">7bit</literal>
+encoding is only suitable for plain text messages in the US-ASCII character
+set.
+The "<literal moreinfo="none">8bit</literal>" encoding method is used by plain text messages
+in other character sets that use octets with the high bit set. An
+alternative to 8bit encoding is
+"<literal moreinfo="none">quoted-printable</literal>". The "<literal moreinfo="none">base64</literal>" encoding
+method is used for files containing binary data (anything other than plain
+text).</para>
+
+ <para>
+MIME sections that contain text messages have their
+"<literal moreinfo="none">Content-Type:</literal>" header
+set to "<literal moreinfo="none">text/plain</literal>";
+or "<literal moreinfo="none">text/html</literal>" for HTML messages.
+There are also several
+other, rare, content types that can be used. MIME sections that contain other
+kinds of data will use some other, appropriate
+"<literal moreinfo="none">Content-Type:</literal>" header, such as
+"<literal moreinfo="none">image/gif</literal>", or "<literal moreinfo="none">audio/x-wav</literal>".</para>
+
+ <para>
+MIME sections that contain textual content may also use the
+<literal moreinfo="none">base64</literal> encoding
+method, they are not required to use <literal moreinfo="none">7bit</literal>,
+<literal moreinfo="none">8bit</literal>, or <literal moreinfo="none">quoted-printable</literal>.
+"<literal moreinfo="none">text/pdf</literal>" sections, that contain PDF files,
+typically contain binary data
+and must use the <literal moreinfo="none">base64</literal> encoding.
+Consequently, MIME sections that typically
+contain binary data, such as
+<literal moreinfo="none">image/gif</literal> and <literal moreinfo="none">audio/x-wav</literal>,
+are free to use
+encodings other than <literal moreinfo="none">base64</literal>, as long as all the data can
+be represented by
+printable characters (but, in practice, that never happens).</para>
+
+ <para>
+MIME sections may also contain other, optional, headers such as
+"<literal moreinfo="none">Content-Disposition:</literal>",
+"<literal moreinfo="none">Content-ID:</literal>", and "<literal moreinfo="none">Content-Name:</literal>".
+Consult the
+appropriate RFCs for the specific usage of these headers. These headers can be
+added by <command moreinfo="none">makemime</command> by using the
+<option>-a</option> option, as described below. These
+headers play no part in creating the overall structure of a MIME-encoded
+message, and <command moreinfo="none">makemime</command> does not care much about these
+headers. It simply
+includes them, and their content, upon request.</para>
+
+ <para>
+Multiple files are formatted as a single message MIME message in two steps:
+first, by creating a MIME section for each file;
+and then creating a single MIME section that contains other MIME sections.
+A "<literal moreinfo="none">multipart/mixed</literal>" MIME section contains a
+collection of MIME sections that represent different objects, attached
+together.
+A "<literal moreinfo="none">multipart/alternative</literal>" MIME section contains a
+collection of MIME
+sections which are alternative representations of the same object, such as an
+HTML and a plain text version of the same message. Other "multipart" MIME
+sections also exist, and their usage is defined by their respective
+RFCs.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Creating a single MIME section</title>
+
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">makemime</command>
+ <arg choice="req" rep="norepeat">-c "<replaceable>type</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-e "<replaceable>encoding</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-o <replaceable>outputfile</replaceable></arg>
+ <arg choice="opt" rep="norepeat">-C "<replaceable>charset</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-N "<replaceable>name"</replaceable></arg>
+ <arg rep="repeat" choice="opt">-a "<replaceable>header: value"</replaceable></arg>
+ <arg choice="req" rep="norepeat"><replaceable>filename</replaceable></arg>
+ </cmdsynopsis>
+
+ <para>
+The <option>-c</option> option reads <filename moreinfo="none">filename</filename>,
+encodes it appropriately, adds the
+"<literal moreinfo="none">Content-Type: <replaceable>type</replaceable></literal>" and
+"<literal moreinfo="none">Content-Transfer-Encoding:</literal>" MIME headers, then writes the
+result to standard output. <literal moreinfo="none">type</literal> can be any valid MIME type,
+except for <literal moreinfo="none">multipart</literal>.
+Setting <filename moreinfo="none">filename</filename> to "<literal moreinfo="none">-</literal>"
+reads from standard input.
+Setting <filename moreinfo="none">filename</filename> to "&amp;<replaceable>n</replaceable>"
+reads from file descriptor #<replaceable>n</replaceable>.</para>
+
+ <para>
+The <option>-C</option> option sets the MIME <literal moreinfo="none">charset</literal>
+attribute for <literal moreinfo="none">text/plain</literal> content. The <option>-N</option>
+option sets the <literal moreinfo="none">name</literal> attribute for
+<literal moreinfo="none">Content-Type:</literal>.</para>
+
+ <para>
+<replaceable>encoding</replaceable> argument should be specified. It's more
+efficient to do so. <replaceable>encoding</replaceable> must be one of the
+following:
+<literal moreinfo="none">7bit</literal>, <literal moreinfo="none">8bit</literal>,
+<literal moreinfo="none">quoted-printable</literal>, or <literal moreinfo="none">base64</literal>.</para>
+
+ <para>
+If <replaceable>encoding</replaceable> is not specified,
+<command moreinfo="none">makemime</command>
+reads the <filename moreinfo="none">filename</filename> twice - once to figure out the best
+encoding method, and the second time to encode <filename moreinfo="none">filename</filename>.
+If <filename moreinfo="none">filename</filename> is a pipe <command moreinfo="none">makemime</command>
+creates a temporary file, which is not very efficient if
+<filename moreinfo="none">filename</filename> is large.
+However letting <command moreinfo="none">makemime</command> pick the encoding method
+is more convenient if <filename moreinfo="none">filename</filename> is relatively small.</para>
+
+ <para>
+Another possibility is to omit <replaceable>encoding</replaceable> and set
+<replaceable>type</replaceable> to <literal moreinfo="none">auto</literal>.
+This combination sets "<literal moreinfo="none">Content-Type:</literal>" to either
+<literal moreinfo="none">text/plain</literal>, or
+<literal moreinfo="none">application/octet-stream</literal>, based on the selected
+<replaceable>encoding</replaceable>.</para>
+
+ <para>
+By default the encoded MIME section is written to standard output.
+The <option>-o</option> option writes the MIME section to
+<replaceable>outputfile</replaceable>. <replaceable>outputfile</replaceable> may be
+"&amp;<replaceable>n</replaceable>",
+which writes the MIME section to a pipe on file descriptor
+#<replaceable>n</replaceable>.</para>
+
+ <para>
+<command moreinfo="none">makemime</command> does not generate any other headers.
+Particularly, the
+"<literal moreinfo="none">Mime-Version:</literal>" header is required for
+MIME-formatted E-mail messages. Additional headers are specified by the
+<option>-a</option> option, which may be used
+multiple times to insert multiple headers.
+<command moreinfo="none">makemime</command> doesn't do anything
+with them except to insert the headers into the generated MIME section.</para>
+
+ <para>Note that
+"<literal moreinfo="none">Mime-Version:</literal>" is only required for the top level
+MIME section.
+This header is not required for individual MIME sections that are later
+combined into a multipart MIME collection.</para>
+
+ <note>
+ <para>
+The <option>-c</option> option must occur listed first, the remaining
+options must follow the <option>-c</option> option.</para>
+ </note>
+ </refsect2>
+
+ <refsect2>
+ <title>Creating a multipart MIME collection</title>
+
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">makemime</command>
+ <arg choice="req" rep="norepeat">-m "multipart/<replaceable>type</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-e "<replaceable>encoding</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-o <replaceable>outputfile</replaceable></arg>
+ <arg rep="repeat" choice="opt">-a "<replaceable>header: value"</replaceable></arg>
+ <arg choice="req" rep="norepeat"><replaceable>filename</replaceable></arg>
+ </cmdsynopsis>
+
+ <para>
+The <option>-m</option> option is identical to the <option>-c</option> option,
+except for three differences.</para>
+
+ <para>
+<replaceable>type</replaceable> must be either
+"<literal moreinfo="none">multipart/mixed</literal>",
+"<literal moreinfo="none">multipart/alternative</literal>", or
+some other MIME multipart content type. Additionally,
+"<replaceable>encoding</replaceable>" can only be
+"<literal moreinfo="none">7bit</literal>" or "<literal moreinfo="none">8bit</literal>", and will default to "<literal moreinfo="none">8bit</literal>" if not specified. Finally,
+<filename moreinfo="none">filename</filename> must be a MIME-formatted section, NOT a regular
+file. Usually
+<filename moreinfo="none">filename</filename> is created by a previous
+invocation of <command moreinfo="none">makemime</command> (it can also be a pipe, like
+the <option>-c</option> option), but it can be created via any other
+means.</para>
+
+ <para>
+The <option>-m</option> option creates an initial multipart MIME collection,
+that contains
+only one MIME section, taken from <filename moreinfo="none">filename</filename>.
+The collection is written to standard output, or the pipe or
+to <replaceable>outputfile</replaceable>.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Creating a multipart MIME section</title>
+
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">makemime</command>
+ <arg choice="req" rep="norepeat">-j <replaceable>file1</replaceable>"</arg>
+ <arg choice="opt" rep="norepeat">-o <replaceable>outputfile</replaceable></arg>
+ <arg choice="req" rep="norepeat"><replaceable>file2</replaceable></arg>
+ </cmdsynopsis>
+
+ <para>
+This option adds a MIME section to an existing MIME collection.
+<replaceable>file1</replaceable> must be a MIME collection that was
+previously created by the <option>-m</option> option.
+<replaceable>file2</replaceable> must be a MIME section that was previously
+created by the <option>-c</option> option.
+The <option>-j</option> options adds the MIME section in
+<replaceable>file2</replaceable> to the MIME collection in
+<replaceable>file1</replaceable>. The result is written to standard output
+or to <replaceable>outputfile</replaceable>.</para>
+
+ <para>
+
+<replaceable>file1</replaceable> and/or <replaceable>file2</replaceable> may
+be
+"<literal moreinfo="none">@&amp;<replaceable>n</replaceable></literal>" which reads from
+file descriptor #<replaceable>n</replaceable>.
+The <replaceable>outputfile</replaceable>
+may also specify a file descriptor.</para>
+
+ <para><replaceable>file1</replaceable> and
+<replaceable>file2</replaceable> should ideally be created by
+<command moreinfo="none">makemime</command> as well.
+It's also possible to use MIME-formatted files created by other software, but
+with some degree of care. <command moreinfo="none">makemime</command> is not intended to be a
+MIME parser, but a MIME generator. However some amount of MIME parsing is
+necessary to append a MIME section to an existing MIME collection.
+<command moreinfo="none">makemime</command>'s parsing is sufficient
+for appending a new section to a MIME collection, as long as the
+MIME headers in the MIME collections are straightforward. Very convoluted MIME
+headers may confuse <command moreinfo="none">makemime</command>, and it may not be able to
+handle them.</para>
+ </refsect2>
+
+ <refsect2>
+<title>Recursive MIME collections</title>
+
+ <para>
+MIME collection may contain other MIME collections as well as MIME
+sections. The <option>-m</option> and the <option>-j</option> options may use
+a multipart MIME collection in place of a MIME section automatically
+because a multipart MIME collection is just a special type of a MIME section.
+The following example
+encodes a text message that can be alternatively represented as HTML or plain
+text, with some additional attachments:</para>
+
+ <para>
+1. Create a MIME collection that has a
+<literal moreinfo="none">text/plain</literal> and a <literal moreinfo="none">text/html</literal> MIME
+section.</para>
+
+ <para>
+2. Create a MIME collection consisting of the MIME section generated in
+step one, plus additional MIME sections containing other attachments.</para>
+
+ <para>For example:</para>
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+# Take two files containing the text and the html version of a message, and
+# add MIME headers to them.
+
+makemime -c "text/plain; charset=iso-8859-1" -o tmp1.txt msg.txt
+makemime -c "text/html; charset=iso-8859-1" -o tmp1.html msg.html
+
+# Combine the result into a multipart/alternative collection
+
+makemime -m "multipart/alternative" -a "Content-Disposition: inline" \
+ -o tmp.ma1 tmp1.txt
+makemime -j tmp.ma1 -o tmp.ma2 tmp1.html
+
+# Add MIME headers to an image attachment.
+
+makemime -c "image/gif" -a "Content-Disposition: attachment" \
+ -o tmp2.gif attachment.gif
+
+# Create the final multipart/mixed collection
+
+makemime -m "multipart/mixed" -a "Mime-Version: 1.0" \
+ -o tmp.mm1 tmp.ma2
+makemime -j tmp.mm1 -o output.msg tmp2.gif
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+<filename moreinfo="none">output.msg</filename> now contains the complete MIME collection.
+Just add the
+<literal moreinfo="none">Subject:</literal>, <literal moreinfo="none">From:</literal>, and
+<literal moreinfo="none">To:</literal> headers (can also be done by additional
+<option>-a</option> options, of
+course), and send it on its way.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Building complex MIME encodings</title>
+
+ <para>
+There are several different ways to build complete MIME encodings from
+multiple MIME sections. One way is to use temporary files to create MIME
+sections, then combine them together into a single MIME collection. A slightly
+more complicated approach involves setting up pipes between multiple makemime
+processes, in order to avoid using temporary files.</para>
+
+ <para>This can be done manually, by hand. It is also possible to have
+<command moreinfo="none">makemime</command> do this automatically. <command moreinfo="none">makemime</command>
+will set up these pipes and run multiple instances of itself to create a
+single MIME collection, with
+multiple attachments of complexity limited only by your system's limit on
+the maximum number of open files and pipes.</para>
+
+ <para>
+Any file that's read by the
+<option>-c</option>,
+<option>-m</option>,
+and
+<option>-j</option>
+options (
+<option>-o</option>
+specifies a file to
+create, and doesn't count) may be replaced by a single argument containing a
+left parenthesis, additional options, then a single argument containing a
+right parenthesis.
+A single invocation of <command moreinfo="none">makemime</command> can only use one
+<option>-c</option>, <option>-m</option>, or <option>-j</option> option.
+However, another <option>-c</option>, <option>-m</option>, or
+<option>-j</option> option may be specified
+inside the left and the right parenthesis, and its output is used in place of
+the file it replaced. In the previous example the third and the fourth
+invocation of <command moreinfo="none">makemime</command> can be replaced with the following
+command:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+makemime -j \( \
+ -m "multipart/alternative" \
+ -a "Content-Disposition: inline" tmp1.txt \
+ \) -o tmp.ma2 \
+ tmp1.html
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+Note that the parenthesis must be backslashed, to avoid their special
+meaning to the shell. An equivalent argument file would have the following
+contents:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+-j
+ (
+ -m
+ multipart/alternative
+ -a
+ Content-Disposition: inline
+ tmp1.txt
+ )
+ -o
+ tmp.ma2
+ tmp1.html
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+These constructs can be arbitrarily nested, and are limited by the amount
+of available memory and resources. The entire sequence in the previous
+section is equivalent to the following command:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+makemime -j \
+ \( \
+ -m "multipart/mixed" \
+ -a "Mime-Version: 1.0" \
+ \( \
+ -j \
+ \( \
+ -m "multipart/alternative" \
+ -a "Content-Disposition: inline" \
+ \( \
+ -c "text/plain; charset=iso-8859-1" \
+ msg.txt \
+ \) \
+ \) \
+ \( \
+ -c "text/html; charset=iso-8859-1" \
+ msg.html \
+ \) \
+ \) \
+ \) \
+ -o output.msg \
+ \( \
+ -c "image/gif" \
+ -a "Content-Disposition: attachment" \
+ attachment.gif \
+ \)
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+An equivalent argument file would be:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+-j
+(
+ -m
+ multipart/mixed
+ -a
+ Mime-Version: 1.0
+ (
+ -j
+ (
+ -m
+ multipart/alternative
+ -a
+ Content-Disposition: inline
+ (
+ -c
+ text/plain; charset=iso-8859-1
+ msg.txt
+ )
+ )
+ (
+ -c
+ text/html; charset=iso-8859-1
+ msg.html
+ )
+ )
+)
+-o
+ output.msg
+(
+ -c
+ image/gif
+ -a
+ Content-Disposition: attachment
+ attachment.gif
+)
+</programlisting>
+ </informalexample>
+ </blockquote>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="maildrop.html"><citerefentry><refentrytitle>maildrop</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<ulink url="maildropfilter.html"><citerefentry><refentrytitle>maildropfilter</refentrytitle><manvolnum>5</manvolnum></citerefentry></ulink>,
+<ulink url="reformail.html"><citerefentry><refentrytitle>reformail</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<ulink url="reformime.html"><citerefentry><refentrytitle>reformime</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<citerefentry><refentrytitle>egrep</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+<citerefentry><refentrytitle>grep</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+<citerefentry><refentrytitle>sendmail</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2045.txt">RFC 2045</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2046.txt">RFC 2046</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2047.txt">RFC 2047</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2048.txt">RFC 2048</ulink>,
+<ulink url="http://www.rfc-editor.org/rfc/rfc2048.txt">RFC 2049</ulink>.</para>
+ </refsect1>
+</refentry>
diff --git a/rfc2045/reformime.c b/rfc2045/reformime.c
new file mode 100644
index 0000000..26396cb
--- /dev/null
+++ b/rfc2045/reformime.c
@@ -0,0 +1,1228 @@
+/*
+** Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <langinfo.h>
+
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include "rfc2045.h"
+#include "rfc822/rfc822.h"
+#include "rfc822/rfc2047.h"
+#include "rfc2045charset.h"
+#include "unicode/unicode.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include "numlib/numlib.h"
+
+#if HAS_GETHOSTNAME
+#else
+int gethostname(const char *, size_t);
+#endif
+
+extern int rfc2045_in_reformime;
+
+static const char *defchset;
+
+
+void rfc2045_error(const char *errmsg)
+{
+ fprintf(stderr, "reformime: %s\n", errmsg);
+ exit(1);
+}
+
+static void do_print_structure(struct rfc2045 *p, struct rfc2045id *id, void *ptr)
+{
+ p=p;
+ ptr=p;
+
+ while (id)
+ {
+ printf("%d%c", id->idnum, id->next ? '.':'\n');
+ id=id->next;
+ }
+}
+
+static int decode_to_file(const char *p, size_t n, void *ptr)
+{
+FILE *fp=(FILE *)ptr;
+
+ while (n)
+ {
+ --n;
+ if (putc((int)(unsigned char)*p++, fp) == EOF)
+ {
+ perror("write");
+ exit(1);
+ }
+ }
+ return (0);
+}
+
+void usage()
+{
+ fprintf(stderr, "Usage: reformime [options]\n");
+ fprintf(stderr, " -d - parse a delivery status notification.\n");
+ fprintf(stderr, " -e - extract contents of MIME section.\n");
+ fprintf(stderr, " -x - extract MIME section to a file.\n");
+ fprintf(stderr, " -X - pipe MIME section to a program.\n");
+ fprintf(stderr, " -i - show MIME info.\n");
+ fprintf(stderr, " -s n.n.n.n[,n.n.n.n]* - specify MIME section(s).\n");
+ fprintf(stderr, " -r - rewrite message, filling in missing MIME headers.\n");
+ fprintf(stderr, " -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.\n");
+ fprintf(stderr, " -r8 - also convert quoted-printable encoding to 8bit, if possible.\n");
+ fprintf(stderr, " -c charset - default charset for rewriting, -o, and -O.\n");
+ fprintf(stderr, " -m [file] [file]... - create a MIME message digest.\n");
+ fprintf(stderr, " -h \"header\" - decode RFC 2047-encoded header.\n");
+ fprintf(stderr, " -o \"header\" - encode unstructured header using RFC 2047.\n");
+ fprintf(stderr, " -O \"header\" - encode address list header using RFC 2047.\n");
+
+ exit(1);
+}
+
+static char *tempname(const char *tempdir)
+{
+char pidbuf[NUMBUFSIZE], timebuf[NUMBUFSIZE], hostnamebuf[256];
+static unsigned counter=0;
+time_t t;
+char *p;
+
+ libmail_str_pid_t(getpid(), pidbuf);
+ time(&t);
+ libmail_str_time_t(t, timebuf);
+ hostnamebuf[sizeof(hostnamebuf)-1]=0;
+ if (gethostname(hostnamebuf, sizeof(hostnamebuf)))
+ hostnamebuf[0]=0;
+ p=malloc(strlen(tempdir)+strlen(pidbuf)+strlen(timebuf)+
+ strlen(hostnamebuf)+100);
+ if (!p) return (0);
+ sprintf(p, "%s/%s.%s-%u.%s", tempdir, timebuf, pidbuf, counter++,
+ hostnamebuf);
+ return (p);
+}
+
+struct rfc2045 *read_message()
+{
+char buf[BUFSIZ];
+struct rfc2045 *p=rfc2045_alloc_ac();
+FILE *tempfp=0;
+int l;
+
+ if (fseek(stdin, 0L, SEEK_END) < 0 ||
+ fseek(stdin, 0L, SEEK_SET) < 0) /* Pipe, save to temp file */
+ {
+ tempfp=tmpfile();
+ }
+
+ while ((l=fread(buf, 1, sizeof(buf), stdin)) > 0)
+ {
+
+ rfc2045_parse(p, buf, l);
+ if (tempfp && fwrite(buf, l, 1, tempfp) != 1)
+ {
+ perror("fwrite");
+ exit(1);
+ }
+ }
+ rfc2045_parse_partial(p);
+
+ if (tempfp) /* Replace stdin */
+ {
+ dup2(fileno(tempfp), 0);
+ fclose(tempfp);
+ }
+ return (p);
+}
+
+void print_structure(struct rfc2045 *p)
+{
+ rfc2045_decode(p, &do_print_structure, 0);
+}
+
+static void notfound(const char *p)
+{
+ fprintf(stderr, "reformime: MIME section %s not found.\n", p);
+ exit(1);
+}
+
+static void do_print_info(struct rfc2045 *s)
+{
+const char *content_type, *transfer_encoding, *charset;
+off_t start, end, body;
+char *content_name;
+off_t nlines, nbodylines;
+const char *p;
+
+char *disposition_name, *disposition_filename;
+
+ rfc2045_mimeinfo(s, &content_type, &transfer_encoding, &charset);
+ rfc2045_mimepos(s, &start, &end, &body, &nlines, &nbodylines);
+
+ if (rfc2231_udecodeType(s, "name", NULL, &content_name) < 0)
+ {
+ perror("malloc");
+ exit(1);
+ }
+
+ printf("content-type: %s\n", content_type);
+ if (*content_name)
+ {
+ printf("content-name: %s\n", content_name);
+ }
+ free(content_name);
+
+ printf("content-transfer-encoding: %s\n", transfer_encoding);
+ printf("charset: %s\n", charset);
+ if (s->content_disposition && *s->content_disposition)
+ printf("content-disposition: %s\n", s->content_disposition);
+
+ if ((rfc2231_udecodeDisposition(s, "name", NULL, &disposition_name) < 0
+ && (disposition_name=strdup("")) == NULL)
+ ||
+ (rfc2231_udecodeDisposition(s, "filename", NULL,
+ &disposition_filename) < 0
+ && (disposition_filename=strdup("")) == NULL))
+ {
+ perror("malloc");
+ exit(1);
+ }
+
+ if (*disposition_name)
+ printf("content-disposition-name: %s\n", disposition_name);
+
+ free(disposition_name);
+
+ if (*disposition_filename)
+ {
+ printf("content-disposition-filename: %s\n",
+ disposition_filename);
+ }
+ free(disposition_filename);
+
+ if (*(p=rfc2045_content_id(s)))
+ printf("content-id: <%s>\n", p);
+ if (*(p=rfc2045_content_description(s)))
+ {
+ char *s=rfc822_display_hdrvalue_tobuf("content-description",
+ p,
+ defchset,
+ NULL,
+ NULL);
+
+ if (!s)
+ {
+ perror("rfc2047_decode_unicode");
+ exit(1);
+ }
+ printf("content-description: %s\n", s);
+ free(s);
+ }
+ if (*(p=rfc2045_content_language(s)))
+ printf("content-language: %s\n", p);
+ if (*(p=rfc2045_content_md5(s)))
+ printf("content-md5: %s\n", p);
+
+ printf("starting-pos: %lu\n", (unsigned long)start);
+ printf("starting-pos-body: %lu\n", (unsigned long)body);
+ printf("ending-pos: %lu\n", (unsigned long)end);
+ printf("line-count: %lu\n", (unsigned long)nlines);
+ printf("body-line-count: %lu\n", (unsigned long)nbodylines);
+}
+
+static void do_print_info_multiple(struct rfc2045 *p, struct rfc2045id *id,
+ void *ptr)
+{
+ printf("section: ");
+ do_print_structure(p, id, ptr);
+ do_print_info(p);
+ printf("\n");
+}
+
+void print_info(struct rfc2045 *p, const char *mimesection)
+{
+struct rfc2045 *s;
+
+ if (mimesection)
+ {
+ s=rfc2045_find(p, mimesection);
+ if (!s)
+ notfound(mimesection);
+ printf("section: %s\n", mimesection);
+ do_print_info(s);
+ return;
+ }
+ rfc2045_decode(p, &do_print_info_multiple, 0);
+}
+
+static void do_print_section(struct rfc2045 *s, FILE *fp)
+{
+off_t start, end, body;
+off_t nlines;
+off_t nbodylines;
+
+ rfc2045_mimepos(s, &start, &end, &body, &nlines, &nbodylines);
+
+ if (fseek(stdin, body, SEEK_SET) == -1)
+ {
+ perror("fseek");
+ exit(1);
+ }
+
+ rfc2045_cdecode_start(s, &decode_to_file, fp);
+ while (body < end)
+ {
+ char buf[BUFSIZ];
+ size_t n=sizeof(buf);
+
+ if ((off_t)n > end-body) n=end-body;
+ n=fread(buf, 1, n, stdin);
+ if (n == 0)
+ {
+ perror("fread");
+ exit(1);
+ }
+ rfc2045_cdecode(s, buf, n);
+ body += n;
+ }
+ rfc2045_cdecode_end(s);
+}
+
+void print_decode(struct rfc2045 *p, const char *mimesection)
+{
+struct rfc2045 *s;
+
+ if (!mimesection)
+ usage();
+
+ s=rfc2045_find(p, mimesection);
+ if (!s)
+ notfound(mimesection);
+
+ do_print_section(s, stdout);
+}
+
+void rewrite(struct rfc2045 *p, int rwmode)
+{
+ struct rfc2045src *src;
+
+ rfc2045_ac_check(p, rwmode);
+
+ src=rfc2045src_init_fd(fileno(stdin));
+
+ if (src == NULL || rfc2045_rewrite(p, src, fileno(stdout),
+ "reformime (" RFC2045PKG " " RFC2045VER ")"))
+ {
+ perror("reformime");
+ exit(1);
+ }
+ rfc2045src_deinit(src);
+}
+
+static char *get_suitable_filename(struct rfc2045 *r, const char *pfix,
+ int ignore_filename)
+{
+char *disposition_name;
+char *disposition_filename;
+char *filename_buf;
+char *content_name;
+char *p, *q;
+char *dyn_disp_name=0;
+
+const char *disposition_filename_s;
+
+ if (rfc2231_udecodeDisposition(r, "name", NULL, &disposition_name) < 0)
+ disposition_name=NULL;
+
+ if (rfc2231_udecodeDisposition(r, "filename", NULL,
+ &disposition_filename) < 0)
+ disposition_filename=NULL;
+
+ if (rfc2231_udecodeType(r, "name", NULL,
+ &content_name) < 0)
+ content_name=NULL;
+
+ disposition_filename_s=disposition_filename;
+
+ if (!disposition_filename_s || !*disposition_filename_s)
+ disposition_filename_s=disposition_name;
+ if (!disposition_filename_s || !*disposition_filename_s)
+ disposition_filename_s=content_name;
+
+ filename_buf=strdup(disposition_filename_s ? disposition_filename_s:"");
+
+ if (!filename_buf)
+ {
+ perror("strdup");
+ exit(1);
+ }
+
+ if (content_name) free(content_name);
+ if (disposition_name) free(disposition_name);
+ if (disposition_filename) free(disposition_filename);
+
+ if (strlen(filename_buf) > 32)
+ {
+ p=filename_buf;
+ q=filename_buf + strlen(filename_buf)-32;
+ while ( (*p++ = *q++) != 0)
+ ;
+ }
+
+ /* Strip leading/trailing spaces */
+
+ p=filename_buf;
+ while (*p && isspace((int)(unsigned char)*p))
+ ++p;
+
+ q=filename_buf;
+ while ((*q=*p) != 0)
+ {
+ ++p;
+ ++q;
+ }
+
+ for (p=q=filename_buf; *p; p++)
+ if (!isspace((int)(unsigned char)*p))
+ q=p+1;
+ *q=0;
+
+ disposition_filename_s=filename_buf;
+
+ if (ignore_filename)
+ {
+ char numbuf[NUMBUFSIZE];
+ static size_t counter=0;
+ const char *p=libmail_str_size_t(++counter, numbuf);
+
+ dyn_disp_name=malloc(strlen(disposition_filename_s)
+ + strlen(p)+2);
+ if (!dyn_disp_name)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ disposition_filename_s=strcat(strcat(strcpy(
+ dyn_disp_name, p), "-"),
+ disposition_filename_s);
+ }
+ else if (!disposition_filename_s || !*disposition_filename_s)
+ {
+ dyn_disp_name=tempname(".");
+ disposition_filename_s=dyn_disp_name+2; /* Skip over ./ */
+ }
+
+ p=malloc((pfix ? strlen(pfix):0)+strlen(disposition_filename_s)+1);
+ if (!p)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ *p=0;
+ if (pfix) strcpy(p, pfix);
+ q=p+strlen(p);
+ for (strcpy(q, disposition_filename_s); *q; q++)
+ if (!isalnum(*q) && *q != '.' && *q != '-')
+ *q='_';
+
+ if (dyn_disp_name) free(dyn_disp_name);
+
+ if (!pfix)
+ {
+ const char *content_type_s;
+ const char *content_transfer_encoding_s;
+ const char *charset_s;
+ int c;
+ static char filenamebuf[256];
+ char *t;
+ FILE *tty;
+
+ if ((tty=fopen("/dev/tty", "r+")) == 0)
+ {
+ perror("/dev/tty");
+ exit(1);
+ }
+
+ rfc2045_mimeinfo(r, &content_type_s,
+ &content_transfer_encoding_s, &charset_s);
+
+ fprintf (tty, "Extract %s? ", content_type_s);
+ fflush(tty);
+ c=getc(tty);
+ if (c != '\n' && c != EOF)
+ {
+ int cc;
+
+ while ((cc=getc(tty)) != '\n' && cc != EOF)
+ ;
+ }
+ if (c != 'y' && c != 'Y')
+ {
+ free(p);
+ fclose(tty);
+ free(filename_buf);
+ return (0);
+ }
+ fprintf (tty, "Filename [%s]: ", p);
+ if (fgets(filenamebuf, sizeof(filenamebuf)-1, tty) == NULL)
+ filenamebuf[0]=0;
+
+ fclose(tty);
+ t=strchr(filenamebuf, '\n');
+ if (t) *t=0;
+ else
+ {
+ fprintf(stderr, "Filename too long.\n");
+ exit(1);
+ }
+ if (filenamebuf[0])
+ {
+ free(p);
+ p=strdup(filenamebuf);
+ if (!p)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ }
+ }
+ free(filename_buf);
+ return (p);
+}
+
+static void extract_file(struct rfc2045 *p,
+ const char *filename, int argc, char **argv)
+{
+char *f;
+FILE *fp;
+int ignore=0;
+
+ for (;;)
+ {
+ int fd;
+
+ f=get_suitable_filename(p, filename, ignore);
+ if (!f) return;
+
+ fd=open(f, O_WRONLY|O_CREAT|O_EXCL, 0666);
+ if (fd < 0)
+ {
+ if (errno == EEXIST)
+ {
+ printf("%s exists.\n", f);
+ free(f);
+ ignore=1;
+ continue;
+ }
+
+ perror(f);
+ exit(1);
+ }
+ fp=fdopen(fd, "w");
+ if (!fp)
+ {
+ perror("fdopen");
+ exit(1);
+ }
+ break;
+ }
+
+ do_print_section(p, fp);
+ if (fflush(fp) || ferror(fp))
+ {
+ perror("write");
+ exit(1);
+ }
+ fclose(fp);
+ free(f);
+}
+
+static void extract_pipe(struct rfc2045 *p,
+ const char *filename,
+ int argc, char **argv)
+{
+char *f=get_suitable_filename(p, "FILENAME=", 0);
+int pipefd[2];
+pid_t pid, p2;
+FILE *fp;
+int waitstat;
+
+ if (argc == 0)
+ {
+ fprintf(stderr, "reformime: Invalid -X option.\n");
+ exit(1);
+ }
+
+ if (pipe(pipefd))
+ {
+ perror("pipe");
+ exit(1);
+ }
+
+ if ((fp=fdopen(pipefd[1], "w")) == 0)
+ {
+ perror("fdopen");
+ exit(1);
+ }
+
+ while ((pid=fork()) == -1)
+ {
+ sleep(2);
+ }
+
+ if (pid == 0)
+ {
+ const char *content_type_s;
+ const char *content_transfer_encoding_s;
+ const char *charset_s;
+
+ if (!f) f="FILENAME=attachment.dat";
+ putenv(f);
+ rfc2045_mimeinfo(p, &content_type_s,
+ &content_transfer_encoding_s, &charset_s);
+ f=malloc(strlen(content_type_s)
+ +sizeof("CONTENT_TYPE="));
+ if (!f)
+ {
+ perror("malloc");
+ exit(1);
+ }
+ strcat(strcpy(f, "CONTENT_TYPE="), content_type_s);
+ putenv(f);
+ dup2(pipefd[0], 0);
+ close(pipefd[0]);
+ close(pipefd[1]);
+ execv(argv[0], argv);
+ perror("exec");
+ _exit(1);
+ }
+ close(pipefd[0]);
+ signal(SIGPIPE, SIG_IGN);
+ do_print_section(p, fp);
+ signal(SIGPIPE, SIG_DFL);
+ fclose(fp);
+ close(pipefd[1]);
+
+ while ((p2=wait(&waitstat)) != pid && p2 != -1)
+ ;
+ free(f);
+
+ if ((p2 == pid) && WIFEXITED(waitstat))
+ {
+ if (WEXITSTATUS(waitstat) != 0)
+ {
+ fprintf(stderr, "reformime: %s exited with status %d.\n",
+ argv[0], WEXITSTATUS(waitstat));
+ exit(WEXITSTATUS(waitstat) + 20);
+ }
+ }
+}
+
+static void extract_section(struct rfc2045 *top_rfcp, const char *mimesection,
+ const char *extract_filename, int argc, char **argv,
+ void (*extract_func)(struct rfc2045 *, const char *,
+ int, char **))
+{
+ if (mimesection)
+ {
+ top_rfcp=rfc2045_find(top_rfcp, mimesection);
+ if (!mimesection)
+ notfound(mimesection);
+ if (top_rfcp->firstpart)
+ {
+ fprintf(stderr, "reformime: MIME section %s is a compound section.\n", mimesection);
+ exit(1);
+ }
+ (*extract_func)(top_rfcp, extract_filename, argc, argv);
+ return;
+ }
+
+ /* Recursive */
+
+ if (top_rfcp->firstpart)
+ {
+ for (top_rfcp=top_rfcp->firstpart; top_rfcp;
+ top_rfcp=top_rfcp->next)
+ extract_section(top_rfcp, mimesection,
+ extract_filename, argc, argv, extract_func);
+ return;
+ }
+
+ if (!top_rfcp->isdummy)
+ (*extract_func)(top_rfcp, extract_filename, argc, argv);
+}
+
+static void print_dsn_recip(char *addr, char *action)
+{
+char *p, *q;
+
+ if (!action || !addr)
+ {
+ if (action) free(action);
+ if (addr) free(addr);
+ return;
+ }
+
+ for (p=action; *p; ++p)
+ *p=tolower((int)(unsigned char)*p);
+
+ for (p=addr; *p && isspace((int)(unsigned char)*p); ++p)
+ ;
+
+ if (strncasecmp(p, "rfc822;", 7))
+ {
+ free(action);
+ free(addr);
+ return;
+ }
+ for (q=action; *q && isspace((int)(unsigned char)*q); ++q)
+ ;
+ p += 7;
+ while (*p && isspace((int)(unsigned char)*p))
+ ++p;
+ printf("%s %s\n", q, p);
+ free(action);
+ free(addr);
+}
+
+static void dsn(struct rfc2045 *p, int do_orig)
+{
+const char *content_type_s;
+const char *content_transfer_encoding_s;
+const char *charset_s;
+off_t start_pos, end_pos, start_body;
+off_t dummy;
+const char *q;
+char buf[BUFSIZ];
+unsigned i;
+int ch;
+char *recip;
+char *action;
+char *orecip;
+
+ rfc2045_mimeinfo(p, &content_type_s, &content_transfer_encoding_s,
+ &charset_s);
+ if (strcasecmp(content_type_s, "multipart/report") ||
+ (q=rfc2045_getattr(p->content_type_attr, "report-type")) == 0 ||
+ strcasecmp(q, "delivery-status") ||
+ !p->firstpart || !p->firstpart->next ||
+ !p->firstpart->next->next)
+ _exit(1);
+ p=p->firstpart->next->next;
+ rfc2045_mimeinfo(p, &content_type_s, &content_transfer_encoding_s,
+ &charset_s);
+ rfc2045_mimepos(p, &start_pos, &end_pos, &start_body, &dummy, &dummy);
+ if (strcasecmp(content_type_s, "message/delivery-status") ||
+ fseek(stdin, start_body, SEEK_SET) == -1)
+ _exit(1);
+
+ i=0;
+ recip=0;
+ orecip=0;
+ action=0;
+ while (start_body < end_pos)
+ {
+ if ((ch=getchar()) == EOF) break;
+ ++start_body;
+ if (i < sizeof(buf)-1)
+ buf[i++]= ch;
+ if (ch != '\n') continue;
+ ch=getchar();
+ if (ch != EOF) ungetc(ch, stdin);
+ if (ch != '\n' && isspace((int)(unsigned char)ch))
+ continue;
+ buf[i-1]=0;
+ if (buf[0] == 0)
+ {
+ if (orecip)
+ {
+ if (recip) free(recip);
+ recip=orecip;
+ orecip=0;
+ }
+ print_dsn_recip(recip, action);
+ recip=0;
+ action=0;
+ }
+ if (strncasecmp(buf, "Final-Recipient:", 16) == 0 &&
+ recip == 0)
+ {
+ recip=strdup(buf+16);
+ if (!recip)
+ {
+ perror("strdup");
+ exit(2);
+ }
+ }
+ if (strncasecmp(buf, "Original-Recipient:", 19) == 0 &&
+ orecip == 0 && do_orig)
+ {
+ orecip=strdup(buf+19);
+ if (!orecip)
+ {
+ perror("strdup");
+ exit(2);
+ }
+ }
+ if (strncasecmp(buf, "Action:", 7) == 0 && action == 0)
+ {
+ action=strdup(buf+7);
+ if (!action)
+ {
+ perror("strdup");
+ exit(2);
+ }
+ }
+ i=0;
+ }
+ if (orecip)
+ {
+ if (recip) free(recip);
+ recip=orecip;
+ orecip=0;
+ }
+ print_dsn_recip(recip, action);
+}
+
+static void mimedigest1(int, char **);
+static char mimebuf[BUFSIZ];
+
+static void mimedigest(int argc, char **argv)
+{
+char *p;
+struct filelist { struct filelist *next; char *fn; } *first=0, *last=0;
+unsigned pcnt=0;
+char **l;
+
+ if (argc > 0)
+ {
+ mimedigest1(argc, argv);
+ return;
+ }
+
+ while (fgets(mimebuf, sizeof(mimebuf), stdin))
+ {
+ struct filelist *q;
+
+ if ((p=strchr(mimebuf, '\n')) != 0) *p=0;
+ q=malloc(sizeof(struct filelist));
+ if (!q || !(q->fn=strdup(mimebuf)))
+ {
+ perror("malloc");
+ exit(1);
+ }
+
+ if (last) last->next=q;
+ else first=q;
+ last=q;
+ q->next=0;
+ ++pcnt;
+ }
+ if (pcnt == 0) return;
+
+ if ( (l=malloc(sizeof (char *) * pcnt)) == 0)
+ {
+ perror("malloc");
+ }
+ pcnt=0;
+
+ for (last=first; last; last=last->next)
+ l[pcnt++]=last->fn;
+
+ mimedigest1(pcnt, l);
+}
+
+static void mimedigest1(int argc, char **argv)
+{
+time_t t;
+char boundarybuf[200];
+unsigned boundarycnt=0;
+int i;
+FILE *fp;
+
+ time (&t);
+
+ /* Search for a suitable boundary */
+
+ do
+ {
+ int l;
+
+ sprintf(boundarybuf, "reformime_%lu_%u",
+ (unsigned long)t, ++boundarycnt);
+
+ l=strlen(boundarybuf);
+
+ for (i=0; i<argc; i++)
+ {
+ int err=0;
+
+ if ((fp=fopen(argv[i], "r")) == 0)
+ {
+ perror(argv[i]);
+ exit(1);
+ }
+
+ while (fgets(mimebuf, sizeof(mimebuf), fp))
+ {
+ if (mimebuf[0] != '-' || mimebuf[1] != '-')
+ continue;
+
+ if (strncasecmp(mimebuf+2, boundarybuf, l) == 0)
+ {
+ err=1;
+ break;
+ }
+ }
+ fclose(fp);
+ if (err) break;
+ }
+ } while (i < argc);
+
+ printf("Mime-Version:1.0\n"
+ "Content-Type: multipart/digest; boundary=\"%s\"\n\n%s",
+ boundarybuf, RFC2045MIMEMSG);
+
+ for (i=0; i<argc; i++)
+ {
+ if ((fp=fopen(argv[i], "r")) == 0)
+ {
+ perror(argv[i]);
+ exit(1);
+ }
+
+ printf("\n--%s\nContent-Type: message/rfc822\n\n",
+ boundarybuf);
+
+ while (fgets(mimebuf, sizeof(mimebuf), fp))
+ printf("%s", mimebuf);
+ fclose(fp);
+ }
+
+ printf("\n--%s--\n", boundarybuf);
+}
+
+static void display_decoded_header(const char *ptr, size_t cnt, void *dummy)
+{
+ if (cnt == 0)
+ putchar('\n');
+ else
+ fwrite(ptr, cnt, 1, stdout);
+}
+
+static int doconvtoutf8_stdout(const char *ptr, size_t n, void *dummy)
+{
+ if (fwrite(ptr, n, 1, stdout) != 1)
+ return -1;
+
+ return 0;
+}
+
+static int main2(const char *mimecharset, int argc, char **argv)
+{
+int argn;
+char optc;
+char *optarg;
+char *mimesection=0;
+char *section=0;
+int doinfo=0, dodecode=0, dorewrite=0, dodsn=0, domimedigest=0;
+int dodecodehdr=0, dodecodeaddrhdr=0, doencodemime=0, doencodemimehdr=0;
+
+char *decode_header="";
+struct rfc2045 *p;
+int rwmode=0;
+int convtoutf8=0;
+int dovalidate=0;
+void (*do_extract)(struct rfc2045 *, const char *, int, char **)=0;
+const char *extract_filename=0;
+int rc=0;
+
+
+ rfc2045_in_reformime=1;
+
+ for (argn=1; argn<argc; )
+ {
+ if (argv[argn][0] != '-') break;
+ optarg=0;
+ optc=argv[argn][1];
+ if (optc && argv[argn][2]) optarg=argv[argn]+2;
+ ++argn;
+ switch (optc) {
+ case 'c':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg && *optarg)
+ {
+ char *p=libmail_u_convert_tobuf("",
+ optarg,
+ libmail_u_ucs4_native,
+ NULL);
+
+ if (!p)
+ {
+ fprintf(stderr, "Unknown charset: %s\n",
+ optarg);
+ exit(1);
+ }
+ free(p);
+ mimecharset=optarg;
+ }
+ break;
+
+ case 's':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg && *optarg) section=strdup(optarg);
+ break;
+ case 'i':
+ doinfo=1;
+ break;
+ case 'e':
+ dodecode=1;
+ break;
+ case 'r':
+ dorewrite=1;
+ if (optarg && *optarg == '7')
+ rwmode=RFC2045_RW_7BIT;
+ if (optarg && *optarg == '8')
+ rwmode=RFC2045_RW_8BIT;
+ break;
+ case 'm':
+ domimedigest=1;
+ break;
+ case 'd':
+ dodsn=1;
+ break;
+ case 'D':
+ dodsn=2;
+ break;
+ case 'x':
+ do_extract=extract_file;
+ if (optarg)
+ extract_filename=optarg;
+ break;
+ case 'X':
+ do_extract=extract_pipe;
+ break;
+ case 'V':
+ dovalidate=1;
+ break;
+ case 'h':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg)
+ {
+ decode_header=optarg;
+ }
+ dodecodehdr=1;
+ break;
+ case 'H':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg)
+ {
+ decode_header=optarg;
+ }
+ dodecodeaddrhdr=1;
+ break;
+ case 'o':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg)
+ {
+ decode_header=optarg;
+ }
+ doencodemime=1;
+ break;
+ case 'O':
+ if (!optarg && argn < argc)
+ optarg=argv[argn++];
+ if (optarg)
+ {
+ decode_header=optarg;
+ }
+ doencodemimehdr=1;
+ break;
+ case 'u':
+ convtoutf8=1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ defchset=mimecharset;
+
+ rfc2045_setdefaultcharset(defchset);
+
+ if (domimedigest)
+ {
+ mimedigest(argc-argn, argv+argn);
+ return (0);
+ }
+ else if (dodecodehdr)
+ {
+ if (rfc822_display_hdrvalue("Subject",
+ decode_header,
+ mimecharset,
+ display_decoded_header,
+ NULL,
+ NULL) < 0)
+ {
+ perror("rfc822_display_hdrvalue");
+ return (1);
+ }
+
+ printf("\n");
+ return (0);
+ }
+ else if (dodecodeaddrhdr)
+ {
+ if (rfc822_display_hdrvalue("To",
+ decode_header,
+ mimecharset,
+ display_decoded_header,
+ NULL,
+ NULL) < 0)
+ {
+ perror("rfc822_display_hdrvalue");
+ return (1);
+ }
+
+ printf("\n");
+ return (0);
+ }
+
+ if (doencodemime)
+ {
+ char *s=rfc2047_encode_str(decode_header, mimecharset,
+ rfc2047_qp_allow_any);
+
+ if (s)
+ {
+ printf("%s\n", s);
+ free(s);
+ }
+ return (0);
+ }
+ if (doencodemimehdr)
+ {
+ struct rfc822t *t=rfc822t_alloc_new(decode_header, NULL, NULL);
+ struct rfc822a *a=t ? rfc822a_alloc(t):NULL;
+ char *s;
+
+ if (a && (s=rfc2047_encode_header_addr(a, mimecharset)) != NULL)
+ {
+ printf("%s\n", s);
+ free(s);
+ }
+
+ if (a) rfc822a_free(a);
+ if (t) rfc822t_free(t);
+ return (0);
+ }
+
+ p=read_message();
+
+ if (doinfo)
+ {
+ mimesection = section ? strtok(section, ","):NULL;
+ do {
+ print_info(p, mimesection);
+ if (do_extract)
+ extract_section(p, mimesection,
+ extract_filename, argc-argn,
+ argv+argn, do_extract);
+ if (mimesection)
+ mimesection = strtok(NULL,",");
+ } while (mimesection != NULL);
+ }
+ else if (dodecode)
+ {
+ mimesection = strtok(section,",");
+ do {
+ print_decode(p, mimesection);
+ mimesection = strtok(NULL,",");
+ } while (mimesection != NULL);
+ }
+ else if (dorewrite)
+ rewrite(p, rwmode);
+ else if (dodsn)
+ dsn(p, dodsn == 2);
+ else if (do_extract)
+ {
+ mimesection = strtok(section,",");
+ do {
+ extract_section(p, mimesection, extract_filename,
+ argc-argn, argv+argn, do_extract);
+ mimesection = strtok(NULL,",");
+ } while (mimesection != NULL);
+ }
+ else if (dovalidate)
+ {
+ rc=1;
+
+ if (p->rfcviolation & RFC2045_ERR2COMPLEX)
+ printf("ERROR: MIME complexity.\n");
+ else if (p->rfcviolation & RFC2045_ERRBADBOUNDARY)
+ printf("ERROR: Ambiguous MIME boundary delimiters.\n");
+ else rc=0;
+
+ }
+ else if (convtoutf8)
+ {
+ struct rfc2045src *src;
+ struct rfc2045_decodemsgtoutf8_cb cb;
+
+ memset(&cb, 0, sizeof(cb));
+
+ cb.output_func=doconvtoutf8_stdout;
+ cb.arg=NULL;
+
+ src=rfc2045src_init_fd(0);
+
+ if (src)
+ {
+ rfc2045_decodemsgtoutf8(src, p, &cb);
+ rfc2045src_deinit(src);
+ }
+ }
+ else
+ print_structure(p);
+ rfc2045_free(p);
+ exit(rc);
+ return (rc);
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ rc=main2(unicode_default_chset(), argc, argv);
+ return rc;
+}
diff --git a/rfc2045/reformime.sgml b/rfc2045/reformime.sgml
new file mode 100644
index 0000000..e7f95dd
--- /dev/null
+++ b/rfc2045/reformime.sgml
@@ -0,0 +1,463 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<!-- Copyright 1998 - 2010 Double Precision, Inc. See COPYING for -->
+<!-- distribution information. -->
+<refentry>
+ <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info>
+
+ <refmeta>
+ <refentrytitle>reformime</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class='manual'>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>reformime</refname>
+ <refpurpose>MIME E-mail reformatting tool</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis sepchar=" ">
+ <command moreinfo="none">reformime</command>
+ <arg rep="repeat" choice="opt"><replaceable>options</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+<command moreinfo="none">reformime</command> is a utility for reformatting
+MIME messages.</para>
+
+ <para>
+Generally, <command moreinfo="none">reformime</command> expects to see an
+<ulink url="http://www.rfc-editor.org/rfc/rfc2045.txt">RFC 2045</ulink>
+compliant message on standard input, except in few cases
+such as the <option>-m</option> option.</para>
+
+ <para>
+If no options are given, <command moreinfo="none">reformime</command> prints the MIME
+structure of the message. The output consists of so-called
+"MIME reference tags", one per line.
+For example:</para>
+ <blockquote>
+ <informalexample>
+<programlisting format="linespecific">
+1
+1.1
+1.2
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+<para>
+This shows that the message contains two different MIME sections. The
+first line of the MIME structure output will always contain "1", which refers
+to the entire message.
+In this case it happens to be a <literal moreinfo="none">multipart/mixed</literal>
+message. "1.1" refers to the first section of the multipart message, which
+happens to be a
+<literal moreinfo="none">text/plain</literal> section. "1.2" refers to the second
+section of the message, which happens to be an
+<literal moreinfo="none">application/octet-stream</literal> section.</para>
+
+ <para>
+If the message is not a MIME message, or it does not contain any
+attachments, <command moreinfo="none">reformime</command> prints only "1", that refers
+to the entire message itself:</para>
+
+<blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+1
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+Here's the output from
+<command moreinfo="none">reformime</command> when the first part of the message was itself a
+<literal moreinfo="none">multipart/alternative</literal> section:</para>
+<blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+1
+1.1
+1.1.1
+1.1.2
+1.2
+</programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>
+Arbitrarily complex MIME constructs are possible.</para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>-d</term>
+ <listitem>
+ <para>
+Parse a delivery status notification MIME message
+(<ulink url="http://www.rfc-editor.org/rfc/rfc1894.txt">RFC 1894</ulink>).
+<command moreinfo="none">reformime</command>
+expects to see on standard input a MIME message that consists of
+a delivery status notification, as defined by RFC 1894.
+<command moreinfo="none">reformime</command>
+reads
+the message and prints on standard output a list of addresses and their
+corresponding delivery status, as specified in the delivery status
+notification. Each line printed by
+<command moreinfo="none">reformime</command>
+consists of a delivery
+status, a space, and the address.
+<command moreinfo="none">reformime</command> then terminates with a 0 exit status.
+<command moreinfo="none">reformime</command> produces no output and terminates with an exit
+status of 1 if the standard input does not contain a delivery status
+notification.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-D</term>
+ <listitem>
+ <para>
+Like the <option>-d</option> except that
+<command moreinfo="none">reformime</command> lists the address
+found in the <literal moreinfo="none">Original-Recipient:</literal> header,
+if it exists.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-e</term>
+ <listitem>
+ <para>
+Extract the contents of the indicated MIME section, and display it
+on standard output.
+The <option>-s</option> option is required when
+<option>-e</option> is specified. If the
+specified section or
+sections use either the <literal moreinfo="none">base64</literal> or
+<literal moreinfo="none">quoted-printable</literal> encoding method,
+<command moreinfo="none">reformime</command> automatically
+decodes it. In this case you're better off redirecting the standard output
+into a file.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-i</term>
+ <listitem>
+ <para>
+Display MIME information for each section. <command moreinfo="none">reformime</command>
+displays the contents of the
+<literal moreinfo="none">Content-Type:</literal> header, any encoding used,
+and the character set.
+<command moreinfo="none">reformime</command> also displays the byte offset in the message
+where each section starts and ends (and where the
+actual contents of the section start, after skipping all the headers).</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-m</term>
+ <listitem>
+ <para>
+Create a <literal moreinfo="none">multipart/digest</literal> MIME message digest.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-r</term>
+ <listitem>
+ <para>
+Rewrite message, adding or standardizing
+<ulink url="http://www.rfc-editor.org/rfc/rfc2045.txt">RFC 2045</ulink>
+MIME headers.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-r7</term>
+ <listitem>
+ <para>
+Like <option>-r</option> but also convert <literal moreinfo="none">8bit</literal>-encoded
+MIME sections to <literal moreinfo="none">quoted-printable</literal>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-r8</term>
+ <listitem>
+ <para>
+Like <option>-r</option> but also convert
+<literal moreinfo="none">quoted-printable</literal>-encoded MIME sections to
+<literal moreinfo="none">8bit</literal>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-s <replaceable>section</replaceable></term>
+ <listitem>
+ <para>
+Display MIME information for this section only.
+<replaceable>section</replaceable> is
+a MIME specification tag. The <option>-s</option> option is required if
+<option>-e</option> is also
+specified, and is optional with <option>-i</option>.</para>
+
+ <para>
+Multiple sections may be specified by separating them with commas.
+<command>reformime</command> processes each section using the other options
+that were specified.</para>
+
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-x</term>
+ <listitem>
+ <para>
+Extract the contents of the indicated MIME section to a file.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-X</term>
+ <listitem>
+ <para>
+Pipe the contents of the indicated MIME section to a program.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <refsect2>
+ <title>Extracting RFC 2045 MIME section(s) to file(s)</title>
+
+ <para>
+The <option>-x</option> and <option>-X</option> options extract a specific
+MIME section to a file or to a pipe to an external program.
+Use the <option>-s</option> option to identify the MIME section
+to extract. If the <option>-s</option> option is not specified,
+every MIME section in the message is extracted, one at a time.
+If <option>-s</option> lists multiple sections, each section gets
+extracted separately.
+<literal moreinfo="none">quoted-printable</literal> and <literal moreinfo="none">base64</literal> encoding are
+automatically decoded.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>-x</term>
+ <listitem>
+ <para>
+Interactive extraction. <command moreinfo="none">reformime</command> prints the MIME
+content type of each section. Answer with 'y' or 'Y' to extract the MIME
+section. Specify the filename at the next prompt. <command moreinfo="none">reformime</command>
+prompts with a default filename.
+<command moreinfo="none">reformime</command> tries to choose the default
+filename based on the MIME headers, if possible. If not, the default
+filename will be <literal moreinfo="none">attachment1.dat</literal> (if the -s option is not
+specified, the next filename will be <literal moreinfo="none">attachment2.dat</literal>,
+and so on).</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-x<replaceable>PREFIX</replaceable></term>
+ <listitem>
+ <para>
+Automatic extraction. <command moreinfo="none">reformime</command> automatically
+extracts one or more MIME sections, and saves them to a file.
+The filename is formed by taking
+<replaceable>PREFIX</replaceable>, and appending the default filename to it.
+Note that there's no space between "-x" and "PREFIX". For example:</para>
+ <blockquote>
+ <informalexample>
+ <programlisting format="linespecific">
+reformime -xfiles-
+</programlisting>
+ </informalexample>
+ </blockquote>
+ <para>
+This command saves MIME sections as
+<filename moreinfo="none">files-attachment1.dat</filename>, then
+<filename moreinfo="none">files-attachment2.dat</filename>, etc.
+<command moreinfo="none">reformime</command> tries to append the filename specified in the
+MIME headers for each section, where possible.
+<command moreinfo="none">reformime</command> replaces all suspect characters with the
+underscore character.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>-X prog arg1 arg2 ...</term>
+ <listitem>
+ <para>
+The <option>-X</option> option must be the last option to
+<command moreinfo="none">reformime</command>. <command moreinfo="none">reformime</command> runs an external
+program <command moreinfo="none">prog</command>, and pipes the contents of the MIME section to
+the program. <command moreinfo="none">reformime</command> sets the environment variable
+<envar>CONTENT_TYPE</envar> to the MIME content type. The environment
+variable <envar>FILENAME</envar> gets set to the default filename of
+<command moreinfo="none">reformime</command>'s liking. If the <option>-s</option> option is
+not specified, the program runs once
+for every MIME section in the message.
+The external program, <command moreinfo="none">prog</command> must terminate with a zero
+exit status in order for <command moreinfo="none">reformime</command> to proceed to the
+next MIME section in the message (or the next section specified by
+<option>-s</option>).
+In any case, if <command moreinfo="none">prog</command> terminates with a non-zero exit
+status, <command moreinfo="none">reformime</command> terminates with the exit status of
+20 plus <command moreinfo="none">prog</command>'s exit status.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <note>
+ <para>
+<command moreinfo="none">reformime</command> extracts every MIME section in the message
+unless the <option>-s</option> option is specified.
+This includes even the <literal moreinfo="none">text/plain</literal> MIME
+content that usually precedes a binary attachment.</para>
+ </note>
+ </refsect2>
+
+ <refsect2>
+ <title>Adding RFC 2045 MIME headers</title>
+
+ <para>
+The <option>-r</option> option performs the following actions:</para>
+
+ <para>
+If there is no <literal moreinfo="none">Mime-Version:</literal>,
+<literal moreinfo="none">Content-Type:</literal>, or
+<literal moreinfo="none">Content-Transfer-Encoding:</literal> header,
+<command moreinfo="none">reformime</command> adds one.</para>
+
+ <para>
+If the <literal moreinfo="none">Content-Transfer-Encoding:</literal> header contains
+<literal moreinfo="none">8bit</literal> or <literal moreinfo="none">raw</literal>, but only seven-bit data is
+found, <command moreinfo="none">reformime</command> changes
+the <literal moreinfo="none">Content-Transfer-Encoding</literal> header to
+<literal moreinfo="none">7bit</literal>.</para>
+
+ <para><option>-r7</option> does the same thing, but also converts
+<literal moreinfo="none">8bit</literal>-encoded content that contains eight-bit characters to
+<literal moreinfo="none">quoted-printable</literal> encoding.</para>
+
+ <para><option>-r8</option> does the same thing, but also converts
+<literal moreinfo="none">quoted-printable</literal>-encoded content to
+<literal moreinfo="none">8bit</literal>, except in some situations.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Creating <literal moreinfo="none">multipart/digest</literal> MIME digests</title>
+
+ <para>The <option>-m</option> option creates a MIME digest.
+<command moreinfo="none">reformime</command> reads a list of filenames on standard input.
+Each line read from standard input contains the name of a file that is
+presumed to contain an RFC 2822-formatted message.
+<command moreinfo="none">reformime</command> splices all files into a
+<command moreinfo="none">multipart/digest</command> MIME section,
+and writes it to standard output.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Translating MIME headers</title>
+
+ <para>
+The following options do not read a message from standard input.
+These options process MIME headers via the command line, and are designed
+to be conveniently used by mail-handling scripts.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>-h "<replaceable>header</replaceable>"</term>
+ <listitem>
+ <para>
+Decode a MIME-encoded "<replaceable>header</replaceable>" and print the
+decoded 8-bit content on standard output.
+The decoding gets carried out as if the contents occurred in the
+<quote>Subject</quote> header.
+Example:</para>
+ <informalexample>
+ <programlisting format="linespecific">
+$ reformime -h '=?iso-8859-1?Q?H=F3la!?='
+H&#243;la!
+</programlisting>
+ </informalexample>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-H "<replaceable>header</replaceable>"</term>
+ <listitem>
+ <para>
+Like <option>-h</option> except that <replaceable>header</replaceable> is
+parsed as a list of
+email addresses, like <quote>From</quote> or <quote>To</quote>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-o "<replaceable>text</replaceable>"</term>
+ <listitem>
+ <para>
+MIME-encode "<replaceable>text</replaceable>", and print the results
+on standard output.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-O "<replaceable>text</replaceable>"</term>
+ <listitem>
+ <para>
+Like the <option>-o option</option>, except that
+<replaceable>text</replaceable>
+is a structured header with RFC 2822 addresses.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-c "<replaceable>charset</replaceable>"</term>
+ <listitem>
+ <para>
+ Use <replaceable>charset</replaceable> as the character set
+ setting, by the
+ <option>-h</option>,
+ <option>-H</option>,
+ <option>-o</option> and
+ <option>-O</option> options.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-u</term>
+
+ <listitem>
+ <para>
+ This <quote>undocumented</quote> option reads a MIME message on
+ standard input, and converts its contents to an UTF-8-encoded
+ character stream, which is written to standard output.
+ </para>
+
+ <para>
+ The standard output receives a concatenated amalgam of the
+ headers and <quote>text</quote> MIME object data. It is meant to
+ be used as part of a generic search function. This option
+ decodes various kinds of header MIME encoding, the
+ <literal>quoted-printable</literal> and <literal>base64</literal>
+ transfer encodings of <quote>text</quote> MIME objects.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="reformail.html"><citerefentry><refentrytitle>reformail</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<citerefentry><refentrytitle>sendmail</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+<ulink url="mailbot.html"><citerefentry><refentrytitle>mailbot</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<ulink url="maildrop.html"><citerefentry><refentrytitle>maildrop</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<ulink url="maildropfilter.html"><citerefentry><refentrytitle>maildropfilter</refentrytitle><manvolnum>5</manvolnum></citerefentry></ulink>,
+<citerefentry><refentrytitle>egrep</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+<citerefentry><refentrytitle>grep</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+<citerefentry><refentrytitle>sendmail</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ </refsect1>
+</refentry>
diff --git a/rfc2045/rfc2045.c b/rfc2045/rfc2045.c
new file mode 100644
index 0000000..2245f43
--- /dev/null
+++ b/rfc2045/rfc2045.c
@@ -0,0 +1,1375 @@
+/*
+** Copyright 1998 - 2004 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <ctype.h>
+#include "rfc2045.h"
+#include "rfc822/rfc822.h"
+#include "rfc2045charset.h"
+
+static char *rfc2045_defcharset=0;
+
+int rfc2045_in_reformime=0;
+
+extern void rfc2045_enomem();
+
+#define MAXLEVELS 20
+#define MAXPARTS 300
+
+/*
+ New RFC2045 structure.
+*/
+
+struct rfc2045 *rfc2045_alloc()
+{
+struct rfc2045 *p=(struct rfc2045 *)malloc(sizeof(struct rfc2045));
+
+ if (!p)
+ {
+ rfc2045_enomem();
+ return (0);
+ }
+
+ /* Initialize everything to nulls, except for one thing */
+
+ memset(p, '\0', sizeof(*p));
+
+ p->pindex=1; /* Start with part #1 */
+ p->workinheader=1;
+ /* Most of the time, we're about to read a header */
+
+ return (p);
+}
+
+const char *rfc2045_getattr(const struct rfc2045attr *p, const char *name)
+{
+ while (p)
+ {
+ if (p->name && strcmp(p->name, name) == 0)
+ return (p->value);
+ p=p->next;
+ }
+ return (0);
+}
+
+int rfc2045_attrset(struct rfc2045attr **p, const char *name, const char *val)
+{
+char *v;
+
+ while (*p)
+ {
+ if (strcmp( (*p)->name, name) == 0) break;
+ p=&(*p)->next;
+ }
+ if (val == 0)
+ {
+ struct rfc2045attr *q= *p;
+
+ if (q)
+ {
+ *p=q->next;
+ if (q->name) free(q->name);
+ if (q->value) free(q->value);
+ free(q);
+ }
+ return 0;
+ }
+
+ v=strdup(val);
+ if (!v)
+ return -1;
+
+ if (!*p)
+ {
+ if (((*p)=(struct rfc2045attr *)malloc(sizeof(**p))) == 0)
+ {
+ free(v);
+ return -1;
+ }
+ memset( (*p), 0, sizeof(**p));
+ if ( ((*p)->name=strdup(name)) == 0)
+ {
+ free( *p );
+ *p=0;
+ free(v);
+ return -1;
+ }
+ }
+ if ( (*p)->value ) free ( (*p)->value );
+ (*p)->value=v;
+ return 0;
+}
+
+/* static const char cb_name[]="boundary"; */
+
+/* #define ContentBoundary(p) (rfc2045_getattr( (p)->content_type_attr, cb_name)) */
+
+#define ContentBoundary(p) ( (p)->boundary )
+
+/*
+ Unallocate the RFC2045 structure. Recursively unallocate
+ all sub-structures. Unallocate all associated buffers.
+*/
+
+static void rfc2045_freeattr(struct rfc2045attr *p)
+{
+ while (p)
+ {
+ struct rfc2045attr *q=p->next;
+
+ if (p->name) free(p->name);
+ if (p->value) free(p->value);
+ free(p);
+ p=q;
+ }
+}
+
+void rfc2045_free(struct rfc2045 *p)
+{
+struct rfc2045 *q, *r;
+
+ for (q=p->firstpart; q; )
+ {
+ r=q->next;
+ rfc2045_free(q);
+ q=r;
+ }
+ rfc2045_freeattr(p->content_type_attr);
+ rfc2045_freeattr(p->content_disposition_attr);
+
+ if (p->header) free(p->header);
+ if (p->content_md5) free(p->content_md5);
+ if (p->content_base) free(p->content_base);
+ if (p->content_location) free(p->content_location);
+ if (p->content_language) free(p->content_language);
+ if (p->content_id) free(p->content_id);
+ if (p->content_description) free(p->content_description);
+ if (p->content_transfer_encoding) free(p->content_transfer_encoding);
+ if (p->boundary) free(p->boundary);
+ if (p->content_type) free(p->content_type);
+ if (p->mime_version) free(p->mime_version);
+ if (p->workbuf) free(p->workbuf);
+ if (p->content_disposition) free(p->content_disposition);
+ if (p->rw_transfer_encoding) free(p->rw_transfer_encoding);
+ free(p);
+}
+
+/*
+ Generic dynamic buffer append.
+*/
+
+void rfc2045_add_buf(
+ char **bufptr, /* Buffer */
+ size_t *bufsize, /* Buffer's maximum size */
+ size_t *buflen, /* Buffer's current size */
+
+ const char *p, size_t len) /* Append this data */
+{
+ if (len + *buflen > *bufsize)
+ {
+ size_t newsize=len+*buflen+256;
+ char *p= *bufptr ? (char *)realloc(*bufptr, newsize):
+ (char *)malloc(newsize);
+
+ if (!p)
+ {
+ rfc2045_enomem();
+ return;
+ }
+ *bufptr=p;
+ *bufsize=newsize;
+ }
+
+ memcpy(*bufptr + *buflen, p, len);
+ *buflen += len;
+}
+
+/* Append to the work buffer */
+
+void rfc2045_add_workbuf(struct rfc2045 *h, const char *p, size_t len)
+{
+ rfc2045_add_buf( &h->workbuf, &h->workbufsize, &h->workbuflen, p, len);
+}
+
+/* Append one character to the work buffer */
+
+void rfc2045_add_workbufch(struct rfc2045 *h, int c)
+{
+char cc= (char)c;
+
+ rfc2045_add_workbuf(h, &cc, 1);
+}
+
+/*
+ Generic function to duplicate contents of a string.
+ The destination string may already be previously allocated,
+ so unallocate it.
+*/
+
+static void set_string(char **p,
+ const char *q)
+{
+ if (*p) free(*p);
+
+ *p=0;
+ if (!q) return;
+
+ if ((*p=(char *)malloc(strlen(q)+1)) == 0)
+ {
+ rfc2045_enomem();
+ return;
+ }
+
+ strcpy(*p, q);
+}
+
+/* Update byte counts for this structure, and all the superstructures */
+
+static void update_counts(struct rfc2045 *p, size_t newcnt, size_t newendcnt,
+ unsigned nlines)
+{
+ while (p)
+ {
+ p->endpos = newcnt;
+ p->endbody = newendcnt;
+ p->nlines += nlines;
+ if (!p->workinheader)
+ p->nbodylines += nlines;
+ p=p->parent;
+ }
+}
+
+/*
+ Main entry point for RFC2045 parsing. External data is fed
+ by repetitively calling rfc2045_parse().
+
+ rfc2045_parse() breaks up input into lines, and calls doline()
+ to process each line.
+*/
+
+static void doline(struct rfc2045 *);
+
+void rfc2045_parse_partial(struct rfc2045 *h);
+
+void rfc2045_parse(struct rfc2045 *h, const char *buf, size_t s)
+{
+ size_t l;
+
+ while (s)
+ {
+ for (l=0; l<s; l++)
+ if (buf[l] == '\n') break;
+ if (l < s && buf[l] == '\n')
+ {
+ ++l;
+ rfc2045_add_workbuf(h, buf, l);
+ doline(h);
+ h->workbuflen=0;
+ }
+ else
+ rfc2045_add_workbuf(h, buf, l);
+ buf += l;
+ s -= l;
+ }
+
+ if (h->workbuflen > 1024)
+ rfc2045_parse_partial(h);
+}
+
+void rfc2045_parse_partial(struct rfc2045 *h)
+{
+ /*
+ ** Our buffer's getting pretty big. Let's see if we can
+ ** partially handle it.
+ */
+
+ if (h->workbuflen > 0)
+ {
+ struct rfc2045 *p;
+ int l, i;
+
+ for (p=h; p->lastpart && !p->lastpart->workclosed;
+ p=p->lastpart)
+ ;
+
+ /* If p->workinheader, we've got a mother of all headers
+ ** here. Well, that's just too bad, we'll end up garbling
+ ** it.
+ */
+
+ l=h->workbuflen;
+
+ /* We do need to make sure that the final \r\n gets
+ ** stripped off, so don't gobble up everything if
+ ** the last character we see is a \r
+ */
+
+ if (h->workbuf[l-1] == '\r')
+ --l;
+
+ /* If we'll be rewriting, make sure rwprep knows about
+ ** stuff that was skipped just now. */
+
+ if (h->rfc2045acptr && !p->workinheader &&
+ (!p->lastpart || !p->lastpart->workclosed))
+ (*h->rfc2045acptr->section_contents)(h->workbuf, l);
+
+ update_counts(p, p->endpos+l, p->endpos+l, 0);
+ p->informdata=1;
+ for (i=0; l<h->workbuflen; l++)
+ h->workbuf[i++]=h->workbuf[l];
+ h->workbuflen=i;
+ }
+}
+
+/*
+ Append a new RFC2045 subpart. Adds new RFC2045 structure to the
+ end of the list of existing RFC2045 substructures.
+*/
+
+static struct rfc2045 *append_part_noinherit(struct rfc2045 *p, size_t startpos){
+struct rfc2045 *newp;
+
+ newp=rfc2045_alloc();
+ if (p->lastpart)
+ {
+ p->lastpart->next=newp;
+ newp->pindex=p->lastpart->pindex+1;
+ }
+ else
+ {
+ p->firstpart=newp;
+ newp->pindex=0;
+ }
+ p->lastpart=newp;
+ newp->parent=p;
+
+ /* Initialize source pointers */
+ newp->startpos=newp->endpos=newp->startbody=newp->endbody=startpos;
+
+ while (p->parent)
+ p=p->parent;
+ ++p->numparts;
+
+ return (newp);
+}
+
+static struct rfc2045 *append_part(struct rfc2045 *p, size_t startpos)
+{
+struct rfc2045 *newp=append_part_noinherit(p, startpos);
+
+ /* Substructures inherit content transfer encoding and character set */
+
+ set_string(&newp->content_transfer_encoding,
+ p->content_transfer_encoding);
+
+ if (rfc2045_attrset(&newp->content_type_attr, "charset",
+ rfc2045_getattr(p->content_type_attr, "charset"))
+ < 0)
+ rfc2045_enomem();
+
+ return (newp);
+}
+
+/*
+ doline() processes next line in the RFC2045 message.
+
+ Drills down the list of all the multipart messages currently open,
+ and checks if the line is a boundary line for the given multipart.
+ In theory the boundary line, if there is one, should be the boundary
+ line only for the inner multipart only, but, this takes into account
+ broken MIME messages.
+*/
+
+static void do_header(struct rfc2045 *);
+
+static void doline(struct rfc2045 *p)
+{
+size_t cnt=p->workbuflen;
+char *c=p->workbuf;
+size_t n=cnt-1; /* Strip \n (we always get at least a \n here) */
+struct rfc2045 *newp;
+struct rfc2045ac *rwp=p->rfc2045acptr;
+unsigned num_levels=0;
+
+size_t k;
+int bit8=0;
+
+ if (p->numparts > MAXPARTS)
+ {
+ p->rfcviolation |= RFC2045_ERR2COMPLEX;
+ return;
+ }
+
+ for (k=0; k<cnt; k++)
+ {
+ if (c[k] == 0)
+ c[k]=' ';
+ if (c[k] & 0x80) bit8=1;
+ }
+
+ if (n && c[n-1] == '\r') /* Strip trailing \r */
+ --n;
+
+ /* Before the main drill down loop before, look ahead and see if we're
+ ** in a middle of a form-data section. */
+
+ for (newp=p; newp->lastpart &&
+ !newp->lastpart->workclosed; newp=newp->lastpart,
+ ++num_levels)
+ {
+ if (ContentBoundary(newp) == 0 || newp->workinheader)
+ continue;
+
+ if (newp->lastpart->informdata)
+ {
+ p=newp->lastpart;
+ p->informdata=0;
+ break;
+ }
+ }
+
+ /* Drill down until we match a boundary, or until we've reached
+ the last RFC2045 section that has been opened.
+ */
+
+ while (p->lastpart)
+ {
+ size_t l;
+ const char *cb;
+
+ if (p->lastpart->workclosed)
+ {
+ update_counts(p, p->endpos+cnt, p->endpos+n, 1);
+ return;
+ }
+ /* Leftover trash -- workclosed is set when the final
+ ** terminating boundary has been seen */
+
+ /* content_boundary may be set before the entire header
+ ** has been seen, so continue drilling down in that case
+ */
+
+ cb=ContentBoundary(p);
+
+ if (cb == 0 || p->workinheader)
+ {
+ p=p->lastpart;
+ ++num_levels;
+ continue;
+ }
+
+ l=strlen(cb);
+
+ if (c[0] == '-' && c[1] == '-' && n >= 2+l &&
+ strncasecmp(cb, c+2, l) == 0)
+ {
+
+ if (rwp && (!p->lastpart || !p->lastpart->isdummy))
+ (*rwp->end_section)();
+
+ /* Ok, we've found a boundary */
+
+ if (n >= 4+l && strncmp(c+2+l, "--", 2) == 0)
+ {
+ /* Last boundary */
+
+ p->lastpart->workclosed=1;
+ update_counts(p, p->endpos+cnt, p->endpos+cnt,
+ 1);
+ return;
+ }
+
+ /* Create new RFC2045 section */
+
+ newp=append_part(p, p->endpos+cnt);
+ update_counts(p, p->endpos+cnt, p->endpos+n, 1);
+
+ /* The new RFC2045 section is MIME compliant */
+
+ if ((newp->mime_version=strdup(p->mime_version)) == 0)
+ rfc2045_enomem();
+ return;
+ }
+ p=p->lastpart;
+ ++num_levels;
+ }
+
+ /* Ok, we've found the RFC2045 section that we're working with.
+ ** No what?
+ */
+
+ if (! p->workinheader)
+ {
+ /* Processing body, just update the counts. */
+
+ size_t cnt_update=cnt;
+
+ if (bit8 && !p->content_8bit &&
+ (p->rfcviolation & RFC2045_ERR8BITCONTENT) == 0)
+ {
+ struct rfc2045 *q;
+
+ for (q=p; q; q=q->parent)
+ q->rfcviolation |= RFC2045_ERR8BITCONTENT;
+ }
+
+ /*
+ ** In multiparts, the final newline in a part belongs to the
+ ** boundary, otherwise, include it in the text.
+ */
+ if (p->parent && p->parent->content_type &&
+ strncasecmp(p->parent->content_type,
+ "multipart/", 10) == 0)
+ cnt_update=n;
+
+ if (!p->lastpart || !p->lastpart->workclosed)
+ {
+ if (rwp && !p->isdummy)
+ (*rwp->section_contents)(c, cnt);
+
+ update_counts(p, p->endpos+cnt, p->endpos+cnt_update,
+ 1);
+ }
+ return;
+ }
+
+ if (bit8 && (p->rfcviolation & RFC2045_ERR8BITHEADER) == 0)
+ {
+ struct rfc2045 *q;
+
+ for (q=p; q; q=q->parent)
+ q->rfcviolation |= RFC2045_ERR8BITHEADER;
+ }
+
+ /* In the header */
+
+ if ( n == 0 ) /* End of header, body begins. Parse header. */
+ {
+ do_header(p); /* Clean up any left over header line */
+ p->workinheader=0;
+
+ /* Message body starts right here */
+
+ p->startbody=p->endpos+cnt;
+ update_counts(p, p->startbody, p->startbody, 1);
+ --p->nbodylines; /* Don't count the blank line */
+
+ /* Discard content type and boundary if I don't understand
+ ** this MIME flavor.
+ */
+
+ if (!RFC2045_ISMIME1(p->mime_version))
+ {
+ set_string(&p->content_type, 0);
+
+ rfc2045_freeattr(p->content_type_attr);
+ p->content_type_attr=0;
+ set_string(&p->content_disposition, 0);
+ rfc2045_freeattr(p->content_disposition_attr);
+ p->content_disposition_attr=0;
+ if (p->boundary)
+ {
+ free(p->boundary);
+ p->boundary=0;
+ }
+ }
+
+ /* Normally, if we don't have a content_type, default it
+ ** to text/plain. However, if the multipart type is
+ ** multipart/digest, it is message/rfc822.
+ */
+
+ if (RFC2045_ISMIME1(p->mime_version) && !p->content_type)
+ {
+ char *q="text/plain";
+
+ if (p->parent && p->parent->content_type &&
+ strcmp(p->parent->content_type,
+ "multipart/digest") == 0)
+ q="message/rfc822";
+ set_string(&p->content_type, q);
+ }
+
+ /* If this is not a multipart section, we don't want to
+ ** hear about any boundaries
+ */
+
+ if (!p->content_type ||
+ strncmp(p->content_type, "multipart/", 10))
+ {
+ if (p->boundary)
+ free(p->boundary);
+ p->boundary=0;
+ }
+
+ /* If this section's a message, we will expect to see
+ ** more RFC2045 stuff, so create a nested RFC2045 structure,
+ ** and indicate that we expect to see headers.
+ */
+
+ if (p->content_type &&
+ strcmp(p->content_type, "message/rfc822") == 0)
+ {
+ newp=append_part_noinherit(p, p->startbody);
+ newp->workinheader=1;
+ return;
+ }
+
+ /*
+ ** If this is a multipart message (boundary defined),
+ ** create a RFC2045 structure for the pseudo-section
+ ** that precedes the first boundary line.
+ */
+
+ if (ContentBoundary(p))
+ {
+ newp=append_part(p, p->startbody);
+ newp->workinheader=0;
+ newp->isdummy=1;
+ /* It's easier just to create it. */
+ return;
+ }
+
+ if (rwp)
+ (*rwp->start_section)(p);
+ return;
+ }
+
+ /* RFC822 header continues */
+
+ update_counts(p, p->endpos + cnt, p->endpos+n, 1);
+
+ /* If this header line starts with a space, append one space
+ ** to the saved contents of the previous line, and append this
+ ** line to it.
+ */
+
+ if (isspace((int)(unsigned char)*c))
+ {
+ rfc2045_add_buf(&p->header, &p->headersize, &p->headerlen, " ", 1);
+ }
+ else
+ {
+ /* Otherwise the previous header line is complete, so process it */
+
+ do_header(p);
+ p->headerlen=0;
+ }
+
+ /* Save this line in the header buffer, because the next line
+ ** could be a continuation.
+ */
+
+ rfc2045_add_buf( &p->header, &p->headersize, &p->headerlen, c, n);
+}
+
+/***********************************************************************/
+
+/*
+** paste_tokens() - recombine an array of RFC822 tokens back as a string.
+** (Comments) are ignored.
+*/
+
+static char *paste_tokens(struct rfc822t *h, int start, int cnt)
+{
+int l;
+int i;
+char *p;
+
+ /* Calculate string size */
+
+ l=1;
+ for (i=0; i<cnt; i++)
+ {
+ if (h->tokens[start+i].token == '(')
+ continue;
+
+ if (rfc822_is_atom(h->tokens[start+i].token))
+ l += h->tokens[start+i].len;
+ else
+ l++;
+ }
+
+ /* Do it */
+
+ p=( char *)malloc(l);
+ if (!p)
+ {
+ rfc2045_enomem();
+ return (0);
+ }
+ l=0;
+
+ for (i=0; i<cnt; i++)
+ {
+ if (h->tokens[start+i].token == '(')
+ continue;
+
+ if (rfc822_is_atom(h->tokens[start+i].token))
+ {
+ int l2=h->tokens[start+i].len;
+
+ memcpy(p+l, h->tokens[start+i].ptr, l2);
+ l += l2;
+ }
+ else p[l++]=h->tokens[start+i].token;
+ }
+ p[l]=0;
+ return (p);
+}
+
+/* Various permutations of the above, including forcing the string to
+** lowercase
+*/
+
+static char *lower_paste_tokens(struct rfc822t *h, int start, int cnt)
+{
+char *p=paste_tokens(h, start, cnt);
+char *q;
+
+ for (q=p; q && *q; q++)
+ *q=tolower(*q);
+ return (p);
+}
+
+static char *paste_token(struct rfc822t *h, int i)
+{
+ if (i >= h->ntokens) return (0);
+ return (paste_tokens(h, i, 1));
+}
+
+static char *lower_paste_token(struct rfc822t *h, int i)
+{
+char *p=paste_token(h, i);
+char *q;
+
+ for (q=p; q && *q; q++)
+ *q=tolower(*q);
+ return (p);
+}
+
+/*
+ do_header() - process completed RFC822 header.
+*/
+
+static void mime_version(struct rfc2045 *, struct rfc822t *);
+static void content_type(struct rfc2045 *, struct rfc822t *);
+static void content_transfer_encoding(struct rfc2045 *, struct rfc822t *);
+static void content_disposition(struct rfc2045 *, struct rfc822t *);
+static void content_id(struct rfc2045 *, struct rfc822t *);
+static void content_description(struct rfc2045 *, const char *);
+static void content_language(struct rfc2045 *, const char *);
+static void content_md5(struct rfc2045 *, const char *);
+static void content_base(struct rfc2045 *, struct rfc822t *);
+static void content_location(struct rfc2045 *, struct rfc822t *);
+
+static void do_header(struct rfc2045 *p)
+{
+struct rfc822t *header;
+char *t;
+
+ if (p->headerlen == 0) return;
+ rfc2045_add_buf( &p->header, &p->headersize, &p->headerlen, "", 1);
+ /* 0 terminate */
+
+ /* Parse the header line according to RFC822 */
+
+ header=rfc822t_alloc_new(p->header, NULL, NULL);
+
+ if (!header) return; /* Broken header */
+
+ if (header->ntokens < 2 ||
+ header->tokens[0].token ||
+ header->tokens[1].token != ':')
+ {
+ rfc822t_free(header);
+ return; /* Broken header */
+ }
+
+ t=lower_paste_token(header, 0);
+
+ if (t == 0)
+ ;
+ else if (strcmp(t, "mime-version") == 0)
+ {
+ free(t);
+ mime_version(p, header);
+ }
+ else if (strcmp(t, "content-type") == 0)
+ {
+ free(t);
+ content_type(p, header);
+ } else if (strcmp(t, "content-transfer-encoding") == 0)
+ {
+ free(t);
+ content_transfer_encoding(p, header);
+ } else if (strcmp(t, "content-disposition") == 0)
+ {
+ free(t);
+ content_disposition(p, header);
+ } else if (strcmp(t, "content-id") == 0)
+ {
+ free(t);
+ content_id(p, header);
+ } else if (strcmp(t, "content-description") == 0)
+ {
+ free(t);
+ t=strchr(p->header, ':');
+ if (t) ++t;
+ while (t && isspace((int)(unsigned char)*t))
+ ++t;
+ content_description(p, t);
+ } else if (strcmp(t, "content-language") == 0)
+ {
+ free(t);
+ t=strchr(p->header, ':');
+ if (t) ++t;
+ while (t && isspace((int)(unsigned char)*t))
+ ++t;
+ content_language(p, t);
+ } else if (strcmp(t, "content-base") == 0)
+ {
+ free(t);
+ content_base(p, header);
+ } else if (strcmp(t, "content-location") == 0)
+ {
+ free(t);
+ content_location(p, header);
+ } else if (strcmp(t, "content-md5") == 0)
+ {
+ free(t);
+ t=strchr(p->header, ':');
+ if (t) ++t;
+ while (t && isspace((int)(unsigned char)*t))
+ ++t;
+ content_md5(p, t);
+ }
+ else free(t);
+ rfc822t_free(header);
+}
+
+/* Mime-Version: and Content-Transfer-Encoding: headers are easy */
+
+static void mime_version(struct rfc2045 *p, struct rfc822t *header)
+{
+char *vers=paste_tokens(header, 2, header->ntokens-2);
+
+ if (!vers) return;
+
+ if (p->mime_version) free(p->mime_version);
+ p->mime_version=vers;
+}
+
+static void content_transfer_encoding(struct rfc2045 *r,
+ struct rfc822t *header)
+{
+char *p;
+
+ p=lower_paste_tokens(header, 2, header->ntokens-2);
+ if (!p) return;
+
+ if (r->content_transfer_encoding)
+ free(r->content_transfer_encoding);
+ r->content_transfer_encoding=p;
+
+ if (strcmp(p, "8bit") == 0)
+ r->content_8bit=1;
+}
+
+/* Dig into the content_type header */
+
+static void parse_content_header(struct rfc822t *header,
+ int init_start,
+ void (*init_token)(char *, void *),
+ void (*init_parameter)(const char *,
+ struct rfc822t *,
+ int, int,
+ void *),
+ void *void_arg)
+{
+int start;
+int i, j;
+char *p;
+
+ /* Look for the 1st ; */
+
+ for (start=init_start; start < header->ntokens; start++)
+ if (header->tokens[start].token == ';')
+ break;
+
+ /* Everything up to the 1st ; is the content type */
+
+ p=lower_paste_tokens(header, init_start, start-init_start);
+ if (!p) return;
+
+ (*init_token)(p, void_arg);
+ if (start < header->ntokens) start++;
+
+ /* Handle the remainder of the Content-Type: header */
+
+ while (start < header->ntokens)
+ {
+ /* Look for next ; */
+
+ for (i=start; i<header->ntokens; i++)
+ if (header->tokens[i].token == ';')
+ break;
+ j=start;
+ if (j < i)
+ {
+ ++j;
+
+ /* We only understand <atom>= */
+
+ while (j < i && header->tokens[j].token == '(')
+ ++j;
+ if (j < i && header->tokens[j].token == '=')
+ {
+ ++j;
+
+ /*
+ ** reformime: loose parsing due to loose
+ ** parsing in MSOE, leading to viruses slipping
+ ** through virus scanners if we strictly
+ ** parsed the content-type header.
+ */
+ if (rfc2045_in_reformime && j < i
+ && header->tokens[j].token == '"')
+ i=j+1;
+
+ p=lower_paste_token(header, start);
+ if (!p) return;
+ (*init_parameter)(p, header, j, i-j, void_arg);
+ free(p);
+ }
+ }
+ if ( i<header->ntokens ) ++i; /* Skip over ; */
+ start=i;
+ }
+}
+
+/* Dig into the content_type header */
+
+static void save_content_type(char *, void *);
+static void save_content_type_parameter( const char *,
+ struct rfc822t *, int, int, void *);
+
+static void content_type(struct rfc2045 *r, struct rfc822t *header)
+{
+ parse_content_header(header, 2, &save_content_type,
+ &save_content_type_parameter, r);
+}
+
+static void save_content_type(char *content_type, void *void_arg)
+{
+ struct rfc2045 *r=(struct rfc2045 *)void_arg;
+
+ if (r->content_type) free(r->content_type);
+ r->content_type=content_type;
+}
+
+static void save_content_type_parameter(const char *name,
+ struct rfc822t *header, int start,
+ int len, void *void_arg)
+{
+ struct rfc2045 *r=(struct rfc2045 *)void_arg;
+ char *p;
+
+ p=strcmp(name, "charset") == 0 ?
+ lower_paste_tokens(header, start, len):
+ paste_tokens(header, start, len);
+ if (!p) return;
+
+ if (rfc2045_attrset(&r->content_type_attr, name, p) < 0)
+ {
+ free(p);
+ rfc2045_enomem();
+ }
+
+ free(p);
+
+ if (strcmp(name, "boundary") == 0)
+ {
+ struct rfc2045 *q;
+
+ if (r->boundary)
+ free(r->boundary);
+ p=lower_paste_tokens(header, start, len);
+ r->boundary=p;
+
+ /*
+ ** Check all the outer MIME boundaries. If this is a
+ ** substring of an outer MIME boundary, or the outer
+ ** boundary is a substring of the inner boundary, we
+ ** have an ambiguity - see "IMPLEMENTOR'S NOTE" in
+ ** section 5.1.1 of RFC 2046.
+ */
+
+ for (q=r->parent; q; q=q->parent)
+ {
+ const char *a, *b;
+
+ if (!q->boundary)
+ continue;
+
+ for (a=q->boundary, b=p; *a && *b; a++, b++)
+ if (*a != *b)
+ break;
+
+ if (!*a || !*b)
+ {
+ while (q->parent)
+ q=q->parent;
+ q->rfcviolation |= RFC2045_ERRBADBOUNDARY;
+ break;
+ }
+ }
+ }
+}
+
+/* Dig into content-disposition */
+
+static void save_content_disposition(char *, void *);
+static void save_content_disposition_parameter( const char *,
+ struct rfc822t *, int, int,
+ void *);
+
+static void content_disposition(struct rfc2045 *r, struct rfc822t *header)
+{
+ parse_content_header(header, 2, &save_content_disposition,
+ &save_content_disposition_parameter, r);
+}
+
+static void save_content_disposition(char *content_disposition, void *void_arg)
+{
+ struct rfc2045 *r=(struct rfc2045 *)void_arg;
+
+ if (r->content_disposition) free(r->content_disposition);
+ r->content_disposition=content_disposition;
+}
+
+static void save_content_disposition_parameter(const char *name,
+ struct rfc822t *header,
+ int start, int len,
+ void *void_arg)
+{
+ struct rfc2045 *r=(struct rfc2045 *)void_arg;
+ char *p;
+
+ p=paste_tokens(header, start, len);
+ if (!p) return;
+
+ if (rfc2045_attrset(&r->content_disposition_attr, name, p) < 0)
+ {
+ free(p);
+ rfc2045_enomem();
+ }
+ free(p);
+}
+
+char *rfc2045_related_start(const struct rfc2045 *p)
+{
+const char *cb=rfc2045_getattr( p->content_type_attr, "start");
+struct rfc822t *t;
+struct rfc822a *a;
+int i;
+
+ if (!cb || !*cb) return (0);
+
+ t=rfc822t_alloc_new(cb, 0, NULL);
+ if (!t)
+ {
+ rfc2045_enomem();
+ return(0);
+ }
+
+ a=rfc822a_alloc(t);
+ if (!a)
+ {
+ rfc822t_free(t);
+ rfc2045_enomem();
+ return (0);
+ }
+ for (i=0; i<a->naddrs; i++)
+ if (a->addrs[i].tokens)
+ {
+ char *s=rfc822_getaddr(a, i);
+
+ rfc822a_free(a);
+ rfc822t_free(t);
+ if (!s)
+ rfc2045_enomem();
+ return (s);
+ }
+
+ rfc822a_free(a);
+ rfc822t_free(t);
+ return (0);
+}
+
+static void content_id(struct rfc2045 *p, struct rfc822t *t)
+{
+struct rfc822a *a=rfc822a_alloc(t);
+int i;
+
+ if (!a)
+ {
+ rfc2045_enomem();
+ return;
+ }
+
+ for (i=0; i<a->naddrs; i++)
+ if (a->addrs[i].tokens)
+ {
+ char *s=rfc822_getaddr(a, i);
+
+ if (!s)
+ {
+ rfc822a_free(a);
+ rfc2045_enomem();
+ return;
+ }
+ if (p->content_id)
+ free(p->content_id);
+ p->content_id=s;
+ break;
+ }
+
+ rfc822a_free(a);
+}
+
+static void content_description(struct rfc2045 *p, const char *s)
+{
+ if (s && *s)
+ set_string(&p->content_description, s);
+}
+
+static void content_language(struct rfc2045 *p, const char *s)
+{
+ if (s && *s)
+ set_string(&p->content_language, s);
+}
+
+static void content_md5(struct rfc2045 *p, const char *s)
+{
+ if (s && *s)
+ set_string(&p->content_md5, s);
+}
+
+static void content_base(struct rfc2045 *p, struct rfc822t *t)
+{
+char *s;
+int i;
+
+ for (i=0; i<t->ntokens; i++)
+ if (t->tokens[i].token == '"')
+ t->tokens[i].token=0;
+
+ s=paste_tokens(t, 2, t->ntokens-2);
+ set_string(&p->content_base, s);
+}
+
+static void content_location(struct rfc2045 *p, struct rfc822t *t)
+{
+char *s;
+int i;
+
+ for (i=0; i<t->ntokens; i++)
+ if (t->tokens[i].token == '"')
+ t->tokens[i].token=0;
+
+ s=paste_tokens(t, 2, t->ntokens-2);
+ set_string(&p->content_location, s);
+ free(s);
+}
+
+/* -------------------- */
+
+#define GETINFO(s, def) ( (s) && (*s) ? (s):def)
+
+void rfc2045_mimeinfo(const struct rfc2045 *p,
+ const char **content_type_s,
+ const char **content_transfer_encoding_s,
+ const char **charset_s)
+{
+const char *c;
+
+ *content_type_s=GETINFO(p->content_type, "text/plain");
+ *content_transfer_encoding_s=GETINFO(p->content_transfer_encoding,
+ "8bit");
+
+ c=rfc2045_getattr(p->content_type_attr, "charset");
+ if (!c) c=rfc2045_getdefaultcharset();
+
+ *charset_s=c;
+}
+
+const char *rfc2045_getdefaultcharset()
+{
+const char *p=rfc2045_defcharset;
+
+ if (!p) p=RFC2045CHARSET;
+ return (p);
+}
+
+void rfc2045_setdefaultcharset(const char *charset)
+{
+char *p=strdup(charset);
+
+ if (!p)
+ {
+ rfc2045_enomem();
+ return;
+ }
+
+ if (rfc2045_defcharset) free(rfc2045_defcharset);
+ rfc2045_defcharset=p;
+}
+
+const char *rfc2045_boundary(const struct rfc2045 *p)
+{
+const char *cb=rfc2045_getattr( p->content_type_attr, "boundary");
+
+ if (!cb) cb="";
+ return (cb);
+}
+
+int rfc2045_isflowed(const struct rfc2045 *p)
+{
+ const char *cb=rfc2045_getattr(p->content_type_attr, "format");
+
+ return (cb && strcmp(cb, "flowed") == 0);
+}
+
+int rfc2045_isdelsp(const struct rfc2045 *p)
+{
+ const char *cb=rfc2045_getattr(p->content_type_attr, "delsp");
+
+ return (cb && strcmp(cb, "yes") == 0);
+}
+
+const char *rfc2045_content_id(const struct rfc2045 *p)
+{
+ return (p->content_id ? p->content_id:"");
+}
+
+const char *rfc2045_content_description(const struct rfc2045 *p)
+{
+ return (p->content_description ? p->content_description:"");
+}
+
+const char *rfc2045_content_language(const struct rfc2045 *p)
+{
+ return (p->content_language ? p->content_language:"");
+}
+
+const char *rfc2045_content_md5(const struct rfc2045 *p)
+{
+ return (p->content_md5 ? p->content_md5:"");
+}
+
+void rfc2045_mimepos(const struct rfc2045 *p,
+ off_t *start_pos, off_t *end_pos, off_t *start_body,
+ off_t *nlines, off_t *nbodylines)
+{
+ *start_pos=p->startpos;
+ *end_pos=p->endpos;
+
+ *nlines=p->nlines;
+ *nbodylines=p->nbodylines;
+ if (p->parent) /* MIME parts do not have the trailing CRLF */
+ {
+ *end_pos=p->endbody;
+ if (*nlines) --*nlines;
+ if (*nbodylines) --*nbodylines;
+ }
+ *start_body=p->startbody;
+
+ if (*start_body == *start_pos) /* No header */
+ {
+ *start_body= *end_pos;
+ }
+}
+
+unsigned rfc2045_mimepartcount(const struct rfc2045 *p)
+{
+const struct rfc2045 *q;
+unsigned n=0;
+
+ for (q=p->firstpart; q; q=q->next) ++n;
+ return (n);
+}
+
+/*
+** Generic interface into parse_content_header
+*/
+
+struct rfc2045_parse_mime_info {
+ void (*header_type_cb)(const char *, void *);
+ void (*header_param_cb)(const char *, const char *, void *);
+ void *void_arg;
+};
+
+static void parse_mime_cb(char *, void *);
+static void parse_param_cb(const char *, struct rfc822t *,
+ int, int, void *);
+
+int rfc2045_parse_mime_header(const char *header,
+ void (*header_type_cb)(const char *, void *),
+ void (*header_param_cb)(const char *,
+ const char *,
+ void *),
+ void *void_arg)
+{
+ struct rfc2045_parse_mime_info mi;
+ struct rfc822t *h=rfc822t_alloc_new(header, NULL, NULL);
+
+ mi.header_type_cb=header_type_cb;
+ mi.header_param_cb=header_param_cb;
+ mi.void_arg=void_arg;
+
+ if (!h)
+ return -1;
+
+ parse_content_header(h, 0, parse_mime_cb, parse_param_cb, &mi);
+ rfc822t_free(h);
+ return 0;
+}
+
+static void parse_mime_cb(char *t, void *void_arg)
+{
+ struct rfc2045_parse_mime_info *mi=
+ (struct rfc2045_parse_mime_info *)void_arg;
+
+ (*mi->header_type_cb)(t, mi->void_arg);
+ free(t);
+}
+
+
+static void parse_param_cb(const char *name,
+ struct rfc822t *header, int start,
+ int len, void *void_arg)
+{
+ struct rfc2045_parse_mime_info *mi=
+ (struct rfc2045_parse_mime_info *)void_arg;
+ char *p=paste_tokens(header, start, len);
+
+ if (!p)
+ return;
+
+ (*mi->header_param_cb)(name, p, mi->void_arg);
+ free(p);
+}
diff --git a/rfc2045/rfc2045.h b/rfc2045/rfc2045.h
new file mode 100644
index 0000000..ddfab1f
--- /dev/null
+++ b/rfc2045/rfc2045.h
@@ -0,0 +1,658 @@
+/*
+** Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#ifndef rfc2045_h
+#define rfc2045_h
+
+#include "rfc2045/rfc2045_config.h" /* VPATH build */
+#include "numlib/numlib.h"
+#include <sys/types.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if 0
+}
+#endif
+
+#define RFC2045_ISMIME1(p) ((p) && atoi(p) == 1)
+#define RFC2045_ISMIME1DEF(p) (!(p) || atoi(p) == 1)
+
+struct rfc2045 {
+ struct rfc2045 *parent;
+ unsigned pindex;
+ struct rfc2045 *next;
+
+ off_t startpos, /* At which offset in msg this section starts */
+ endpos, /* Where it ends */
+ startbody, /* Where the body of the msg starts */
+ endbody; /* endpos - trailing CRLF terminator */
+ off_t nlines; /* Number of lines in message */
+ off_t nbodylines; /* Number of lines only in the body */
+ char *mime_version;
+ char *content_type;
+ struct rfc2045attr *content_type_attr; /* Content-Type: attributes */
+
+ char *content_disposition;
+ char *boundary;
+ struct rfc2045attr *content_disposition_attr;
+ char *content_transfer_encoding;
+ int content_8bit; /*
+ ** Set if content_transfer_encoding is
+ ** 8bit
+ */
+ char *content_id;
+ char *content_description;
+ char *content_language;
+ char *content_md5;
+ char *content_base;
+ char *content_location;
+ struct rfc2045ac *rfc2045acptr;
+ int has8bitchars; /* For rewriting */
+ int haslongline; /* For rewriting */
+ unsigned rfcviolation; /* Boo-boos */
+
+#define RFC2045_ERR8BITHEADER 1 /* 8 bit characters in headers */
+#define RFC2045_ERR8BITCONTENT 2 /* 8 bit contents, but no 8bit
+ content-transfer-encoding */
+#define RFC2045_ERR2COMPLEX 4 /* Too many nested contents */
+#define RFC2045_ERRBADBOUNDARY 8 /* Overlapping MIME boundaries */
+
+ unsigned numparts; /* # of parts allocated */
+
+ char *rw_transfer_encoding; /* For rewriting */
+
+#define RFC2045_RW_7BIT 1
+#define RFC2045_RW_8BIT 2
+
+ /* Subsections */
+
+ struct rfc2045 *firstpart, *lastpart;
+
+ /* Working area */
+
+ char *workbuf;
+ size_t workbufsize;
+ size_t workbuflen;
+ int workinheader;
+ int workclosed;
+ int isdummy;
+ int informdata; /* In a middle of a long form-data part */
+ char *header;
+ size_t headersize;
+ size_t headerlen;
+
+ int (*decode_func)(struct rfc2045 *, const char *, size_t);
+ void *misc_decode_ptr;
+ int (*udecode_func)(const char *, size_t, void *);
+} ;
+
+struct rfc2045attr {
+ struct rfc2045attr *next;
+ char *name;
+ char *value;
+ } ;
+
+struct rfc2045 *rfc2045_alloc();
+void rfc2045_parse(struct rfc2045 *, const char *, size_t);
+void rfc2045_parse_partial(struct rfc2045 *);
+void rfc2045_free(struct rfc2045 *);
+
+void rfc2045_mimeinfo(const struct rfc2045 *,
+ const char **,
+ const char **,
+ const char **);
+
+const char *rfc2045_boundary(const struct rfc2045 *);
+int rfc2045_isflowed(const struct rfc2045 *);
+int rfc2045_isdelsp(const struct rfc2045 *);
+char *rfc2045_related_start(const struct rfc2045 *);
+const char *rfc2045_content_id(const struct rfc2045 *);
+const char *rfc2045_content_description(const struct rfc2045 *);
+const char *rfc2045_content_language(const struct rfc2045 *);
+const char *rfc2045_content_md5(const struct rfc2045 *);
+
+void rfc2045_mimepos(const struct rfc2045 *, off_t *, off_t *, off_t *,
+ off_t *, off_t *);
+unsigned rfc2045_mimepartcount(const struct rfc2045 *);
+
+void rfc2045_xdump(struct rfc2045 *);
+
+struct rfc2045id {
+ struct rfc2045id *next;
+ int idnum;
+} ;
+
+void rfc2045_decode(struct rfc2045 *,
+ void (*)(struct rfc2045 *, struct rfc2045id *, void *),
+ void *);
+
+struct rfc2045 *rfc2045_find(struct rfc2045 *, const char *);
+
+
+/*
+** Source of an rfc2045-formatted content (internal)
+*/
+
+struct rfc2045src {
+ void (*deinit_func)(void *);
+
+ int (*seek_func)(off_t pos, void *);
+ ssize_t (*read_func)(char *buf, size_t cnt, void *);
+
+ void *arg;
+};
+/* Read from a filedesc, returns a malloced buffer */
+
+struct rfc2045src *rfc2045src_init_fd(int fd);
+
+/* Destroy a rfc2045src */
+
+void rfc2045src_deinit(struct rfc2045src *);
+
+/************************/
+
+void rfc2045_cdecode_start(struct rfc2045 *,
+ int (*)(const char *, size_t, void *), void *);
+int rfc2045_cdecode(struct rfc2045 *, const char *, size_t);
+int rfc2045_cdecode_end(struct rfc2045 *);
+
+const char *rfc2045_getdefaultcharset();
+void rfc2045_setdefaultcharset(const char *);
+struct rfc2045 *rfc2045_fromfd(int);
+#define rfc2045_fromfp(f) (rfc2045_fromfd(fileno((f))))
+struct rfc2045 *rfc2045header_fromfd(int);
+#define rfc2045header_fromfp(f) (rfc2045header_fromfd(fileno((f))))
+
+extern void rfc2045_error(const char *);
+
+
+struct rfc2045ac {
+ void (*start_section)(struct rfc2045 *);
+ void (*section_contents)(const char *, size_t);
+ void (*end_section)();
+ } ;
+
+struct rfc2045 *rfc2045_alloc_ac();
+int rfc2045_ac_check(struct rfc2045 *, int);
+int rfc2045_rewrite(struct rfc2045 *p, struct rfc2045src *src, int fdout_arg,
+ const char *appname);
+int rfc2045_rewrite_func(struct rfc2045 *p, struct rfc2045src *src,
+ int (*funcarg)(const char *, int, void *),
+ void *funcargarg,
+ const char *appname);
+
+/* Internal functions */
+
+int rfc2045_try_boundary(struct rfc2045 *, struct rfc2045src *, const char *);
+char *rfc2045_mk_boundary(struct rfc2045 *, struct rfc2045src *);
+const char *rfc2045_getattr(const struct rfc2045attr *, const char *);
+int rfc2045_attrset(struct rfc2045attr **, const char *, const char *);
+
+/* MIME content base/location */
+
+char *rfc2045_content_base(struct rfc2045 *p);
+ /* This joins Content-Base: and Content-Location:, as best as I
+ ** can figure it out.
+ */
+
+char *rfc2045_append_url(const char *, const char *);
+ /* Do this with two arbitrary URLs */
+
+/* MISC mime functions */
+
+struct rfc2045 *rfc2045_searchcontenttype(struct rfc2045 *, const char *);
+ /* Assume that the "real" message text is the first MIME section here
+ ** with the given content type.
+ */
+
+int rfc2045_decodemimesection(struct rfc2045src *, /* Message to decode */
+ struct rfc2045 *, /* MIME section to decode */
+ int (*)(const char *, size_t, void *),
+ /*
+ ** Callback function that receives decoded
+ ** content.
+ */
+ void * /* 3rd arg to the callback function */
+ );
+/*
+** Decode a given MIME section.
+*/
+
+int rfc2045_decodetextmimesection(struct rfc2045src *, /* Message to decode */
+ struct rfc2045 *, /* MIME section */
+ const char *, /* Convert to this character set */
+ int *, /* Set to non-0 if MIME section contained chars that could not be converted to the requested charset */
+ int (*)(const char *, size_t, void *),
+ /*
+ ** Callback function that receives decoded
+ ** content.
+ */
+ void * /* 3rd arg to the callback function */
+ );
+ /*
+ ** Like decodemimesction(), except that the text is automatically
+ ** convert to the specified character set (this function falls back
+ ** to decodemimesection() if libunicode.a is not available, or if
+ ** either the specified character set, or the MIME character set
+ ** is not supported by libunicode.a
+ */
+
+
+ /*
+ ** READ HEADERS FROM A MIME SECTION.
+ **
+ ** Call rfc2045header_start() to allocate a structure for the given
+ ** MIME section.
+ **
+ ** Call rfc2045header_get() to repeatedly get the next header.
+ ** Function returns < 0 for a failure (out of memory, or something
+ ** like that). Function returns 0 for a success. Example:
+ **
+ ** rfc2045header_get(ptr, &header, &value, 0);
+ **
+ ** If success: check if header is NULL - end of headers, else
+ ** "header" and "value" will contain the RFC 822 header.
+ **
+ ** Last argument is flags:
+ */
+
+#define RFC2045H_NOLC 1 /* Do not convert header to lowercase */
+#define RFC2045H_KEEPNL 2 /* Preserve newlines in the value string
+ ** of multiline headers.
+ */
+
+struct rfc2045headerinfo *
+ rfc2045header_start(struct rfc2045src *,/* Readonly source */
+ struct rfc2045 * /* MIME section to read */
+ );
+
+int rfc2045header_get(struct rfc2045headerinfo *,
+ char **, /* Header return */
+ char **, /* Value return */
+ int); /* Flags */
+
+void rfc2045header_end(struct rfc2045headerinfo *);
+
+
+/*
+** Generic MIME header parsing code.
+**
+** header - something like "text/plain; charset=us-ascii; format=flowed".
+**
+** header_type_cb - callback function, receives the "text/plain" parameter.
+**
+** header_param_cb - callback function, repeatedly invoked to process the
+** additional parameters. In this example, receives "charset" and "us-ascii".
+** Note -t he first parameter will always be in lowercase.
+**
+** void_arg - passthrough parameter to the callback functions.
+*/
+
+int rfc2045_parse_mime_header(const char *header,
+ void (*header_type_cb)(const char *, void *),
+ void (*header_param_cb)(const char *,
+ const char *,
+ void *),
+ void *void_arg);
+
+/*
+** The rfc2045_makereply function is used to generate an initial
+** reply to a MIME message. rfc2045_makereply takes the following
+** structure:
+*/
+
+struct rfc2045_mkreplyinfo {
+
+ struct rfc2045src *src; /* Original message source */
+
+ struct rfc2045 *rfc2045partp;
+ /*
+ ** rfc2045 structure for the message to reply. This may actually
+ ** represent a single message/rfc822 section within a larger MIME
+ ** message digest, in which case we format a reply to this message.
+ */
+
+ void *voidarg; /* Transparent argument passed to the callback
+ ** functions.
+ */
+
+ /*
+ ** The following callback functions are called to generate the reply
+ ** message. They must be initialized.
+ */
+
+ void (*write_func)(const char *, size_t, void *);
+ /* Called to write out the content of the message */
+
+ void (*writesig_func)(void *);
+ /* Called to write out the sender's signature */
+
+ int (*myaddr_func)(const char *, void *);
+ /* myaddr_func receives a pointer to an RFC 822 address, and it
+ ** should return non-zero if the address is the sender's address
+ */
+
+ const char *replymode;
+ /*
+ ** replymode must be initialized to one of the following. It sets
+ ** the actual template for the generated response.
+ **
+ ** "forward" - forward original message.
+ ** "forwardatt" - forward original message as an RFC822 attachment
+ ** "reply" - a standard reply to the original message's sender
+ ** "replydsn" - a DSN reply to the original message's sender
+ ** "feedback" - generate a feedback report (RFC 5965)
+ ** "replyfeedback" - "feedback" to the sender's address.
+ ** "replyall" - a "reply to all" response.
+ ** "replylist" - "reply to mailing list" response. This is a reply
+ ** that's addressed to the mailing list the original message was sent
+ ** to.
+ */
+
+ int replytoenvelope;
+ /*
+ ** If non-zero, the "reply" or "replydsn" message gets addressed to the
+ ** "Return-Path" or "Errors-To" address, if available.
+ */
+
+ int donotquote;
+
+ /*
+ ** If donotquote is set, the contents of the original message are not
+ ** quoted by any of the "reply" modes, and replysalut (below) does not
+ ** get emitted.
+ */
+
+ int fullmsg;
+ /*
+ ** For replydsn, feedback, replyfeedback, attach the entire message
+ ** instead of just its headers.
+ */
+
+ const char *replysalut;
+ /*
+ ** This should be set to the salutation to be used for the reply.
+ ** The following %-formats may appear in this string:
+ **
+ ** %% - an explicit % character
+ **
+ ** %n - a newline character
+ **
+ ** %C - the X-Newsgroup: header from the original message
+ **
+ ** %N - the Newsgroups: header from the original message
+ **
+ ** %i - the Message-ID: header from the original message
+ **
+ ** %f - the original message's sender's address
+ **
+ ** %F - the original message's sender's name
+ **
+ ** %S - the Subject: header from the original message
+ **
+ ** %d - the original message's date, in the local timezone
+ **
+ ** %{...}d - use strftime() to format the original message's date.
+ ** A plain %d is equivalent to %{%a, %d %b %Y %H:%M:%S %z}d.
+ **
+ ** Example: "%F writes:"
+ */
+
+ const char *forwarddescr;
+ /*
+ ** For forwardatt, this is the Content-Description: header,
+ ** (typically "Forwarded message").
+ */
+
+ /*
+ ** If not NULL, overrides the Subject: header
+ */
+
+ const char *subject;
+
+ /*
+ ** When reply mode is 'replydsn', dsnfrom must be set to a valid
+ ** email address that's specified as the address that's generating
+ ** the DSN.
+ */
+ const char *dsnfrom;
+
+ /*
+ ** When reply mode is 'replyfeedback', feedbacktype must be set to
+ ** one of the registered feedback types:
+ ** "abuse", "fraud", "other", "virus".
+ */
+ const char *feedbacktype;
+
+ /*
+ ** Feedback report headers.
+ **
+ ** NOTE: rfc2045_makereply() automatically inserts the
+ ** Feedback-Type: (from feedbacktype), User-Agent:, Version:, and
+ ** Arrival-Date: headers.
+ **
+ ** This is an array of alternating header name and header value
+ ** strings. The header name string does not contain a colon,
+ ** rfc2045_makereply supplies one. And, basically, generates
+ ** "name: value" from this list.
+ **
+ ** For convenience-sake, the capitalization of the headers get
+ ** adjusted to match the convention in RFC 5965.
+ **
+ ** The list, which must contain an even number of strings, is terminated
+ ** by a NULL pointer.
+ */
+ const char * const *feedbackheaders;
+
+ /*
+ ** Set the reply/fwd MIME headers. If this is a NULL pointer,
+ ** write_func() receives ``Content-Type: text/plain; format=flowed;
+ ** delsp=yes; charset="charset" '' with the charset specified below,
+ ** and "Content-Transfer-Encoding: 8bit".
+ **
+ ** If this is not a NULL pointer, the effect of
+ ** this function should be invocation of write_func() to perform the
+ ** analogous purpose.
+ **
+ ** The output of content_set_charset() should be consistent with the
+ ** contents of the charset field.
+ */
+
+ void (*content_set_charset)(void *);
+
+ /*
+ ** Set the reply/fwd content.
+ **
+ ** This function gets called at the point where the additional contents
+ ** of the reply/fwd should go.
+ **
+ ** If this is not a NULL pointer, the effect of this function should
+ ** be invocation of write_func() with the additional contents of the
+ ** reply/fwd. The added content should be consistent with the
+ ** charset field.
+ **
+ ** Note -- this content is likely to end up in a multipart MIME
+ ** message, as such it should not contain any lines that look like
+ ** MIME boundaries.
+ */
+
+ void (*content_specify)(void *);
+
+ const char *mailinglists;
+ /*
+ ** This should be set to a whitespace-delimited list of mailing list
+ ** RFC 822 addresses that the respondent is subscribed to. It is used
+ ** to figure out which mailing list the original message was sent to
+ ** (all addresses in the original message are compared against this
+ ** list). In the event that we can't find a mailing list address on
+ ** the original message, "replylist" will fall back to "replyall".
+ */
+
+ const char *charset;
+ /* The respondent's local charset */
+
+ const char *forwardsep;
+ /* This is used instead of replysalut for forwards. */
+} ;
+
+int rfc2045_makereply(struct rfc2045_mkreplyinfo *);
+
+/********** Search message content **********/
+
+/*
+** Callback passed rfc2045_decodemsgtoutf8()
+*/
+
+struct rfc2045_decodemsgtoutf8_cb {
+
+ int flags; /* Optional flags, see below */
+
+ /* Define a non-null function pointer. It gets the name of a header,
+ ** and the raw, unformatted, header contents.
+ ** If returns non-0, the header gets converted and sent to output.
+ ** If null, all headers are sent
+ */
+
+ int (*headerfilter_func)(const char *name, const char *raw, void *arg);
+
+ /* The output function */
+ int (*output_func)(const char *data, size_t cnt, void *arg);
+
+ /* If not null, gets invoked after decoding a single header */
+ int (*headerdone_func)(const char *headername, void *arg);
+
+ void *arg; /* Passthrough arg to _funcs */
+};
+
+#define RFC2045_DECODEMSG_NOBODY 0x01
+/* Do not decode MIME content, headers only */
+
+#define RFC2045_DECODEMSG_NOHEADERS 0x02
+/*
+** Do not decode MIME headers, only body. This is the same as using a
+** headerfilter_func that always returns 0
+*/
+
+#define RFC2045_DECODEMSG_NOHEADERNAME 0x04
+/*
+** Do not prepend name: to converted header content.
+*/
+
+/*
+** Convert a message into a utf8 bytestream. The output produced by this
+** function is a catentation of decoded header and text content data, converted
+** to utf8.
+**
+** This is fed into an output function. The output function takes a single
+** octet, and returns 0 if the octet was processed, or a negative value if
+** the output was aborted.
+*/
+
+int rfc2045_decodemsgtoutf8(struct rfc2045src *src, /* The message */
+ struct rfc2045 *p, /* The parsed message */
+
+ /* The callback */
+ struct rfc2045_decodemsgtoutf8_cb *callback);
+
+
+/********** Decode RFC 2231 attributes ***********/
+
+/*
+** rfc2231_decodeType() decodes an RFC 2231-encoded Content-Type: header
+** attribute, and rfc2231_decodeDisposition() decodes the attribute in the
+** Content-Disposition: header.
+**
+** chsetPtr, langPtr, and textPtr should point to a char ptr. These
+** functions automatically allocate the memory, the caller's responsible for
+** freeing it. A NULL argument may be provided if the corresponding
+** information is not wanted.
+*/
+
+int rfc2231_decodeType(struct rfc2045 *rfc, const char *name,
+ char **chsetPtr,
+ char **langPtr,
+ char **textPtr);
+
+int rfc2231_decodeDisposition(struct rfc2045 *rfc, const char *name,
+ char **chsetPtr,
+ char **langPtr,
+ char **textPtr);
+
+/*
+** The following two functions convert the decoded string to the local
+** charset via unicodelib. textPtr cannot be null, this time, because this
+** is the only return value. A NULL myChset is an alias for the default
+** charset.
+*/
+
+int rfc2231_udecodeType(struct rfc2045 *rfc, const char *name,
+ const char *myChset,
+ char **textPtr);
+
+int rfc2231_udecodeDisposition(struct rfc2045 *rfc, const char *name,
+ const char *myChset,
+ char **textPtr);
+
+/*
+** Build an RFC 2231-encoded name*=value.
+**
+** name, value, charset, language: see RFC 2231.
+**
+** (*cb_func) gets invoked 1 or more time, receives a "name=value" pair
+** each time.
+**
+** cb_func must return 0; a non-0 return terminates rfc2231_attrCreate, which
+** passes through the return code.
+**
+*/
+int rfc2231_attrCreate(const char *name, const char *value,
+ const char *charset,
+ const char *language,
+ int (*cb_func)(const char *param,
+ const char *value,
+ void *void_arg),
+ void *cb_arg);
+
+/** NON-PUBLIC DATA **/
+
+struct rfc2231param {
+ struct rfc2231param *next;
+
+ int paramnum;
+ int encoded;
+
+ const char *value;
+};
+
+void rfc2231_paramDestroy(struct rfc2231param *paramList);
+int rfc2231_buildAttrList(struct rfc2231param **paramList,
+ const char *name,
+
+ const char *attrName,
+ const char *attrValue);
+
+void rfc2231_paramDecode(struct rfc2231param *paramList,
+ char *charsetPtr,
+ char *langPtr,
+ char *textPtr,
+ int *charsetLen,
+ int *langLen,
+ int *textLen);
+
+#if 0
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/rfc2045/rfc2045.sgml b/rfc2045/rfc2045.sgml
new file mode 100644
index 0000000..788b21c
--- /dev/null
+++ b/rfc2045/rfc2045.sgml
@@ -0,0 +1,578 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<!-- Copyright 2001-2007 Double Precision, Inc. See COPYING for -->
+<!-- distribution information. -->
+<refentry>
+ <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info>
+
+ <refmeta>
+ <refentrytitle>rfc2045</refentrytitle>
+ <manvolnum>3</manvolnum>
+ <refmiscinfo class='manual'>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>rfc2045</refname>
+ <refpurpose>RFC 2045 (MIME) parsing library</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+
+ <informalexample>
+ <programlisting format="linespecific">
+#include &lt;rfc822.h&gt;
+#include &lt;rfc2045.h&gt;
+
+cc ... -lrfc2045 -lrfc822
+</programlisting>
+ </informalexample>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+The rfc2045 library parses MIME-formatted messages.
+The rfc2045 library is used to:</para>
+
+ <para>
+1) Parse the structure of a MIME formatted message</para>
+
+ <para>
+2) Examine the contents of each MIME section</para>
+
+ <para>
+3) Optionally rewrite and reformat the message.</para>
+
+ <refsect2>
+ <title>Creating an rfc2045 structure</title>
+ <informalexample>
+ <programlisting format="linespecific">
+#include &lt;rfc2045.h&gt;
+
+struct rfc2045 *ptr=rfc2045_alloc();
+void rfc2045_parse(struct rfc2045 *ptr, const char *txt, size_t cnt);
+
+struct rfc2045 *ptr=rfc2045_fromfd(int fd);
+struct rfc2045 *ptr=rfc2045_fromfp(FILE *fp);
+
+void rfc2045_free(struct rfc2045 *ptr);
+
+void rfc2045_error(const char *errmsg)
+{
+ perror(errmsg);
+ exit(0);
+}
+</programlisting>
+ </informalexample>
+
+ <para>
+The <structname>rfc2045</structname> structure is created from an existing
+message.
+The function <function moreinfo="none">rfc2045_alloc</function>() allocates the structure,
+then <function moreinfo="none">rfc2045_parse</function>() is
+called to initialize the structure based on the contents of a message.
+<parameter moreinfo="none">txt</parameter> points to the contents of the message, and
+<parameter moreinfo="none">cnt</parameter> contains the number of bytes in the message.</para>
+
+ <para>
+Large messages are parsed by calling <function moreinfo="none">rfc2045_parse</function>()
+multiple number of times, each time passing a portion of the overall message.
+There is no need to call a separate function after the entire message is
+parsed -- the <structname>rfc2045</structname> structure is created
+dynamically, on the fly.</para>
+
+ <para>
+<function moreinfo="none">rfc2045_alloc</function>() returns NULL if there was insufficient
+memory to allocate the structure. The <function moreinfo="none">rfc2045_parse</function>()
+also allocates memory, internally, however
+no error indication is return in the event of a memory allocation failure.
+Instead, the function <function moreinfo="none">rfc2045_error</function>() is called,
+with <parameter moreinfo="none">errmsg</parameter> set to
+<literal moreinfo="none">"Out of memory"</literal>.
+<function moreinfo="none">rfc2045_error</function>() is also called by
+<function moreinfo="none">rfc2045_alloc</function>() - it also
+calls <function moreinfo="none">rfc2045_error</function>(), before returning a
+NULL pointer.</para>
+
+ <para>
+The <function moreinfo="none">rfc2045_error</function>() function is not included in the
+rfc2045 library, it must be defined by the application to report the error in
+some appropriate way. All functions below will use
+<function moreinfo="none">rfc2045_error</function>() to report an error condition
+(currently only insufficient memory is reported), in addition to returning any
+kind of an error indicator. Some functions do not return an error indicator,
+so <function moreinfo="none">rfc2045_error</function>() is the only reliable way to detect a
+failure.</para>
+
+ <para>
+The <function moreinfo="none">rfc2045_fromfd</function>() function initializes an
+<structname>rfc2045</structname> structure from
+a file descriptor. It is equivalent to calling
+<function moreinfo="none">rfc2045_alloc</function>(), then reading
+the contents of the given file descriptor, and calling
+<function moreinfo="none">rfc2045_parse</function>(). The
+rfc2045_fromfp() function initializes an <structname>rfc2045</structname>
+structure from a FILE.</para>
+
+ <para>
+After the <structname>rfc2045</structname> structure is initialized, the
+functions described
+below may be used to access and work with the contents of the structure. When
+the <structname>rfc2045</structname> structure is no longer needed, the
+function <function moreinfo="none">rfc2045_free</function>() deallocates and destroys the
+structure.</para>
+ </refsect2>
+
+ <refsect2>
+
+ <title>Structure of a MIME message</title>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+struct rfc2045 {
+
+ struct rfc2045 *parent;
+
+ struct rfc2045 *firstpart;
+ struct rfc2045 *next;
+ int isdummy;
+ int rfcviolation;
+} ;
+</programlisting>
+ </informalexample>
+
+
+ <para>The <structname>rfc2045</structname> structure has many fields,
+only some are publicly documented. A
+MIME message is represented by a recursive tree of linked
+<structname>rfc2045</structname>
+structures. Each instance of the <structname>rfc2045</structname> structure
+represents a single
+MIME section of a MIME-formatted message.</para>
+
+ <para>
+The top-level structure that represents the entire message is created by the
+<function moreinfo="none">rfc2045_alloc</function>() function. The remaining structures are
+created dynamically by
+<function moreinfo="none">rfc2045_parse</function>(). Any <structname>rfc2045</structname>
+structure, except ones whose
+<structfield>isdummy</structfield> flag is set, may be used as an argument to
+any function described in the following chapters.</para>
+
+ <para>
+The <structfield>rfcviolation</structfield> field in the top-level
+<structname>rfc2045</structname>
+indicates any errors found while parsing the MIME message.
+<structname>rfcviolation</structname> is a bitmask of the following
+flags:</para>
+ <variablelist>
+ <varlistentry>
+ <term><errorcode moreinfo="none">RFC2045_ERR8BITHEADER</errorcode></term>
+ <listitem>
+ <para>
+Illegal 8-bit characters in MIME headers.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><errorcode moreinfo="none">RFC2045_ERR8BITCONTENT</errorcode></term>
+ <listitem>
+ <para>
+Illegal 8-bit contents of a MIME section that declared a 7bit transfer
+encoding.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><errorcode moreinfo="none">RFC2045_ERR2COMPLEX</errorcode></term>
+ <listitem>
+ <para>
+The message has too many MIME sections, this is a potential denial-of-service
+attack.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><errorcode moreinfo="none">RFC2045_ERRBADBOUNDARY</errorcode></term>
+ <listitem>
+ <para>
+Ambiguous nested multipart MIME boundary strings.
+(Nested MIME boundary strings where one string is a prefix of another
+string).</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>
+In each <structname>rfc2045</structname> structure that represents a
+multipart MIME section (or one that contains <literal moreinfo="none">message/rfc822</literal>
+content) the <structfield>firstpart</structfield> pointer points to
+the first MIME section in the multipart MIME section (or the included
+"message/rfc822" MIME section). If there are more than one MIME sections in a
+multipart MIME section <structfield>firstpart-&gt;next</structfield> gets you
+the second MIME section, <structfield>firstpart-&gt;next-&gt;next</structfield>
+gets you the third MIME section, and so on. <structfield>parent</structfield>
+points to the parent MIME section, which is NULL for the top-level MIME
+section.</para>
+
+ <para>
+Not all MIME sections are created equal. In a multipart MIME section,
+there is an initial, unused, "filler" section before the first MIME delimiter
+(see
+<ulink url="http://www.rfc-editor.org/rfc/rfc2045.txt">RFC 2045</ulink>
+for more information). This filler section typically contains a
+terse message saying that this is a MIME-formatted message.
+This is not considered to be a "real" MIME section, and
+all MIME-aware software must ignore those. These filler sections are
+designated by setting the <structfield>isdummy</structfield> field
+to a non-zero value. All <structname>rfc2045</structname>
+structures that have <structfield>isdummy</structfield> set should be
+ignored, and skipped over, when traversing the
+<structname>rfc2045</structname> tree.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Basic MIME information</title>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+const char *content_type, *content_transfer_encoding,
+ *content_character_set;
+
+void rfc2045_mimeinfo(const struct rfc2045 *ptr,
+ &amp;content_type, &amp;content_transfer_encoding,
+ &amp;content_character_set);
+
+off_t start_pos, end_pos, start_body, nlines, nbodylines;
+
+void rfc2045_mimepos(const struct rfc2045 *ptr,
+ &amp;start_pos, &amp;end_pos, &amp;start_body, &amp;nlines,
+ &amp;nbodylines);
+</programlisting>
+</informalexample>
+
+ <para>
+The <function moreinfo="none">rfc2045_mimeinfo</function>() function returns the MIME
+content type, encoding method,
+and the character set of the given MIME section. Where the MIME section does
+not specify any property, <function moreinfo="none">rfc2045_mimeinfo</function>()
+automatically supplies a default value. The character set is only meaningful
+for MIME sections with a text content type, however it is still defaulted for
+other sections. It is not permissible to supply a NULL pointer for any
+argument to <function moreinfo="none">rfc2045_mimeinfo</function>().</para>
+
+ <para>
+The <function moreinfo="none">rfc2045_mimepos</function>() function locates the position of
+the given MIME section in the original message. It is not permissible to
+supply a NULL pointer for any argument to
+<function moreinfo="none">rfc2045_mimepos</function>(). All arguments must be used.</para>
+
+ <para>
+<structfield>start_pos</structfield> and <structfield>end_pos</structfield>
+point to the starting and the ending offset, from the beginning of the
+message, of this MIME section. <structfield>nlines</structfield>
+is initialized to the number of lines of text in this MIME section.
+<structfield>start_pos</structfield> is the start of MIME headers for this
+MIME section.
+<structfield>start_body</structfield> is the start of the actual content of
+this MIME section (after all the MIME headers, and the delimiting blank line),
+and <structfield>nbodylines</structfield> is the number of
+lines of actual content in this MIME section.</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+const char *id=rfc2045_content_id(
+ const struct rfc2045 *ptr);
+
+const char *desc=rfc2045_content_description(
+ const struct rfc2045 *ptr);
+
+const char *lang=rfc2045_content_language(
+ const struct rfc2045 *ptr);
+
+const char *md5=rfc2045_content_md5(
+ const struct rfc2045 *ptr);
+</programlisting>
+ </informalexample>
+
+ <para>
+These functions return the contents of the corresponding MIME headers. If
+these headers do not exist, these functions return an empty string, "", NOT a
+null pointer.</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+char *id=rfc2045_related_start(const struct rfc2045 *ptr);
+</programlisting>
+ </informalexample>
+
+ <para>
+This function returns the <structfield>start</structfield> attribute of the
+<literal moreinfo="none">Content-Type:</literal>
+header, which is used by <literal moreinfo="none">multipart/related</literal>
+MIME content. This function returns a
+dynamically-allocated buffer, which must be
+<function moreinfo="none">free</function>(3)-ed after use (a null
+pointer is returned if there was insufficient memory for the buffer, and
+rfc2045_error() is called).</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+const struct rfc2045 *ptr;
+
+const char *disposition=ptr-&gt;content_disposition;
+
+char *charset;
+char *language;
+char *value;
+
+int error;
+
+error=rfc2231_decodeType(rfc, "name", &amp;charset,
+ &amp;language, &amp;value);
+error=rfc2231_decodeDisposition(rfc, "name", &amp;charset,
+ &amp;language, &amp;value);
+
+</programlisting>
+ </informalexample>
+
+ <para>
+These functions and structures provide a mechanism for reading the MIME
+attributes in the <literal moreinfo="none">Content-Type:</literal> and
+<literal moreinfo="none">Content-Disposition:</literal> headers.
+The MIME content type is returned by
+<function moreinfo="none">rfc2045_mimeinfo</function>().
+The MIME content disposition can be accessed in the
+<structfield>content_disposition</structfield> directly (which may be
+<literal moreinfo="none">NULL</literal> if the <literal moreinfo="none">Content-Disposition:</literal>
+header was not specified).</para>
+
+ <para>
+<function moreinfo="none">rfc2231_decodeType</function>() reads MIME attributes from the
+<literal moreinfo="none">Content-Type:</literal> header, and
+<function moreinfo="none">rfc2231_decodeType</function>() reads MIME attributes from the
+<literal moreinfo="none">Content-Disposition:</literal> header.
+These functions understand MIME attributes that are encoded according to
+<ulink url="http://www.rfc-editor.org/rfc/rfc2231.txt">RFC 2231</ulink>.</para>
+
+ <para>
+These functions initialize
+<parameter moreinfo="none">charset</parameter>,
+<parameter moreinfo="none">language</parameter>, and
+<parameter moreinfo="none">value</parameter> parameters, allocating memory automatically.
+It is the caller's responsibility to use <function moreinfo="none">free</function>() to return
+the allocated memory.
+A <literal moreinfo="none">NULL</literal> may be provided in place of a parameter, indicating
+that the caller does not require the corresponding information.</para>
+
+ <para>
+<parameter moreinfo="none">charset</parameter> and
+<parameter moreinfo="none">language</parameter> will be set to an empty string
+(<emphasis>not</emphasis> <literal moreinfo="none">NULL</literal>) if the MIME parameter
+does not exist, or is not encoded according to
+<ulink url="http://www.rfc-editor.org/rfc/rfc2231.txt">RFC 2231</ulink>,
+or does not specify its character set and/or language.
+<parameter moreinfo="none">value</parameter> will be set to an empty string if the MIME
+parameter does not exist.</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+char *url=rfc2045_content_base(struct rfc2045 *ptr);
+
+char *url=rfc2045_append_url(const char *base, const char *url);
+</programlisting>
+</informalexample>
+
+ <para>
+These functions are used to work with
+<literal moreinfo="none">multipart/related</literal> MIME content.
+<function moreinfo="none">rfc2045_content_base</function>() returns the contents of either
+the <literal moreinfo="none">Content-Base:</literal> or the
+<literal moreinfo="none">Content-Location:</literal> header. If both are present, they are
+logically combined.
+<function moreinfo="none">rfc2045_append_url()</function> combines two URLs,
+<parameter moreinfo="none">base</parameter> and
+<parameter moreinfo="none">url</parameter>, and returns the absolute URL that results from the
+combination.</para>
+
+ <para>
+Both functions return a pointer to a dynamically-allocated buffer that must
+be <function moreinfo="none">free</function>(3)-ed after it is no longer needed. Both
+functions return NULL if there was no sufficient memory to allocate the
+buffer. <function moreinfo="none">rfc2045_content_base</function>()
+returns an empty string in the event that there are no
+<literal moreinfo="none">Content-Base:</literal> or
+<literal moreinfo="none">Content-Location:</literal> headers. Either argument to
+<function moreinfo="none">rfc2045_append_url</function>() may be a
+NULL, or an empty string.</para>
+
+ </refsect2>
+
+ <refsect2>
+ <title>Decoding a MIME section</title>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+void rfc2045_cdecode_start(struct rfc2045 *ptr,
+ int (*callback_func)(const char *, size_t, void *),
+ void *callback_arg);
+
+int rfc2045_cdecode(struct rfc2045 *ptr, const char *stuff,
+ size_t nstuff);
+
+int rfc2045_cdecode_end(struct rfc2045 *ptr);
+
+</programlisting>
+ </informalexample>
+
+ <para>
+These functions are used to return the raw contents of the given MIME
+section, transparently decoding quoted-printable or base64-encoded content.
+Because the rfc2045 library does not require the message to be read from a
+file (it can be stored in a memory buffer), the application is responsible for
+reading the contents of the message and calling
+<function moreinfo="none">rfc2045_cdecode</function>().</para>
+
+ <para>
+The <function moreinfo="none">rfc2045_cdecode_start</function>() function begins the process of
+decoding the given MIME section. After calling
+<function moreinfo="none">rfc2045_cdecode_start</function>(), the
+application must the repeatedly call <function moreinfo="none">rfc2045_cdecode</function>()
+with the contents of the MIME message between the offsets given by the
+<structfield>start_body</structfield> and
+<structfield>end_pos</structfield> return values from
+<function moreinfo="none">rfc2045_mimepos</function>(). The
+<function moreinfo="none">rfc2045_cdecode</function>() function can be called repeatedly, if
+necessary, for successive portions of the MIME section. After the last call
+to
+<function moreinfo="none">rfc2045_cdecode</function>(), call
+<function moreinfo="none">rfc2045_cdecode_end</function>() to finish up
+(<function moreinfo="none">rfc2045_cdecode</function>() may have saved some undecoded content
+in an internal part, and
+<function moreinfo="none">rfc2045_cdecode_end</function>() flushes it out).</para>
+
+ <para>
+<function moreinfo="none">rfc2045_cdecode</function>() and
+<function moreinfo="none">rfc2045_cdecode_end</function>() repeatedly call
+<function moreinfo="none">callback_func</function>(), passing it the decoded contents of the
+MIME section. The
+first argument to <function moreinfo="none">callback_func</function>() is a pointer to a
+portion of the decoded
+content, the second argument is the number of bytes in this portion. The
+third argument is <parameter moreinfo="none">callback_arg</parameter>.</para>
+
+ <para>
+<function moreinfo="none">callback_func</function>() is required to return zero, to continue
+decoding. If
+<function moreinfo="none">callback_func</function>() returns non-zero, the decoding
+immediately stops and
+<function moreinfo="none">rfc2045_cdecode</function>() or <function moreinfo="none">rfc2045_cdecode_end</function>() terminates with <function moreinfo="none">callback_func</function>'s return code.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Rewriting MIME messages</title>
+
+ <para>
+This library contains functions that can be used to rewrite a MIME
+message in order to convert 8-bit-encoded data to 7-bit encoding, or to
+convert 7-bit encoded data to full 8-bit data, if possible.</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+
+struct rfc2045 *ptr=rfc2045_alloc_ac();
+int necessary=rfc2045_ac_check(struct rfc2045 *ptr, int mode);
+
+int error=rfc2045_rewrite(struct rfc2045 *ptr,
+ int fdin,
+ int fdout,
+ const char *appname);
+
+int rfc2045_rewrite_func(struct rfc2045 *p, int fdin,
+ int (*funcout)(const char *, int, void *), void *funcout_arg,
+ const char *appname);
+</programlisting>
+ </informalexample>
+
+ <para>
+When rewriting will be used, the <function moreinfo="none">rfc2045_alloc_ac</function>()
+function must be used
+to create the initial <structname>rfc2045</structname> structure. This
+function allocates some
+additional structures that are used in rewriting.
+Use
+<function moreinfo="none">rfc2045_parse</function>()
+to parse the message, as usual. Use
+<function moreinfo="none">rfc2045_free</function>() in a normal way
+to destroy the <structname>rfc2045</structname> structure, when all is said and
+done.</para>
+
+ <para>
+The <function moreinfo="none">rfc2045_ac_check</function>() function must be called to
+determine whether
+rewriting is necessary. <parameter moreinfo="none">mode</parameter> must be set to one of the
+following values:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>RFC2045_RW_7BIT</term>
+ <listitem>
+ <para>
+We want to generate 7-bit content. If the
+original message contains any 8-bit content it will be converted to 7-bit
+content using quoted-printable encoding.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>RFC2045_RW_8BIT</term>
+ <listitem>
+ <para>
+We want to generate 8-bit content. If the
+original message contains any 7-bit quoted-printable content it should be
+rewritten as 8-bit content.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>
+The <function moreinfo="none">rfc2045_ac_check</function>() function returns non-zero if
+there's any content in
+the MIME message that should be converted, OR if there are any missing MIME
+headers. <function moreinfo="none">rfc2045_ac_check</function>() returns zero if there's no
+need to rewrite the
+message. However it might still be worthwhile to rewrite the message anyway.
+There are some instances where it is desirable to provide defaults for some
+missing MIME headers, but they are too trivial to require the message to be
+rewritten. One such case would be a missing Content-Transfer-Encoding: header
+for a multipart section.</para>
+
+ <para>
+Either the <function moreinfo="none">rfc2045_rewrite</function>() or the
+<function moreinfo="none">rfc2045_rewrite_func</function>() function is used
+to rewrite the message. The only difference is that
+<function moreinfo="none">rfc2045_rewrite</function>() writes
+the new message to a given file descriptor, <parameter moreinfo="none">fdout</parameter>, while
+<function moreinfo="none">rfc2045_rewrite_func</function>() repeatedly calls the <parameter moreinfo="none">funcout</parameter> function. Both
+function read the original message from <parameter moreinfo="none">fdin</parameter>.
+<parameter moreinfo="none">funcout</parameter> receives
+to a portion of the MIME message, the number of bytes in the specified
+portion, and <parameter moreinfo="none">funcout_arg</parameter>. When either function rewrites
+a MIME section,
+an informational header gets appended, noting that the message was converted
+by <parameter moreinfo="none">appname</parameter>.</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="rfc822.html"><citerefentry><refentrytitle>rfc822</refentrytitle><manvolnum>3</manvolnum></citerefentry></ulink>,
+<ulink url="reformail.html"><citerefentry><refentrytitle>reformail</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>,
+<ulink url="reformime.html"><citerefentry><refentrytitle>reformime</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>.</para>
+ </refsect1>
+</refentry>
diff --git a/rfc2045/rfc2045_fromfd.c b/rfc2045/rfc2045_fromfd.c
new file mode 100644
index 0000000..795f833
--- /dev/null
+++ b/rfc2045/rfc2045_fromfd.c
@@ -0,0 +1,65 @@
+/*
+** Copyright 1998 - 2008 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+
+#include <sys/types.h>
+
+#include "rfc2045.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* Convert a message to the RFC2045 structure */
+
+struct rfc2045 *rfc2045_fromfd(int fd)
+{
+struct rfc2045 *rfc;
+char buf[BUFSIZ];
+int n;
+off_t orig_pos;
+
+ if ((orig_pos=lseek(fd, 0L, SEEK_CUR)) == (off_t)-1) return (NULL);
+ if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) return (NULL);
+ if ((rfc=rfc2045_alloc()) == 0) return (NULL);
+
+ while ((n=read(fd, buf, sizeof(buf))) > 0)
+ rfc2045_parse(rfc, buf, n);
+ rfc2045_parse_partial(rfc);
+
+ if (lseek(fd, orig_pos, SEEK_SET) == (off_t)-1)
+ {
+ rfc2045_free(rfc);
+ rfc=0;
+ }
+ return (rfc);
+}
+
+/* Convert a message to the RFC2045 structure, halting after header */
+
+struct rfc2045 *rfc2045header_fromfd(int fd)
+{
+struct rfc2045 *rfc;
+char buf[BUFSIZ];
+int n;
+off_t orig_pos;
+
+ if ((orig_pos=lseek(fd, 0L, SEEK_CUR)) == (off_t)-1) return (NULL);
+ if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) return (NULL);
+ if ((rfc=rfc2045_alloc()) == 0) return (NULL);
+
+ while (rfc->workinheader && (n=read(fd, buf, sizeof(buf))) > 0)
+ rfc2045_parse(rfc, buf, n);
+ if (lseek(fd, orig_pos, SEEK_SET) == (off_t)-1)
+ {
+ rfc2045_free(rfc);
+ rfc=0;
+ }
+ return (rfc);
+}
diff --git a/rfc2045/rfc2045acchk.c b/rfc2045/rfc2045acchk.c
new file mode 100644
index 0000000..9632d07
--- /dev/null
+++ b/rfc2045/rfc2045acchk.c
@@ -0,0 +1,145 @@
+/*
+** Copyright 1998 - 2001 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include "rfc2045.h"
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+
+
+extern void rfc2045_enomem();
+
+int rfc2045_ac_check(struct rfc2045 *p, int rwmode)
+{
+int flag=0; /* Flag - rewriting suggested */
+struct rfc2045 *c;
+int hasnon7bit=p->has8bitchars;
+ /* hasnon7bit: 8bit chars in this section or subsections */
+const char *te;
+int is8bitte;
+
+ for (c=p->firstpart; c; c=c->next)
+ if (!c->isdummy)
+ {
+ if (rfc2045_ac_check(c, rwmode)) flag=1;
+ if (c->content_transfer_encoding &&
+ strcmp(c->content_transfer_encoding, "7bit") &&
+ strcmp(c->content_transfer_encoding, "uuencode") &&
+ strcmp(c->content_transfer_encoding, "quoted-printable"))
+ hasnon7bit=1;
+ if (c->has8bitchars)
+ p->has8bitchars=1;
+ }
+
+ if (RFC2045_ISMIME1DEF(p->mime_version) && !p->content_type)
+ {
+ if ((p->content_type=strdup("text/plain")) == 0)
+ rfc2045_enomem();
+ if (p->mime_version)
+ {
+ flag=1;
+ }
+ }
+
+ if (RFC2045_ISMIME1DEF(p->mime_version)
+ && !rfc2045_getattr(p->content_type_attr, "charset")
+ && strncasecmp(p->content_type, "text/", 5) == 0)
+ {
+ if (rfc2045_attrset(&p->content_type_attr, "charset",
+ rfc2045_getdefaultcharset()) < 0)
+ rfc2045_enomem();
+
+ if (p->mime_version
+
+ && p->firstpart == 0 /* sam - don't trigger rewrites on changes to multipart headers */
+
+ )
+ {
+ flag=1;
+ }
+ }
+
+ if (RFC2045_ISMIME1DEF(p->mime_version)
+ && !p->content_transfer_encoding)
+ {
+ if ((p->content_transfer_encoding=strdup(
+ hasnon7bit ? "8bit":"7bit")) == 0)
+ rfc2045_enomem();
+ if (p->mime_version
+
+ && p->firstpart == 0 /* sam - don't trigger rewrites on changes to multipart headers */
+ )
+ {
+ flag=1;
+ }
+ }
+
+#if 0
+ if (RFC2045_ISMIME1DEF(p->mime_version)
+ && strncmp(p->content_type, "text/", 5) == 0 && !hasnon7bit
+ && strcmp(p->content_transfer_encoding, "7bit"))
+ {
+ if (p->mime_version)
+ {
+ flag=1;
+ }
+ }
+#endif
+
+ if (RFC2045_ISMIME1DEF(p->mime_version))
+ {
+ /* Check for conversions */
+
+ te=p->content_transfer_encoding;
+ is8bitte=strcasecmp(te, "base64") &&
+ strcasecmp(te, "quoted-printable") &&
+ strcasecmp(te, "uuencode") &&
+ strcasecmp(te, "7bit"); /* 8 bit contents */
+
+ if (is8bitte && !p->has8bitchars && !p->haslongline)
+ {
+ if (p->rw_transfer_encoding)
+ free(p->rw_transfer_encoding);
+ if ((p->rw_transfer_encoding=strdup("7bit")) == 0)
+ rfc2045_enomem();
+ flag=1;
+ is8bitte=0;
+ }
+
+ if (rwmode == RFC2045_RW_7BIT && (is8bitte || p->haslongline))
+ {
+ if (p->rw_transfer_encoding)
+ free(p->rw_transfer_encoding);
+ if ((p->rw_transfer_encoding=strdup("quoted-printable"))
+ == 0)
+ rfc2045_enomem();
+ flag=1;
+ }
+ else if (rwmode == RFC2045_RW_8BIT &&
+ strcasecmp(te, "quoted-printable") == 0 &&
+ !p->haslongline)
+ {
+ if (p->rw_transfer_encoding)
+ free(p->rw_transfer_encoding);
+ if ((p->rw_transfer_encoding=strdup(hasnon7bit
+ ? "8bit":"7bit")) == 0)
+ rfc2045_enomem();
+ flag=1;
+ }
+ }
+
+ if (!p->mime_version)
+ {
+ if ((p->mime_version=strdup("1.0")) == 0)
+ rfc2045_enomem();
+ }
+ return (flag);
+}
diff --git a/rfc2045/rfc2045acprep.c b/rfc2045/rfc2045acprep.c
new file mode 100644
index 0000000..f3defa3
--- /dev/null
+++ b/rfc2045/rfc2045acprep.c
@@ -0,0 +1,106 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045.h"
+#include <ctype.h>
+#include <string.h>
+
+
+static void start_rwprep(struct rfc2045 *);
+static void do_rwprep(const char *, size_t);
+static void end_rwprep();
+
+static struct rfc2045ac rfc2045acprep={
+ &start_rwprep,
+ &do_rwprep,
+ &end_rwprep};
+
+static struct rfc2045 *currwp;
+static int curlinepos=0;
+
+typedef enum {
+ raw,
+ quotedprint,
+ qpseeneq,
+ qpseeneqh,
+ base64} state_t;
+
+static state_t curstate;
+static int statechar;
+
+#define h2nyb(c) ( (c) >= 'a' && (c) <= 'f' ? (c)-('a'-10): \
+ (c) >= 'A' && (c) <= 'F' ? (c)-('A'-10): (c)-'0')
+
+struct rfc2045 *rfc2045_alloc_ac()
+{
+struct rfc2045 *p=rfc2045_alloc();
+
+ if (p) p->rfc2045acptr= &rfc2045acprep;
+ currwp=0;
+ return (p);
+}
+
+
+static void start_rwprep(struct rfc2045 *p)
+{
+ currwp=p;
+ curlinepos=0;
+ curstate=raw;
+ if (p->content_transfer_encoding)
+ {
+ if (strcmp(p->content_transfer_encoding,
+ "quoted-printable") == 0)
+ curstate=quotedprint;
+ else if (strcmp(p->content_transfer_encoding, "base64") == 0)
+ curstate=base64;
+ }
+}
+
+static void do_rwprep(const char * p, size_t n)
+{
+ if (!currwp) return;
+ for ( ; n; --n, ++p)
+ switch (curstate) {
+ case quotedprint:
+ if (*p == '=')
+ {
+ curstate=qpseeneq;
+ continue;
+ }
+ /* FALLTHRU */
+ case raw:
+ if (*p == '\r' || *p == '\n')
+ curlinepos=0;
+ else if (++curlinepos > 500)
+ currwp->haslongline=1;
+ if ((unsigned char)*p >= 127)
+ currwp->has8bitchars=1;
+ break;
+ case qpseeneq:
+ if (*p == '\n')
+ {
+ curstate=quotedprint;
+ continue;
+ }
+ if (isspace((int)(unsigned char)*p)) continue; /* Ignore WSP */
+ statechar=*p;
+ curstate=qpseeneqh;
+ continue;
+ case qpseeneqh:
+ curstate=quotedprint;
+ if ( (unsigned char)
+ ( (h2nyb(statechar) << 4) + h2nyb(*p) ) >= 127
+ ) currwp->has8bitchars=1;
+ if (++curlinepos > 500)
+ currwp->haslongline=1;
+ continue;
+ case base64:
+ break;
+ }
+}
+
+static void end_rwprep()
+{
+}
diff --git a/rfc2045/rfc2045appendurl.c b/rfc2045/rfc2045appendurl.c
new file mode 100644
index 0000000..2f2ce6c
--- /dev/null
+++ b/rfc2045/rfc2045appendurl.c
@@ -0,0 +1,129 @@
+/*
+** Copyright 2000 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <ctype.h>
+#include "rfc2045.h"
+
+extern void rfc2045_enomem();
+
+/*
+** ---------------------------------------------------------------------
+** Attempt to parse Content-Base: and Content-Location:, and return the
+** "base" of all the relative URLs in the section.
+** ---------------------------------------------------------------------
+*/
+
+static void get_method_path(const char *p,
+ const char **method,
+ unsigned *methodl,
+ const char **path)
+{
+unsigned i;
+
+ for (i=0; p && p[i]; i++)
+ {
+ if (p[i] == ':')
+ {
+ *method=p;
+ *methodl= ++i;
+ *path=p+i;
+ return;
+ }
+
+ if (!isalpha( (int)(unsigned char)p[i]))
+ break;
+ }
+
+ *method=0;
+ *methodl=0;
+ *path=p;
+}
+
+char *rfc2045_append_url(const char *base, const char *loc)
+{
+const char *base_method;
+unsigned base_method_l;
+const char *base_path;
+
+const char *loc_method;
+unsigned loc_method_l;
+const char *loc_path;
+char *buf, *q;
+
+ get_method_path(base, &base_method, &base_method_l, &base_path);
+ get_method_path(loc, &loc_method, &loc_method_l, &loc_path);
+
+ if (loc_method_l)
+ {
+ buf=malloc(strlen(loc)+1);
+ if (!buf)
+ return NULL;
+ else
+ strcpy(buf, loc);
+ return (buf);
+ }
+
+ loc_method=base_method;
+ loc_method_l=base_method_l;
+
+ if (!base_path) base_path="";
+ if (!loc_path) loc_path="";
+
+ buf=malloc(loc_method_l + strlen(base_path)+strlen(loc_path) + 3);
+
+ if (!buf)
+ {
+ return NULL;
+ }
+
+ if (loc_method_l)
+ memcpy(buf, loc_method, loc_method_l);
+ buf[loc_method_l]=0;
+
+ q=buf + loc_method_l;
+
+ strcat(strcpy(q, base_path), "/");
+
+ if ( loc_path[0] == '/')
+ {
+ char *r;
+
+ if (loc_path[1] == '/')
+ /* Location is absolute */
+ {
+ *q=0;
+ }
+
+ /* Relative to top of base */
+
+ else if ( q[0] == '/' && q[1] == '/' &&
+ (r=strchr(q+2, '/')) != 0)
+ {
+ *r=0;
+ }
+ else
+ *q=0; /* No sys in base, just start with / */
+ }
+
+ strcat(q, loc_path);
+
+ return (buf);
+}
+
+char *rfc2045_content_base(struct rfc2045 *p)
+{
+ return (rfc2045_append_url(p->content_base, p->content_location));
+}
diff --git a/rfc2045/rfc2045cdecode.c b/rfc2045/rfc2045cdecode.c
new file mode 100644
index 0000000..34e6e50
--- /dev/null
+++ b/rfc2045/rfc2045cdecode.c
@@ -0,0 +1,214 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045.h"
+#include <ctype.h>
+#include <string.h>
+
+
+extern void rfc2045_add_buf( char **, size_t *, size_t *,
+ const char *, size_t);
+extern void rfc2045_add_workbuf(struct rfc2045 *, const char *, size_t);
+extern void rfc2045_add_workbufch(struct rfc2045 *, int);
+
+static int decode_raw(struct rfc2045 *p, const char *s, size_t l)
+{
+ if (s && l) return ((*p->udecode_func)(s,l,p->misc_decode_ptr));
+ return (0);
+}
+
+static const char xdigit[]="0123456789ABCDEF";
+
+static int tou(char c)
+{
+ if (c >= 'a' && c <= 'f')
+ return c + ('A'-'a');
+ return c;
+}
+
+static int do_decode_qp(struct rfc2045 *p)
+{
+char *a, *b, *c, *end;
+int d;
+
+ end=p->workbuf + p->workbuflen;
+ for (a=b=p->workbuf; a < end; )
+ {
+ if (*a != '=')
+ {
+ *b++ = *a++;
+ continue;
+ }
+ ++a;
+ if (!*a || a >= end || isspace((int)(unsigned char)*a))
+ break;
+
+ if ((c=strchr(xdigit, tou(*a))) == 0) continue;
+ d= (c-xdigit)*16;
+ ++a;
+ if (!*a || a >= end)
+ break;
+ if ((c=strchr(xdigit, tou(*a))) == 0) continue;
+ d += c-xdigit;
+ ++a;
+ *b++=d;
+ }
+ p->workbuflen= b-p->workbuf;
+ d=(*p->udecode_func)(p->workbuf, p->workbuflen, p->misc_decode_ptr);
+ p->workbuflen=0;
+ return (d);
+}
+
+static const unsigned char decode64tab[256]={
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,62,100,100,100,63,
+ 52,53,54,55,56,57,58,59,60,61,100,100,100,99,100,100,
+ 100,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
+ 15,16,17,18,19,20,21,22,23,24,25,100,100,100,100,100,
+ 100,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
+ 41,42,43,44,45,46,47,48,49,50,51,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100
+};
+
+/* When we have enough base64-encoded data in the buffer, decode it. */
+
+static int do_decode_base64(struct rfc2045 *p)
+{
+size_t i, j;
+char a,b,c;
+size_t k;
+int rc;
+
+ /* Remove everything except base64encoded data */
+
+ for (i=j=0; i<p->workbuflen; i++)
+ if (decode64tab[(int)(unsigned char)p->workbuf[i]] < 100)
+ p->workbuf[j++]=p->workbuf[i];
+
+
+ p->workbuflen=j;
+
+ /* Decode the data, in 4-byte pieces */
+
+ i=j / 4;
+ i=i*4;
+ k=0;
+ for (j=0; j<i; j += 4)
+ {
+ int w=decode64tab[(int)(unsigned char)p->workbuf[j]];
+ int x=decode64tab[(int)(unsigned char)p->workbuf[j+1]];
+ int y=decode64tab[(int)(unsigned char)p->workbuf[j+2]];
+ int z=decode64tab[(int)(unsigned char)p->workbuf[j+3]];
+
+ a= (w << 2) | (x >> 4);
+ b= (x << 4) | (y >> 2);
+ c= (y << 6) | z;
+ p->workbuf[k++]=a;
+ if ( p->workbuf[j+2] != '=')
+ p->workbuf[k++]=b;
+ if ( p->workbuf[j+3] != '=')
+ p->workbuf[k++]=c;
+ }
+ rc=(*p->udecode_func)(p->workbuf, k, p->misc_decode_ptr);
+
+ /* Anything left? Move it to the start of the buffer */
+
+ k=0;
+ while (j < p->workbuflen)
+ p->workbuf[k++]=p->workbuf[j++];
+ p->workbuflen=k;
+ return (rc);
+}
+
+static int decode_qp(struct rfc2045 *p, const char *s, size_t l)
+{
+size_t start,i;
+int rc;
+
+ if (!s)
+ return (do_decode_qp(p));
+
+ for (start=0; start<l; )
+ {
+ for (i=start; i<l; i++)
+ {
+ if (s[i] != '\n') continue;
+ rfc2045_add_workbuf(p, s+start, i-start);
+ rfc2045_add_workbufch(p, '\n');
+ if ((rc=do_decode_qp(p)) != 0) return (rc);
+ start= ++i;
+ break;
+ }
+ rfc2045_add_workbuf(p, s+start, i-start);
+ if (p->workbuflen > 1024)
+ {
+ char buf[10];
+ int i;
+
+ for (i=p->workbuflen - 5; i<p->workbuflen; i++)
+ if (p->workbuf[i] == '=') break;
+ if (i < p->workbuflen)
+ {
+ int j=p->workbuflen-i;
+
+ memcpy(buf, p->workbuf+i, j);
+ buf[j]=0;
+ p->workbuflen=i;
+ }
+ else buf[0]=0;
+ if ((rc=do_decode_qp(p)) != 0) return (rc);
+ rfc2045_add_workbuf(p, buf, strlen(buf));
+ }
+ start=i;
+ }
+ return (0);
+}
+
+static int decode_base64(struct rfc2045 *p, const char *s, size_t l)
+{
+ if (!s)
+ return (do_decode_base64(p));
+
+ rfc2045_add_workbuf(p, s, l);
+ if (p->workbuflen > 256)
+ return (do_decode_base64(p));
+ return (0);
+}
+
+void rfc2045_cdecode_start(struct rfc2045 *p,
+ int (*u)(const char *, size_t, void *), void *miscptr)
+{
+ p->misc_decode_ptr=miscptr;
+ p->udecode_func=u;
+ p->decode_func= &decode_raw;
+ p->workbuflen=0;
+ if (p->content_transfer_encoding)
+ {
+ if (strcmp(p->content_transfer_encoding,
+ "quoted-printable") == 0)
+ p->decode_func= &decode_qp;
+ else if (strcmp(p->content_transfer_encoding, "base64") == 0)
+ p->decode_func= &decode_base64;
+ }
+}
+
+int rfc2045_cdecode_end(struct rfc2045 *p)
+{
+ return ((*p->decode_func)(p, NULL, 0));
+}
+
+int rfc2045_cdecode(struct rfc2045 *p, const char *s, size_t l)
+{
+ if (s && l) return ((*p->decode_func)(p, s, l));
+ return (0);
+}
diff --git a/rfc2045/rfc2045charset.h.in b/rfc2045/rfc2045charset.h.in
new file mode 100644
index 0000000..fd18046
--- /dev/null
+++ b/rfc2045/rfc2045charset.h.in
@@ -0,0 +1,9 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+
+#define RFC2045CHARSET "@RFC2045CHARSET@"
+
+#define RFC2045MIMEMSG "This is a MIME-formatted message. If you see this text it means that your\nE-mail software does not support MIME-formatted messages.\n"
diff --git a/rfc2045/rfc2045decode.c b/rfc2045/rfc2045decode.c
new file mode 100644
index 0000000..1fa2d79
--- /dev/null
+++ b/rfc2045/rfc2045decode.c
@@ -0,0 +1,40 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#include "rfc2045.h"
+
+static void decode(struct rfc2045id *topid,
+ struct rfc2045id **childidptr,
+ struct rfc2045 *r,
+ void (*func)(struct rfc2045 *, struct rfc2045id *, void *),
+ void *ptr)
+{
+struct rfc2045id nextid;
+
+ *childidptr=0;
+ (*func)(r, topid, ptr);
+ *childidptr=&nextid;
+ nextid.idnum=1;
+ if (r->content_type && strncmp(r->content_type, "multipart/", 10) == 0)
+ nextid.idnum=0;
+ for (r=r->firstpart; r; r=r->next)
+ {
+ if (nextid.idnum)
+ decode(topid, &nextid.next, r, func, ptr);
+ ++nextid.idnum;
+ }
+}
+
+void rfc2045_decode(struct rfc2045 *p,
+ void (*func)(struct rfc2045 *, struct rfc2045id *, void *),
+ void *ptr)
+{
+struct rfc2045id topid;
+
+ topid.idnum=1;
+ decode(&topid, &topid.next, p, func, ptr);
+}
diff --git a/rfc2045/rfc2045decodemimesection.c b/rfc2045/rfc2045decodemimesection.c
new file mode 100644
index 0000000..03ce386
--- /dev/null
+++ b/rfc2045/rfc2045decodemimesection.c
@@ -0,0 +1,47 @@
+/*
+** Copyright 2000-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc2045.h"
+#include "rfc2045src.h"
+#include <stdio.h>
+#include <unistd.h>
+
+
+/*
+** This function is used to decode MIME section content, and pass it to
+** a handler function. It's basically a wrapper around rfc2045_cdecode
+** functions.
+*/
+
+int rfc2045_decodemimesection(struct rfc2045src *src, struct rfc2045 *rfc,
+ int (*handler)(const char *, size_t, void *),
+ void *voidarg)
+{
+ off_t start_pos, end_pos, start_body;
+ char buf[BUFSIZ];
+ ssize_t cnt;
+ off_t dummy;
+ int rc;
+
+ rfc2045_mimepos(rfc, &start_pos, &end_pos, &start_body,
+ &dummy, &dummy);
+ if (SRC_SEEK(src, start_body) == (off_t)-1) return (-1);
+
+ rfc2045_cdecode_start(rfc, handler, voidarg);
+
+ while (start_body < end_pos)
+ {
+ cnt=sizeof(buf);
+ if (cnt > end_pos-start_body)
+ cnt=end_pos-start_body;
+ cnt=SRC_READ(src, buf, cnt);
+ if (cnt <= 0) break;
+ if ((rc=rfc2045_cdecode(rfc, buf, cnt)) != 0)
+ return (rc);
+ start_body += cnt;
+ }
+ return (rfc2045_cdecode_end(rfc));
+}
diff --git a/rfc2045/rfc2045decodemimesectionu.c b/rfc2045/rfc2045decodemimesectionu.c
new file mode 100644
index 0000000..3711b1e
--- /dev/null
+++ b/rfc2045/rfc2045decodemimesectionu.c
@@ -0,0 +1,68 @@
+/*
+** Copyright 2000-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc2045.h"
+#include "../unicode/unicode.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <iconv.h>
+#include <errno.h>
+
+/*
+** Call rfc2045_decodemimesection, expecting textual content. Convert
+** textual content to local character set, if possible. This is implemented
+** by saving the real callback function, then calling rfc2045_decodemimesection
+** and specifying our own callback function, which does the conversion, then
+** calls the original callback function. Neat, eh?
+*/
+
+static int myhandler(const char *, size_t, void *);
+
+int rfc2045_decodetextmimesection(struct rfc2045src *src,
+ struct rfc2045 *rfc,
+ const char *mychset,
+ int *conv_err,
+ int (*handler)(const char *,
+ size_t, void *),
+ void *voidarg)
+{
+ const char *dummy;
+ const char *src_chset;
+
+ libmail_u_convert_handle_t ci;
+ int rc;
+ int dummy_flag;
+
+ if (!conv_err)
+ conv_err= &dummy_flag;
+
+ rfc2045_mimeinfo(rfc, &dummy, &dummy, &src_chset);
+
+ *conv_err=0;
+
+ if ((ci=libmail_u_convert_init(src_chset, mychset, handler, voidarg))
+ == NULL)
+ {
+ *conv_err=1;
+ return -1;
+ }
+
+ rc=rfc2045_decodemimesection(src, rfc, &myhandler, &ci);
+
+ dummy_flag=0;
+ if (libmail_u_convert_deinit(ci, &dummy_flag))
+ rc= -1;
+
+ if (dummy_flag)
+ *conv_err=1;
+ return (rc);
+}
+
+static int myhandler(const char *cp, size_t cnt, void *voidarg)
+{
+ return libmail_u_convert(*(libmail_u_convert_handle_t *)
+ voidarg, cp, cnt);
+}
diff --git a/rfc2045/rfc2045decodemsgtoutf8.c b/rfc2045/rfc2045decodemsgtoutf8.c
new file mode 100644
index 0000000..e3c5fe8
--- /dev/null
+++ b/rfc2045/rfc2045decodemsgtoutf8.c
@@ -0,0 +1,148 @@
+/*
+** Copyright 2010-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045.h"
+#include "rfc822/rfc822.h"
+#include <ctype.h>
+#include <unistd.h>
+#include <string.h>
+
+
+
+struct doconvtoutf8_info {
+
+ struct rfc2045_decodemsgtoutf8_cb *callback;
+
+ int err_flag;
+};
+
+static void doconvtoutf8_write(const char *p, size_t n,
+ void *void_arg)
+{
+ struct doconvtoutf8_info *ptr=
+ (struct doconvtoutf8_info *)void_arg;
+
+ if (n && ptr->err_flag == 0)
+ ptr->err_flag=(*ptr->callback->output_func)
+ (p, n, ptr->callback->arg);
+}
+
+static void doconvtoutf8_error(const char *p, int n,
+ void *void_arg)
+{
+ struct doconvtoutf8_info *ptr=(struct doconvtoutf8_info *)void_arg;
+
+ ptr->err_flag= -1;
+}
+
+static int doconvtoutf8_rfc822hdr(const char *header,
+ const char *value,
+ struct rfc2045_decodemsgtoutf8_cb *callback)
+{
+ struct doconvtoutf8_info info;
+
+ info.err_flag=0;
+ info.callback=callback;
+
+ if (callback->headerfilter_func &&
+ (*callback->headerfilter_func)(header, value, callback->arg) == 0)
+ return 0;
+
+ if ((callback->flags & RFC2045_DECODEMSG_NOHEADERNAME) == 0)
+ {
+ doconvtoutf8_write(header, strlen(header), &info);
+ doconvtoutf8_write(": ", 2, &info);
+ }
+ rfc822_display_hdrvalue(header, value, "utf-8",
+ doconvtoutf8_write,
+ doconvtoutf8_error,
+ &info);
+ doconvtoutf8_write("\n", 1, &info);
+ if (callback->headerdone_func && info.err_flag == 0)
+ {
+ int rc=(*callback->headerdone_func)(header, callback->arg);
+
+ if (rc)
+ info.err_flag=rc;
+ }
+
+ return info.err_flag;
+}
+
+static int decode_handler(const char *p, size_t n, void *voidarg)
+{
+ const struct doconvtoutf8_info *ptr=
+ (const struct doconvtoutf8_info *)voidarg;
+
+ int rc=0;
+
+ if (n)
+ rc=(*ptr->callback->output_func)(p, n, ptr->callback->arg);
+
+ return rc;
+}
+
+int rfc2045_decodemsgtoutf8(struct rfc2045src *src,
+ struct rfc2045 *p,
+ struct rfc2045_decodemsgtoutf8_cb *callback)
+{
+ struct rfc2045headerinfo *hi;
+ int rc;
+
+ hi=rfc2045header_start(src, p);
+
+ if (hi)
+ {
+ char *header;
+ char *value;
+
+ while (rfc2045header_get(hi, &header, &value,
+ RFC2045H_NOLC |
+ RFC2045H_KEEPNL) == 0 && header)
+ {
+ if (callback->flags & RFC2045_DECODEMSG_NOHEADERS)
+ continue;
+
+ if (doconvtoutf8_rfc822hdr(header, value,
+ callback) < 0)
+ return -1;
+ }
+ rfc2045header_end(hi);
+ }
+
+ if (p->firstpart)
+ {
+ for (p=p->firstpart; p; p=p->next)
+ {
+ if (!p->isdummy)
+ {
+ if ((rc=rfc2045_decodemsgtoutf8(src, p,
+ callback))
+ != 0)
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ const char *content_type;
+ const char *transfer_encoding;
+ const char *charset;
+ struct doconvtoutf8_info info;
+
+ info.callback=callback;
+
+ rfc2045_mimeinfo(p, &content_type, &transfer_encoding,
+ &charset);
+
+ if (strncmp(content_type, "text/", 5) == 0 &&
+ (callback->flags & RFC2045_DECODEMSG_NOBODY) == 0 &&
+ (rc=rfc2045_decodetextmimesection(src, p, "utf-8", NULL,
+ decode_handler,
+ &info)) != 0)
+ return rc;
+ }
+ return 0;
+}
diff --git a/rfc2045/rfc2045enomem.c b/rfc2045/rfc2045enomem.c
new file mode 100644
index 0000000..e8d100f
--- /dev/null
+++ b/rfc2045/rfc2045enomem.c
@@ -0,0 +1,9 @@
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include "rfc2045.h"
+
+void rfc2045_enomem()
+{
+ rfc2045_error("Out of memory.");
+}
diff --git a/rfc2045/rfc2045find.c b/rfc2045/rfc2045find.c
new file mode 100644
index 0000000..b1368ad
--- /dev/null
+++ b/rfc2045/rfc2045find.c
@@ -0,0 +1,49 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#include "rfc2045.h"
+#include <ctype.h>
+
+struct rfc2045findstruct {
+
+ const char *partnum;
+ struct rfc2045 *ptr;
+ } ;
+
+static void do_decode(struct rfc2045 *p, struct rfc2045id *id, void *ptr)
+{
+struct rfc2045findstruct *fs=(struct rfc2045findstruct *)ptr;
+const char *partnum=fs->partnum;
+unsigned n;
+
+ while (id)
+ {
+ if (!isdigit((int)(unsigned char)*partnum)) return;
+ n=0;
+ while (isdigit((int)(unsigned char)*partnum))
+ n=n*10 + *partnum++ - '0';
+ if (*partnum)
+ {
+ if (*partnum != '.') return;
+ ++partnum;
+ }
+ if (n != (unsigned)id->idnum) return;
+ id=id->next;
+ }
+ if ( *partnum == '\0') fs->ptr=p;
+}
+
+
+struct rfc2045 *rfc2045_find(struct rfc2045 *p, const char *str)
+{
+struct rfc2045findstruct fs;
+
+ fs.partnum=str;
+ fs.ptr=0;
+ rfc2045_decode(p, &do_decode, &fs);
+ return (fs.ptr);
+}
diff --git a/rfc2045/rfc2045header.c b/rfc2045/rfc2045header.c
new file mode 100644
index 0000000..a4b64f9
--- /dev/null
+++ b/rfc2045/rfc2045header.c
@@ -0,0 +1,287 @@
+/*
+** Copyright 2000-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc2045.h"
+#include "rfc2045src.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+struct rfc2045src_fd {
+ struct rfc2045src src;
+ int fd;
+};
+
+static void deinit_func_fd(void *);
+
+static int seek_func_fd(int64_t pos, void *);
+
+static ssize_t read_func_fd(char *buf, size_t cnt, void *);
+
+struct rfc2045src *rfc2045src_init_fd(int fd)
+{
+ struct rfc2045src_fd *ptr=malloc(sizeof(struct rfc2045src_fd));
+
+ if (!ptr)
+ return NULL;
+
+ memset(ptr, 0, sizeof(*ptr));
+
+ ptr->src.arg=ptr;
+ ptr->src.deinit_func=deinit_func_fd;
+ ptr->src.seek_func=seek_func_fd;
+ ptr->src.read_func=read_func_fd;
+ ptr->fd=fd;
+ return &ptr->src;
+}
+
+void rfc2045src_deinit(struct rfc2045src *ptr)
+{
+ (*ptr->deinit_func)(ptr->arg);
+}
+
+static void deinit_func_fd(void *ptr)
+{
+ struct rfc2045src_fd *s=(struct rfc2045src_fd *)ptr;
+
+ free(s);
+}
+
+static int seek_func_fd(off_t pos, void *ptr)
+{
+ struct rfc2045src_fd *s=(struct rfc2045src_fd *)ptr;
+
+ if (lseek(s->fd, pos, SEEK_SET) < 0)
+ return -1;
+
+ return 0;
+}
+
+static ssize_t read_func_fd(char *buf, size_t cnt, void *ptr)
+{
+ struct rfc2045src_fd *s=(struct rfc2045src_fd *)ptr;
+
+ return read(s->fd, buf, cnt);
+}
+
+
+struct rfc2045headerinfo {
+
+ struct rfc2045src *src;
+
+ char *headerbuf;
+ size_t headerbufsize;
+
+ char readbuf[1024];
+
+ char *readptr;
+ size_t readleft;
+
+ size_t headerleft;
+ int firstheader;
+} ;
+
+struct rfc2045headerinfo *rfc2045header_start(struct rfc2045src *src,
+ struct rfc2045 *rfcp)
+{
+ off_t start_pos, dummy, start_body;
+ int firstheader;
+ struct rfc2045headerinfo *p;
+
+ if (rfcp)
+ {
+ rfc2045_mimepos(rfcp, &start_pos, &dummy, &start_body, &dummy,
+ &dummy);
+ firstheader=0;
+ }
+ else
+ {
+ start_pos=0;
+ start_body=0;
+ firstheader=1;
+ }
+
+ if (SRC_SEEK(src, start_pos) < 0)
+ return NULL;
+
+ p=(struct rfc2045headerinfo *)calloc(sizeof(struct rfc2045headerinfo),
+ 1);
+
+ if (!p)
+ return (NULL);
+ p->src=src;
+
+ p->headerleft=start_body - start_pos;
+ p->firstheader=firstheader;
+ return (p);
+}
+
+void rfc2045header_end(struct rfc2045headerinfo *p)
+{
+ if (p->headerbuf)
+ free(p->headerbuf);
+ free(p);
+}
+
+static int fill(struct rfc2045headerinfo *p)
+{
+ ssize_t n;
+
+ n=sizeof(p->readbuf);
+
+ if (p->firstheader == 0)
+ {
+ if (p->headerleft == 0)
+ return (-1);
+
+
+ if (n > p->headerleft)
+ n=p->headerleft;
+ }
+
+ n=SRC_READ(p->src, p->readbuf, n);
+
+ if (n <= 0)
+ {
+ p->headerleft=0;
+ p->readleft=0;
+ return (-1);
+ }
+ p->readptr=p->readbuf;
+ p->readleft = n;
+
+ if (p->firstheader == 0)
+ p->headerleft -= n;
+
+ return ((int)(unsigned char)p->readbuf[0]);
+}
+
+#define PEEK(p) ((p)->readleft ? (int)(unsigned char)*p->readptr:fill(p))
+
+
+int rfc2045header_get(struct rfc2045headerinfo *p, char **header,
+ char **value,
+ int flags)
+{
+ int c=PEEK(p);
+ int isnl=0;
+ size_t n=0;
+ char *s, *t;
+ int eatspace=0;
+
+ if (c == -1 || c == '\r' || c == '\n')
+ {
+ *header=*value=NULL;
+ return (0);
+ }
+
+ for (;;)
+ {
+ if (n >= p->headerbufsize)
+ {
+ size_t n=p->headerbufsize += 256;
+ char *s= p->headerbuf ?
+ realloc(p->headerbuf, n):
+ malloc(n);
+
+ if (!s)
+ return (-1);
+ p->headerbuf=s;
+ p->headerbufsize=n;
+ }
+
+ c=PEEK(p);
+ if (c < 0)
+ break;
+
+ if (c == '\r')
+ {
+ --p->readleft;
+ ++p->readptr;
+ continue;
+ }
+
+ if (isnl) /* Last char was newline */
+ {
+ if (!isspace((int)(unsigned char)c) || c == '\n')
+ break;
+
+ isnl=0;
+
+ if ((flags & RFC2045H_KEEPNL) == 0)
+ eatspace=1; /* Fold headers */
+ }
+
+ if (c == '\n')
+ isnl=1;
+
+ if (eatspace)
+ {
+ if (c != '\n' && isspace((int)(unsigned char)c))
+ {
+ --p->readleft;
+ ++p->readptr;
+ continue;
+ }
+ eatspace=0;
+ }
+
+ if (c == '\n' && (flags & RFC2045H_KEEPNL) == 0)
+ c=' ';
+
+ p->headerbuf[n++]=c;
+ --p->readleft;
+ ++p->readptr;
+ }
+
+ while (n > 0 && p->headerbuf[n-1] == ' ')
+ --n;
+
+ p->headerbuf[n]=0;
+
+ *header= *value= p->headerbuf;
+
+ while (**value)
+ {
+ if (**value == ':')
+ {
+ **value=0;
+ ++*value;
+
+ while (**value && isspace((int)(unsigned char)**value))
+ ++*value;
+ break;
+ }
+
+ if (!(flags & RFC2045H_NOLC))
+ {
+ if (**value >= 'A' && **value <= 'Z')
+ **value += 'a' - 'A';
+ }
+ ++*value;
+ }
+
+ s=strrchr( *value, '\n');
+
+ if (s && *s && s[1] == 0)
+ *s=0;
+
+ s=strrchr( *value, '\r');
+
+ if (s && *s && s[1] == 0)
+ *s=0;
+
+ for (s=t=*value; *s; )
+ if (!isspace((int)(unsigned char)*s++))
+ t=s;
+ *t=0;
+ return (0);
+}
diff --git a/rfc2045/rfc2045mkboundary.c b/rfc2045/rfc2045mkboundary.c
new file mode 100644
index 0000000..2d7c159
--- /dev/null
+++ b/rfc2045/rfc2045mkboundary.c
@@ -0,0 +1,72 @@
+/*
+** Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include "rfc2045.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "numlib/numlib.h"
+
+
+#if HAS_GETHOSTNAME
+
+#else
+
+extern int gethostname(char *, size_t);
+#endif
+
+extern void rfc2045_enomem();
+
+char *rfc2045_mk_boundary(struct rfc2045 *s, struct rfc2045src *src)
+{
+char hostnamebuf[256];
+pid_t mypid;
+char pidbuf[NUMBUFSIZE];
+time_t mytime;
+char timebuf[NUMBUFSIZE];
+static size_t cnt=0;
+char cntbuf[NUMBUFSIZE];
+char *p;
+int rc;
+
+ hostnamebuf[sizeof(hostnamebuf)-1]=0;
+ if (gethostname(hostnamebuf, sizeof(hostnamebuf)-1))
+ hostnamebuf[0]=0;
+ mypid=getpid();
+ time(&mytime);
+ libmail_str_pid_t(mypid, pidbuf);
+ libmail_str_time_t(mytime, timebuf);
+ for (;;)
+ {
+ char tempbuf[NUMBUFSIZE];
+
+ libmail_str_size_t(++cnt, tempbuf);
+ sprintf(cntbuf, "%4s", tempbuf);
+ for (p=cntbuf; *p == ' '; *p++ = '0')
+ ;
+ p=malloc(strlen(hostnamebuf)+strlen(pidbuf)
+ +strlen(timebuf)+strlen(cntbuf)+11);
+ if (!p)
+ {
+ rfc2045_enomem();
+ return (NULL);
+ }
+
+ sprintf(p, "=_%-1.30s-%s-%s-%s", hostnamebuf,
+ pidbuf, timebuf, cntbuf);
+ if ((rc=rfc2045_try_boundary(s, src, p)) == 0)
+ break;
+ free(p);
+ if (rc < 0)
+ return (NULL);
+ }
+ return (p);
+}
diff --git a/rfc2045/rfc2045reply.c b/rfc2045/rfc2045reply.c
new file mode 100644
index 0000000..0692fb0
--- /dev/null
+++ b/rfc2045/rfc2045reply.c
@@ -0,0 +1,1667 @@
+/*
+** Copyright 2000-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc2045.h"
+#include "rfc2045src.h"
+#include "rfc3676parser.h"
+#include "rfc822/rfc2047.h"
+#include "rfc2045charset.h"
+#include "rfc822/rfc822.h"
+#include "unicode/unicode.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+
+extern void rfc2045_enomem();
+
+static int mkreply(struct rfc2045_mkreplyinfo *);
+static int mkforward(struct rfc2045_mkreplyinfo *);
+
+static void mksalutation_datefmt(const char *fmt_start,
+ const char *fmt_end,
+ const char *date,
+ void (*callback_func)(const char *,
+ size_t, void *),
+ void *callback_arg)
+{
+ time_t t;
+
+ if (!fmt_start)
+ {
+ fmt_start="%a, %d %b %Y %H:%M:%S %z";
+ fmt_end=fmt_start + strlen(fmt_start);
+ }
+
+ if ((t=rfc822_parsedt(date)))
+ {
+ struct tm tmbuf;
+
+ if (localtime_r(&t, &tmbuf))
+ {
+ char *fmtstr=malloc(fmt_end-fmt_start + 1);
+
+ if (fmtstr)
+ {
+ char fmtbuf[1024];
+
+ memcpy(fmtstr, fmt_start, fmt_end - fmt_start);
+ fmtstr[fmt_end-fmt_start]=0;
+
+ fmtbuf[strftime(fmtbuf,
+ sizeof(fmtbuf)-1,
+ fmtstr, &tmbuf)]=0;
+
+ free(fmtstr);
+ (*callback_func)(fmtbuf, strlen(fmtbuf),
+ callback_arg);
+ return;
+ }
+ }
+ }
+ (*callback_func)(date, strlen(date), callback_arg);
+}
+
+static void mksalutation_cb(const char *salutation_template,
+ const char *newsgroup,
+ const char *message_id,
+ const char *newsgroups,
+ const char *sender_addr,
+ const char *sender_name,
+ const char *date,
+ const char *subject,
+
+ void (*callback_func)(const char *, size_t, void *),
+ void *callback_arg)
+{
+ const char *p;
+
+ for (p=salutation_template; *p; p++)
+ {
+ const char *fmt_start=0, *fmt_end=0;
+
+ if (*p != '%' || p[1] == '\0')
+ {
+ (*callback_func)( p, 1, callback_arg );
+ continue;
+ }
+
+ ++p;
+
+ if (*p == '{')
+ {
+ fmt_start= ++p;
+
+ while (*p)
+ {
+ if (*p == '}')
+ {
+ fmt_end=p;
+ ++p;
+ break;
+ }
+ }
+
+ if (!fmt_end)
+ continue;
+ }
+
+#define CBSTR(s) s, strlen(s), callback_arg
+
+ switch (*p) {
+ case 'n':
+ (*callback_func)("\n", 1, callback_arg );
+ continue;
+ case 'C':
+ (*callback_func)(CBSTR(newsgroup));
+ break;
+ case 'i':
+ (*callback_func)(CBSTR(message_id));
+ break;
+ case 'N':
+ (*callback_func)(CBSTR(newsgroups));
+ break;
+ case 'f':
+ (*callback_func)(CBSTR(sender_addr));
+ break;
+ case 'F':
+ (*callback_func)(CBSTR(sender_name));
+ break;
+ case 'd':
+ mksalutation_datefmt(fmt_start,
+ fmt_end,
+ date,
+ callback_func, callback_arg);
+ break;
+ case 's':
+ (*callback_func)(CBSTR(subject));
+ break;
+ default:
+ (*callback_func)(p, 1, callback_arg);
+ break;
+ }
+#undef CBSTR
+
+ }
+}
+
+static void mksal_count(const char *str,
+ size_t cnt,
+ void *arg)
+{
+ *(size_t *)arg += cnt;
+}
+
+static void mksal_save(const char *str,
+ size_t cnt,
+ void *arg)
+{
+ if (cnt)
+ memcpy(*(char **)arg, str, cnt);
+
+ *(char **)arg += cnt;
+}
+
+static char *mksalutation(const char *salutation_template,
+ const char *newsgroup,
+ const char *message_id,
+ const char *newsgroups,
+ const char *sender_addr,
+ const char *sender_name,
+ const char *date,
+ const char *subject,
+ const char *charset)
+{
+ size_t cnt;
+ char *p, *q;
+
+ char *subj_decoded=rfc822_display_hdrvalue_tobuf("subject", subject,
+ charset, NULL, NULL);
+
+ if (!subj_decoded)
+ return NULL;
+
+ cnt=1;
+
+ mksalutation_cb(salutation_template,
+ newsgroup,
+ message_id,
+ newsgroups,
+ sender_addr,
+ sender_name,
+ date,
+ subj_decoded,
+ mksal_count, &cnt);
+
+ p=q=malloc(cnt);
+
+ if (!p)
+ {
+ free(subj_decoded);
+ return NULL;
+ }
+
+ mksalutation_cb(salutation_template,
+ newsgroup,
+ message_id,
+ newsgroups,
+ sender_addr,
+ sender_name,
+ date,
+ subj_decoded,
+ mksal_save, &q);
+ *q=0;
+
+ free(subj_decoded);
+ return p;
+}
+
+
+int rfc2045_makereply(struct rfc2045_mkreplyinfo *ri)
+{
+ if (strcmp(ri->replymode, "forward") == 0
+ || strcmp(ri->replymode, "forwardatt") == 0)
+ return (mkforward(ri));
+
+ return (mkreply(ri));
+}
+
+struct replyinfostruct {
+
+ struct rfc2045_mkreplyinfo *ri;
+ rfc3676_parser_t parser;
+
+ size_t quote_level_adjust;
+ size_t quote_level;
+ int start_line;
+ int isflowed;
+ size_t trailing_spaces;
+ libmail_u_convert_handle_t u_handle;
+
+};
+
+/*
+** Pass original content to the RFC 3676 parser
+*/
+
+static int quotereply(const char *p, size_t l, void *voidptr)
+{
+ struct replyinfostruct *ris=(struct replyinfostruct *)voidptr;
+
+ return rfc3676parser(ris->parser, p, l);
+}
+
+/*
+** Push formatted reply downstream.
+*/
+
+static int output_reply(const char *ptr, size_t cnt, void *arg)
+{
+ struct replyinfostruct *s=(struct replyinfostruct *)arg;
+
+ (*s->ri->write_func)(ptr, cnt, s->ri->voidarg);
+ return 0;
+}
+
+/*
+** RFC 3676 parser: Start of a new line in the reply.
+*/
+static int reply_begin(size_t quote_level,
+ void *arg)
+{
+ struct replyinfostruct *s=(struct replyinfostruct *)arg;
+ unicode_char quoteChar='>';
+
+ /*
+ ** Save quote level, begin conversion from unicode to the native
+ ** charset.
+ */
+ s->quote_level=quote_level+s->quote_level_adjust;
+
+ s->u_handle=libmail_u_convert_init(libmail_u_ucs4_native,
+ s->ri->charset,
+ output_reply,
+ s);
+
+ /*
+ ** Emit quoting indentation, if any.
+ */
+ s->start_line=1;
+ s->trailing_spaces=0;
+
+ if (s->u_handle)
+ {
+ size_t i;
+
+ for (i=0; i<s->quote_level; i++)
+ {
+ libmail_u_convert_uc(s->u_handle, &quoteChar, 1);
+ }
+ }
+ return 0;
+}
+
+/*
+** RFC 3676: (possibly partial) contents of a deflowed line, as unicode.
+*/
+
+static int reply_contents(const unicode_char *txt,
+ size_t txt_size,
+ void *arg)
+{
+ unicode_char spaceChar=' ';
+ size_t nonspc_cnt;
+
+ struct replyinfostruct *s=(struct replyinfostruct *)arg;
+
+ if (!s->u_handle || txt_size == 0)
+ return 0;
+
+ /*
+ ** Space-stuff the initial character.
+ */
+
+ if (s->start_line)
+ {
+ if (!s->isflowed)
+ {
+ /*
+ ** If the original content is not flowed, the rfc3676
+ ** parser does not parse the number of > quote
+ ** characters and does not set the quote level.
+ */
+
+ if ((s->quote_level > 0 && *txt != '>') || *txt == ' ')
+ libmail_u_convert_uc(s->u_handle,
+ &spaceChar, 1);
+
+ }
+ else
+ {
+ if (s->quote_level > 0 || *txt == ' ' || *txt == '>')
+ libmail_u_convert_uc(s->u_handle,
+ &spaceChar, 1);
+ }
+ s->start_line=0;
+ }
+
+ /*
+ ** Trim any trailing spaces from the RFC 3676 parsed content.
+ */
+
+ for (nonspc_cnt=txt_size; nonspc_cnt > 0; --nonspc_cnt)
+ if (txt[nonspc_cnt-1] != ' ')
+ break;
+
+ /*
+ ** If the contents are not totally whitespace, it's ok now to emit
+ ** any accumulated whitespace from previous content.
+ */
+
+ if (nonspc_cnt)
+ {
+ while (s->trailing_spaces)
+ {
+ libmail_u_convert_uc(s->u_handle, &spaceChar, 1);
+ --s->trailing_spaces;
+ }
+
+ libmail_u_convert_uc(s->u_handle, txt, nonspc_cnt);
+ }
+
+ s->trailing_spaces += txt_size - nonspc_cnt;
+ return 0;
+}
+
+static int reply_end(void *arg)
+{
+ unicode_char newLine='\n';
+ struct replyinfostruct *s=(struct replyinfostruct *)arg;
+
+ libmail_u_convert_uc(s->u_handle, &newLine, 1);
+
+ libmail_u_convert_deinit(s->u_handle, NULL);
+ return 0;
+}
+
+/*
+** RFC 3676 parser: flowed line break. Replicate it.
+*/
+static int reply_wrap(void *arg)
+{
+ unicode_char spaceChar=' ';
+ struct replyinfostruct *s=(struct replyinfostruct *)arg;
+
+ /*
+ ** It's safe to preserve trailing spaces on flowed lines.
+ */
+
+ while (s->trailing_spaces)
+ {
+ libmail_u_convert_uc(s->u_handle, &spaceChar, 1);
+ --s->trailing_spaces;
+ }
+
+ libmail_u_convert_uc(s->u_handle, &spaceChar, 1);
+ reply_end(s);
+ reply_begin(s->quote_level-s->quote_level_adjust, s);
+ /* Undo the adjustment in reply_begin() */
+
+ return 0;
+}
+
+static void reformat(struct rfc2045_mkreplyinfo *ri, struct rfc2045 *rfc,
+ size_t adjustLevel)
+{
+ struct replyinfostruct ris;
+
+ struct rfc3676_parser_info info;
+ int conv_err;
+
+ ris.ri=ri;
+ ris.quote_level_adjust=adjustLevel;
+
+ memset(&info, 0, sizeof(info));
+
+ info.charset=ri->charset;
+ ris.isflowed=info.isflowed=rfc2045_isflowed(rfc);
+ info.isdelsp=rfc2045_isdelsp(rfc);
+
+ info.line_begin=reply_begin;
+ info.line_contents=reply_contents;
+ info.line_flowed_notify=reply_wrap;
+ info.line_end=reply_end;
+ info.arg=&ris;
+
+ if ((ris.parser=rfc3676parser_init(&info)) != NULL)
+ {
+ rfc2045_decodetextmimesection(ri->src, rfc,
+ ri->charset,
+ &conv_err,
+ quotereply,
+ &ris);
+ rfc3676parser_deinit(ris.parser, NULL);
+ }
+}
+
+static void replybody(struct rfc2045_mkreplyinfo *ri, struct rfc2045 *rfc)
+{
+ rfc=rfc2045_searchcontenttype(rfc, "text/plain");
+
+ if (!rfc)
+ return;
+
+ reformat(ri, rfc, 1);
+}
+
+static void writes(struct rfc2045_mkreplyinfo *ri, const char *c)
+{
+ (*ri->write_func)(c, strlen(c), ri->voidarg);
+}
+
+static void forwardbody(struct rfc2045_mkreplyinfo *ri, long nbytes)
+{
+ char buf[BUFSIZ];
+ ssize_t i;
+
+ while ((i=nbytes > sizeof(buf) ? sizeof(buf):nbytes) > 0 &&
+ (i=SRC_READ(ri->src, buf, i)) > 0)
+ {
+ nbytes -= i;
+ (*ri->write_func)(buf, i, ri->voidarg);
+ }
+}
+
+/*
+** Format a forward
+*/
+static int mkforward(struct rfc2045_mkreplyinfo *ri)
+{
+ off_t start_pos, end_pos, start_body;
+ off_t dummy;
+
+ char *header, *value;
+ char *subject=0;
+
+ char *boundary=0;
+
+ struct rfc2045headerinfo *hi;
+
+ struct rfc2045 *textplain_content;
+ struct rfc2045 *first_attachment;
+ int attachment_is_message_rfc822;
+
+ /*
+ ** Use the original message's subject to set the subject of the
+ ** forward message.
+ */
+
+ hi=rfc2045header_start(ri->src, ri->rfc2045partp);
+
+ if (!hi)
+ return (-1);
+
+ for (;;)
+ {
+ if (rfc2045header_get(hi, &header, &value, 0))
+ {
+ rfc2045header_end(hi);
+ return (-1);
+ }
+
+ if (!header)
+ break;
+ if (strcmp(header, "subject") == 0)
+ {
+ if (subject) free(subject);
+
+ subject=strdup(value);
+ if (!subject)
+ {
+ rfc2045header_end(hi);
+ return (-1);
+ }
+ }
+ }
+
+ rfc2045header_end(hi);
+
+ writes(ri, "Subject: ");
+
+ if (ri->subject)
+ {
+ /*
+ ** ... unless the caller overrides it.
+ */
+
+ writes(ri, ri->subject);
+ }
+ else if (subject)
+ {
+ char *s=rfc822_coresubj_keepblobs(subject);
+
+ if (!s)
+ return (-1);
+
+ writes(ri, s);
+ free(s);
+ writes(ri, " (fwd)");
+ }
+ writes(ri, "\nMime-Version: 1.0\n");
+
+ /*
+ ** To assemble a forward template, two things are needed:
+ **
+ ** 1. The original message, as text/plain.
+ **
+ ** 2. Any attachments in the original message.
+ ** A. The attachments get either copied to the forward message, or
+ ** B. The original message is attached as a single message/rfc822
+ ** entity.
+ **
+ ** 2b is always produced by "forwardatt". If a suitable text/plain
+ ** part of the original message could not be found, 2b is also
+ ** produced even by "forward".
+ */
+
+ textplain_content=NULL;
+
+ attachment_is_message_rfc822=0;
+ first_attachment=NULL;
+
+ {
+ const char *content_type, *dummy;
+
+ struct rfc2045 *top_part=ri->rfc2045partp;
+
+ rfc2045_mimeinfo(top_part,
+ &content_type, &dummy, &dummy);
+
+ if (strcmp(content_type, "multipart/signed") == 0)
+ {
+ struct rfc2045 *p=top_part->firstpart;
+
+ if (p && p->isdummy)
+ p=p->next;
+
+ if (p)
+ {
+ top_part=p;
+ rfc2045_mimeinfo(top_part,
+ &content_type, &dummy, &dummy);
+ }
+ }
+ else if (strcmp(content_type, "multipart/x-mimegpg") == 0)
+ {
+ struct rfc2045 *p=top_part->firstpart;
+
+ if (p && p->isdummy)
+ p=p->next;
+
+ if (p)
+ {
+ const char *part_ct;
+
+ rfc2045_mimeinfo(p,
+ &part_ct, &dummy, &dummy);
+
+ if (strcmp(part_ct, "text/x-gpg-output") == 0
+ && p->next)
+ {
+ top_part=p->next;
+ rfc2045_mimeinfo(top_part,
+ &content_type,
+ &dummy, &dummy);
+ }
+ }
+ }
+
+ if (strcmp(content_type, "text/plain") == 0)
+ {
+ textplain_content=top_part;
+ }
+ else if (strcmp(content_type, "multipart/alternative") == 0)
+ {
+ textplain_content=
+ rfc2045_searchcontenttype(top_part,
+ "text/plain");
+ }
+ else if (strcmp(content_type, "multipart/mixed") == 0)
+ {
+ struct rfc2045 *p=top_part->firstpart;
+
+ if (p->isdummy)
+ p=p->next;
+
+ textplain_content=
+ rfc2045_searchcontenttype(p, "text/plain");
+
+ /*
+ ** If the first part contained a suitable text/plain,
+ ** any remaining MIME parts become attachments that
+ ** get copied to the forward message.
+ */
+ if (textplain_content)
+ first_attachment=p->next;
+ }
+
+ if (strcmp(ri->replymode, "forwardatt") == 0 ||
+ textplain_content == NULL)
+ {
+ /*
+ ** Copy the entire message as the sole message/rfc822
+ ** attachment in the forward message.
+ */
+ textplain_content=NULL;
+ first_attachment=top_part;
+ attachment_is_message_rfc822=1;
+ }
+ }
+
+ boundary=strdup(rfc2045_mk_boundary(ri->rfc2045partp, ri->src));
+ if (!boundary)
+ {
+ if (subject) free(subject);
+ return (-1);
+ }
+
+ if (first_attachment)
+ {
+ writes(ri, "Content-Type: multipart/mixed; boundary=\"");
+ writes(ri, boundary);
+ writes(ri, "\"\n\n");
+ writes(ri, RFC2045MIMEMSG);
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "\n");
+ }
+
+ if (ri->content_set_charset)
+ {
+ (*ri->content_set_charset)(ri->voidarg);
+ }
+ else
+ {
+ writes(ri, "Content-Type: text/plain; format=flowed; delsp=yes; charset=\"");
+ writes(ri, ri->charset);
+ writes(ri, "\"\n");
+ }
+
+ writes(ri, "Content-Transfer-Encoding: 8bit\n\n");
+ if (ri->content_specify)
+ (*ri->content_specify)(ri->voidarg);
+
+ writes(ri, "\n");
+ if (ri->writesig_func)
+ (*ri->writesig_func)(ri->voidarg);
+ writes(ri, "\n");
+
+ if (ri->forwardsep)
+ {
+ writes(ri, ri->forwardsep);
+ writes(ri, "\n");
+ }
+
+ if (textplain_content)
+ {
+ /* Copy original headers. */
+
+ hi=rfc2045header_start(ri->src, ri->rfc2045partp);
+ for (;;)
+ {
+ if (rfc2045header_get(hi, &header, &value,
+ RFC2045H_NOLC|RFC2045H_KEEPNL))
+ {
+ rfc2045header_end(hi);
+ break;
+ }
+ if (!header)
+ break;
+ if (strcasecmp(header, "subject") == 0 ||
+ strcasecmp(header, "from") == 0 ||
+ strcasecmp(header, "to") == 0 ||
+ strcasecmp(header, "cc") == 0 ||
+ strcasecmp(header, "date") == 0 ||
+ strcasecmp(header, "message-id") == 0 ||
+ strcasecmp(header, "resent-from") == 0 ||
+ strcasecmp(header, "resent-to") == 0 ||
+ strcasecmp(header, "resent-cc") == 0 ||
+ strcasecmp(header, "resent-date") == 0 ||
+ strcasecmp(header, "resent-message-id") == 0)
+ {
+ if (subject) free(subject);
+
+ subject=rfc822_display_hdrvalue_tobuf(header,
+ value,
+ ri->charset,
+ NULL,
+ NULL);
+
+ if (subject)
+ {
+ (*ri->write_func)(header,
+ strlen(header),
+ ri->voidarg);
+ (*ri->write_func)(": ", 2, ri->voidarg);
+ (*ri->write_func)(subject,
+ strlen(subject),
+ ri->voidarg);
+ (*ri->write_func)("\n", 1, ri->voidarg);
+ }
+ }
+ }
+ rfc2045header_end(hi);
+ (*ri->write_func)("\n", 1, ri->voidarg);
+
+ reformat(ri, textplain_content, 0);
+ }
+
+ if (first_attachment)
+ {
+ /*
+ ** There are attachments to copy
+ */
+
+ if (attachment_is_message_rfc822)
+ {
+ /* Copy everything as a message/rfc822 */
+
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "\nContent-Type: message/rfc822\n");
+
+ if (ri->forwarddescr)
+ {
+ char *p=rfc2047_encode_str(ri->forwarddescr,
+ ri->charset ?
+ ri->charset
+ : "iso-8859-1",
+ rfc2047_qp_allow_any
+ );
+
+ writes(ri, "Content-Description: ");
+ writes(ri, p ? p:"");
+ free(p);
+ writes(ri, "\n");
+ }
+
+ writes(ri, "\n");
+
+ rfc2045_mimepos(first_attachment, &start_pos, &end_pos,
+ &start_body,
+ &dummy, &dummy);
+
+ if (SRC_SEEK(ri->src, start_pos) == (off_t)-1)
+ {
+ if (subject) free(subject);
+ free(boundary);
+ return -1;
+ }
+ forwardbody(ri, end_pos - start_pos);
+ }
+ else
+ {
+ /* Copy over individual attachments, one by one */
+
+ for (; first_attachment;
+ first_attachment=first_attachment->next)
+ {
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "\n");
+
+ rfc2045_mimepos(first_attachment, &start_pos,
+ &end_pos,
+ &start_body,
+ &dummy, &dummy);
+
+ if (SRC_SEEK(ri->src, start_pos) == (off_t)-1)
+ {
+ if (subject) free(subject);
+ free(boundary);
+ return -1;
+ }
+
+ forwardbody(ri, end_pos - start_pos);
+ }
+ }
+
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "--\n");
+ }
+
+ if (subject) free(subject);
+ free(boundary);
+ return (0);
+}
+
+
+static int writereferences(struct rfc2045_mkreplyinfo *ri,
+ const char *oldref, const char *oldmsgid)
+{
+char *buf=malloc((oldref ? strlen(oldref):0)
+ + (oldmsgid ? strlen(oldmsgid):0)+2);
+char *p, *q;
+struct rfc822t *tp;
+struct rfc822a *ap;
+int i;
+
+ if (!buf)
+ return (-1);
+
+ /* Create new references header */
+ *buf=0;
+ if (oldref) strcat(buf, oldref);
+ if (oldref && oldmsgid) strcat(buf, " ");
+ if (oldmsgid) strcat(buf, oldmsgid);
+
+ /* Do wrapping the RIGHT way, by
+ ** RFC822 parsing the References: header
+ */
+
+ if ((tp=rfc822t_alloc_new(buf, NULL, NULL)) == 0 ||
+ (ap=rfc822a_alloc(tp)) == 0)
+ {
+ free(buf);
+ if (tp)
+ rfc822t_free(tp);
+ return (-1);
+ }
+
+ /* Keep only the last 20 message IDs */
+
+ i=0;
+ if (ap->naddrs > 20) i=ap->naddrs-20;
+ p="";
+ while (i < ap->naddrs)
+ {
+ q=rfc822_gettok(ap->addrs[i].tokens);
+ if (!q)
+ {
+ rfc822a_free(ap);
+ rfc822t_free(tp);
+ free(buf);
+ return (-1);
+ }
+
+ writes(ri, p);
+ writes(ri, "<");
+ writes(ri, q);
+ writes(ri, ">\n");
+ p=" ";
+ free(q);
+ i++;
+ }
+ rfc822a_free(ap);
+ rfc822t_free(tp);
+ free(buf);
+ return (0);
+}
+
+static char *mlcheck(struct rfc2045_mkreplyinfo *ri, const char *);
+
+static int replydsn(struct rfc2045_mkreplyinfo *);
+static int replyfeedback(struct rfc2045_mkreplyinfo *);
+
+static int mkreply(struct rfc2045_mkreplyinfo *ri)
+{
+ char *oldtocc, *oldfrom, *oldreplyto, *oldtolist;
+ char *subject;
+ char *oldmsgid;
+ char *oldreferences;
+ char *oldenvelope;
+ char *header, *value;
+ char *date;
+ char *newsgroup;
+ char *newsgroups;
+
+ char *whowrote;
+ off_t start_pos, end_pos, start_body, dummy;
+ int errflag=0;
+ char *boundary;
+ char *dsn_report_type;
+ int (*dsn_report_gen)(struct rfc2045_mkreplyinfo *);
+
+ struct rfc2045headerinfo *hi;
+
+ oldtocc=0;
+ oldtolist=0;
+ oldfrom=0;
+ oldreplyto=0;
+ subject=0;
+ oldmsgid=0;
+ oldreferences=0;
+ oldenvelope=0;
+ whowrote=0;
+ newsgroup=0;
+ newsgroups=0;
+ date=0;
+
+ rfc2045_mimepos(ri->rfc2045partp, &start_pos, &end_pos, &start_body,
+ &dummy, &dummy);
+
+ hi=rfc2045header_start(ri->src, ri->rfc2045partp);
+
+ if (!hi)
+ return (-1);
+
+#define BLOWUP { \
+ if (whowrote) free(whowrote); \
+ if (subject) free(subject); \
+ if (oldmsgid) free(oldmsgid); \
+ if (oldreferences) free(oldreferences); \
+ if (oldtocc) free(oldtocc); \
+ if (oldtolist) free(oldtolist); \
+ if (oldfrom) free(oldfrom); \
+ if (oldreplyto) free(oldreplyto); \
+ if (oldenvelope) free(oldenvelope); \
+ if (newsgroup) free(newsgroup); \
+ if (newsgroups) free(newsgroups); \
+ if (date) free(date); \
+ rfc2045header_end(hi); \
+ return (-1); \
+ }
+
+ for (;;)
+ {
+ if (rfc2045header_get(hi, &header, &value, 0))
+ {
+ BLOWUP;
+ return (-1);
+ }
+
+ if (!header)
+ break;
+
+ if (strcmp(header, "subject") == 0)
+ {
+ if (subject) free(subject);
+
+ subject=strdup(value);
+ if (!subject)
+ BLOWUP;
+ }
+ else if (strcmp(header, "reply-to") == 0)
+ {
+ if (oldreplyto) free(oldreplyto);
+ oldreplyto=strdup(value);
+ if (!oldreplyto)
+ BLOWUP;
+ }
+ else if (strcmp(header, "from") == 0)
+ {
+ if (oldfrom) free(oldfrom);
+ oldfrom=strdup(value);
+ if (!oldfrom)
+ BLOWUP;
+ }
+ else if (strcmp(header, "message-id") == 0)
+ {
+ if (oldmsgid) free(oldmsgid);
+ oldmsgid=strdup(value);
+ if (!oldmsgid)
+ BLOWUP;
+ }
+ else if (strcmp(header, "references") == 0)
+ {
+ if (oldreferences) free(oldreferences);
+ oldreferences=strdup(value);
+ if (!oldreferences)
+ BLOWUP;
+ }
+ else if ((strcmp(header, "return-path") == 0 ||
+ strcmp(header, "errors-to") == 0) &&
+ ri->replytoenvelope)
+ {
+ if (oldenvelope) free(oldenvelope);
+ oldenvelope=strdup(value);
+ if (!oldenvelope)
+ BLOWUP;
+ }
+ else if (strcmp(header, "newsgroups") == 0)
+ {
+ if (newsgroups) free(newsgroups);
+ newsgroups=strdup(value);
+ if (!newsgroups)
+ BLOWUP;
+ }
+ else if (strcmp(header, "x-newsgroup") == 0)
+ {
+ if (newsgroup) free(newsgroup);
+ newsgroup=strdup(value);
+ if (!newsgroup)
+ BLOWUP;
+ }
+ else if (strcmp(header, "date") == 0)
+ {
+ if (date) free(date);
+ date=strdup(value);
+ if (!date)
+ BLOWUP;
+ }
+ else if ((strcmp(ri->replymode, "replyall") == 0
+ || strcmp(ri->replymode, "replylist") == 0) &&
+ (
+ strcmp(header, "to") == 0 ||
+ strcmp(header, "cc") == 0
+ )
+ )
+ {
+ char *newh=malloc( (oldtocc ?
+ strlen(oldtocc):0)
+ + strlen(value)+2);
+ char *p;
+
+ if (!newh)
+ BLOWUP;
+
+ *newh=0;
+ if (oldtocc)
+ strcat(strcpy(newh, oldtocc),
+ ",");
+ strcat(newh, value);
+ if (oldtocc) free(oldtocc);
+ oldtocc=newh;
+
+ p=mlcheck(ri, value);
+ if (!p || (newh=malloc((oldtolist ?
+ strlen(oldtolist):0)
+ + strlen(p)+2)) == NULL)
+ {
+ if (p)
+ free(p);
+ BLOWUP;
+ }
+
+ if (*p)
+ {
+ *newh=0;
+ if (oldtolist)
+ strcat(strcpy(newh, oldtolist),
+ ",");
+ strcat(newh, p);
+ if (oldtolist)
+ free(oldtolist);
+ oldtolist=newh;
+ }
+ free(p);
+ }
+ }
+
+ rfc2045header_end(hi);
+
+ /* Write: "%s writes:" */
+
+ {
+ struct rfc822t *rfcp=rfc822t_alloc_new(oldfrom ? oldfrom:"",
+ NULL, NULL);
+ struct rfc822a *rfcpa;
+ char *sender_name=NULL;
+ char *sender_addr=NULL;
+ int n;
+
+ if (!rfcp)
+ BLOWUP;
+
+ rfcpa=rfc822a_alloc(rfcp);
+ if (!rfcpa)
+ {
+ rfc822t_free(rfcp);
+ BLOWUP;
+ }
+
+ for (n=0; n<rfcpa->naddrs; ++n)
+ {
+ if (rfcpa->addrs[n].tokens == NULL)
+ continue;
+
+ sender_name=rfc822_display_name_tobuf(rfcpa, n,
+ ri->charset);
+ sender_addr=rfc822_display_addr_tobuf(rfcpa, n,
+ ri->charset);
+ break;
+ }
+
+ rfc822a_free(rfcpa);
+ rfc822t_free(rfcp);
+
+ whowrote=mksalutation(ri->replysalut,
+ newsgroup ? newsgroup:"",
+ oldmsgid ? oldmsgid:"",
+ newsgroups ? newsgroups:"",
+
+ sender_addr ? sender_addr:"(no address given)",
+ sender_name ? sender_name:sender_addr,
+ date,
+ subject,
+ ri->charset);
+
+ if (sender_name)
+ free(sender_name);
+ if (sender_addr)
+ free(sender_addr);
+
+ if (!whowrote)
+ {
+ BLOWUP;
+ }
+ }
+
+ if (newsgroups)
+ free(newsgroups);
+ if (newsgroup)
+ free(newsgroup);
+ if (date)
+ free(date);
+ if (oldreplyto)
+ {
+ if (oldfrom) free(oldfrom);
+ oldfrom=oldreplyto;
+ oldreplyto=0;
+ }
+
+ if (oldenvelope)
+ {
+ if (oldfrom) free(oldfrom);
+ oldfrom=oldenvelope;
+ oldenvelope=0;
+ }
+
+ /*
+ ** Replytolist: if we found mailing list addresses, drop
+ ** oldtocc, we'll use oldtolist.
+ ** Otherwise, drop oldtolist.
+ */
+
+ if (strcmp(ri->replymode, "replylist") == 0)
+ {
+ if (oldtolist)
+ {
+ if (oldtocc)
+ {
+ free(oldtocc);
+ oldtocc=0;
+ }
+
+ if (oldfrom)
+ {
+ free(oldfrom);
+ oldfrom=0;
+ }
+ }
+ }
+ else
+ {
+ if (oldtolist)
+ {
+ free(oldtolist);
+ oldtolist=NULL;
+ }
+ }
+
+ /* Remove duplicate entries from new Cc header */
+
+ if (oldtocc)
+ {
+ struct rfc822t *rfccc, *rfcto;
+ struct rfc822a *arfccc, *arfcto;
+ int i, j;
+ char *new_addresses;
+
+ rfccc=rfc822t_alloc_new(oldtocc, NULL, NULL);
+ rfcto= oldfrom ? rfc822t_alloc_new(oldfrom, NULL,
+ NULL):NULL;
+ arfccc=rfccc ? rfc822a_alloc(rfccc):NULL;
+ arfcto=rfcto ? rfc822a_alloc(rfcto):NULL;
+
+ for (i=0; arfccc && i <arfccc->naddrs; i++)
+ {
+ char *addr=rfc822_getaddr(arfccc, i);
+
+ if (!addr) continue;
+
+ /* Remove address from Cc if it is my address */
+
+ if ( (ri->myaddr_func)(addr, ri->voidarg))
+ {
+ rfc822_deladdr(arfccc, i); --i;
+ free(addr);
+ continue;
+ }
+
+ /* Remove address from Cc if it appears in To: */
+
+ for (j=0; arfcto && j < arfcto->naddrs; j++)
+ {
+ char *addr2=rfc822_getaddr(arfcto, j);
+
+ if (!addr2) continue;
+ if (strcmp(addr, addr2) == 0)
+ {
+ free(addr2);
+ break;
+ }
+ free(addr2);
+ }
+ if (arfcto && j < arfcto->naddrs)
+ {
+ rfc822_deladdr(arfccc, i); --i;
+ free(addr);
+ continue;
+ }
+
+ /* Remove outright duplicates in Cc */
+
+ for (j=i+1; j<arfccc->naddrs; j++)
+ {
+ char *addr2=rfc822_getaddr(arfccc, j);
+
+ if (!addr2) continue;
+ if (strcmp(addr, addr2) == 0)
+ {
+ rfc822_deladdr(arfccc, j);
+ --j;
+ }
+ free(addr2);
+ }
+ free(addr);
+ }
+ new_addresses=rfc822_getaddrs(arfccc);
+ free(oldtocc);
+ oldtocc=new_addresses;
+ if (arfccc) rfc822a_free(arfccc);
+ if (arfcto) rfc822a_free(arfcto);
+ rfc822t_free(rfccc);
+ if (rfcto) rfc822t_free(rfcto);
+ }
+
+ if (strcmp(ri->replymode, "feedback") == 0)
+ {
+ if (oldtolist)
+ {
+ free(oldtolist);
+ oldtolist=NULL;
+ }
+
+ if (oldfrom)
+ {
+ free(oldfrom);
+ oldfrom=NULL;
+ }
+
+ if (oldtocc)
+ {
+ free(oldtocc);
+ oldtocc=NULL;
+ }
+ }
+
+ if (oldtolist)
+ {
+ writes(ri, "To: ");
+ writes(ri, oldtolist);
+ writes(ri, "\n");
+ free(oldtolist);
+ }
+
+ if (oldfrom)
+ {
+ writes(ri, "To: ");
+ writes(ri, oldfrom);
+ writes(ri, "\n");
+ free(oldfrom);
+ }
+
+ if (oldtocc)
+ {
+ writes(ri, "Cc: ");
+ writes(ri, oldtocc);
+ writes(ri, "\n");
+ free(oldtocc);
+ }
+
+ if (oldmsgid || oldreferences)
+ {
+ writes(ri, "References: ");
+ if (writereferences(ri, oldreferences, oldmsgid))
+ errflag= -1;
+ if (oldreferences) free(oldreferences);
+ }
+ if (oldmsgid)
+ {
+ writes(ri, "In-Reply-To: ");
+ writes(ri, oldmsgid);
+ writes(ri, "\n");
+ free(oldmsgid);
+ }
+ writes(ri,"Subject: ");
+
+ if (ri->subject)
+ {
+ writes(ri, ri->subject);
+ }
+ else if (subject)
+ {
+ if (strcmp(ri->replymode, "feedback") == 0 ||
+ strcmp(ri->replymode, "replyfeedback") == 0)
+ {
+ writes(ri, subject);
+ }
+ else
+ {
+ char *s=rfc822_coresubj_keepblobs(subject);
+
+ writes(ri, "Re: ");
+ writes(ri, s ? s:"");
+ if (s) free(s);
+ }
+ free(subject);
+ }
+
+ writes(ri, "\nMime-Version: 1.0\n");
+
+ boundary=NULL;
+ dsn_report_type=NULL;
+
+ if (strcmp(ri->replymode, "replydsn") == 0 && ri->dsnfrom)
+ {
+ dsn_report_type="delivery-status";
+ dsn_report_gen=&replydsn;
+ }
+ else if (strcmp(ri->replymode, "replyfeedback") == 0 ||
+ strcmp(ri->replymode, "feedback") == 0)
+ {
+ dsn_report_type="feedback-report";
+ dsn_report_gen=&replyfeedback;
+ }
+
+ if (dsn_report_type)
+ {
+ boundary=rfc2045_mk_boundary(ri->rfc2045partp, ri->src);
+ if (!boundary)
+ return (-1);
+
+ writes(ri, "Content-Type: multipart/report;"
+ " report-type=");
+
+ writes(ri, dsn_report_type);
+ writes(ri, ";\n boundary=\"");
+
+ writes(ri, boundary);
+
+ writes(ri,"\"\n"
+ "\n"
+ RFC2045MIMEMSG
+ "\n"
+ "--");
+ writes(ri, boundary);
+ writes(ri, "\n");
+ }
+
+ if (ri->content_set_charset)
+ {
+ (*ri->content_set_charset)(ri->voidarg);
+ }
+ else
+ {
+ writes(ri, "Content-Type: text/plain; format=flowed; delsp=yes; charset=\"");
+ writes(ri, ri->charset);
+ writes(ri, "\"\n");
+ }
+ writes(ri, "Content-Transfer-Encoding: 8bit\n\n");
+
+ if (!ri->donotquote)
+ {
+ if (whowrote)
+ {
+ writes(ri, whowrote);
+ free(whowrote);
+ writes(ri, "\n\n");
+ }
+ if (SRC_SEEK(ri->src, start_body) == (off_t)-1)
+ return (-1);
+
+ replybody(ri, ri->rfc2045partp);
+ writes(ri, "\n"); /* First blank line in the reply */
+ }
+
+ if (ri->content_specify)
+ (*ri->content_specify)(ri->voidarg);
+
+ writes(ri, "\n");
+ if (ri->writesig_func)
+ (*ri->writesig_func)(ri->voidarg);
+ writes(ri, "\n");
+
+ if (boundary)
+ {
+ /* replydsn or replyfeedback */
+
+ char *header, *value;
+ struct rfc2045headerinfo *hi;
+
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "\nContent-Type: message/");
+
+ writes(ri, dsn_report_type);
+ writes(ri, "\n"
+ "Content-Transfer-Encoding: 7bit\n\n");
+
+ if (errflag == 0)
+ errflag=(*dsn_report_gen)(ri);
+
+ writes(ri, "\n--");
+ writes(ri, boundary);
+
+ if (ri->fullmsg)
+ {
+ off_t cnt=end_pos - start_pos;
+ char buf[BUFSIZ];
+
+ writes(ri, "\nContent-Type: message/rfc822\n"
+ "Content-Disposition: attachment\n\n");
+
+ if (errflag == 0)
+ errflag=SRC_SEEK(ri->src, start_pos);
+
+ while (errflag == 0 && cnt > 0)
+ {
+ int n=cnt > sizeof(BUFSIZ) ? BUFSIZ:(int)cnt;
+
+ n=SRC_READ(ri->src, buf, n);
+
+ if (n <= 0)
+ {
+ errflag= -1;
+ break;
+ }
+ (*ri->write_func)(buf, n, ri->voidarg);
+ cnt -= n;
+ }
+ }
+ else
+ {
+ writes(ri, "\nContent-Type: text/rfc822-headers; charset=\"iso-8859-1\"\n"
+ "Content-Disposition: attachment\n"
+ "Content-Transfer-Encoding: 8bit\n\n"
+ );
+
+ hi=rfc2045header_start(ri->src, ri->rfc2045partp);
+
+ while (hi)
+ {
+ if (rfc2045header_get(hi, &header, &value,
+ RFC2045H_NOLC) || !header)
+ {
+ rfc2045header_end(hi);
+ break;
+ }
+
+ writes(ri, header);
+ writes(ri, ": ");
+ writes(ri, value);
+ writes(ri, "\n");
+ }
+ }
+ writes(ri, "\n--");
+ writes(ri, boundary);
+ writes(ri, "--\n");
+ free(boundary);
+ }
+ return (errflag);
+}
+
+static void dsn_arrival_date(struct rfc2045_mkreplyinfo *ri)
+{
+ writes(ri, "Arrival-Date: ");
+
+ time_t now;
+
+ time(&now);
+
+ writes(ri, rfc822_mkdate(now));
+ writes(ri, "\n");
+}
+
+static int replydsn(struct rfc2045_mkreplyinfo *ri)
+{
+ dsn_arrival_date(ri);
+
+ writes (ri, "\n"
+ "Final-Recipient: rfc822; ");
+
+ writes(ri, ri->dsnfrom);
+
+ writes(ri, "\n"
+ "Action: delivered\n"
+ "Status: 2.0.0\n");
+ return 0;
+}
+
+static int replyfeedback(struct rfc2045_mkreplyinfo *ri)
+{
+ size_t i;
+
+ dsn_arrival_date(ri);
+ writes(ri, "User-Agent: librfc2045 "
+ RFC2045PKG "/" RFC2045VER
+ "\n"
+ "Version: 1\n");
+
+ for (i=0; ri->feedbackheaders &&
+ ri->feedbackheaders[i] && ri->feedbackheaders[i+1];
+ i += 2)
+ {
+ char *p=strdup(ri->feedbackheaders[i]), *q;
+ char lastch;
+
+ if (!p)
+ return -1;
+
+ lastch='-';
+ for (q=p; *q; q++)
+ {
+ if (*q >= 'A' && *q <= 'Z')
+ *q += 'a'-'A';
+
+ if (lastch == '-' && *q >= 'a' && *q <= 'z')
+ *q += 'A' - 'a';
+ lastch=*q;
+ }
+
+ writes(ri, p);
+ free(p);
+ writes(ri, ": ");
+ writes(ri, ri->feedbackheaders[i+1]);
+ writes(ri, "\n");
+ }
+
+ return 0;
+}
+
+
+/*
+** Accept a list of recipients, and return a list that contains only those
+** recipients that are defined as mailing lists.
+*/
+
+static char *do_checkmailinglists(struct rfc822a *a,
+ struct rfc2045_mkreplyinfo *,
+ char *);
+
+static char *mlcheck(struct rfc2045_mkreplyinfo *ri, const char *hdr)
+{
+ struct rfc822t *t;
+ struct rfc822a *a;
+
+ char *mlcopy;
+ char *p;
+
+ t=rfc822t_alloc_new(hdr, NULL, NULL);
+
+ if (!t)
+ return (0);
+
+ a=rfc822a_alloc(t);
+
+ if (!a)
+ {
+ rfc822t_free(t);
+ return (0);
+ }
+
+ mlcopy=strdup(ri->mailinglists ? ri->mailinglists:"");
+ if (!mlcopy)
+ p=0;
+ else
+ {
+ p=do_checkmailinglists(a, ri, mlcopy);
+ free(mlcopy);
+ }
+
+ rfc822a_free(a);
+ rfc822t_free(t);
+ return (p);
+}
+
+static char *do_checkmailinglists(struct rfc822a *a,
+ struct rfc2045_mkreplyinfo *ri,
+ char *mlbuffer)
+{
+ int i;
+
+ for (i=0; i<a->naddrs; i++)
+ {
+ char *p=rfc822_getaddr(a, i);
+ char *q;
+
+ if (!p)
+ return (NULL);
+
+ strcpy(mlbuffer, ri->mailinglists ? ri->mailinglists:"");
+
+ for (q=mlbuffer; (q=strtok(q, "\n \t")) != NULL; q=NULL)
+ {
+ if (strcasecmp(p, q) == 0)
+ break;
+ }
+
+ free(p);
+ if (q == NULL)
+ {
+ rfc822_deladdr(a, i);
+ --i;
+ }
+ }
+
+ if (a->naddrs == 0)
+ return (strdup(""));
+ return (rfc822_getaddrs(a));
+}
diff --git a/rfc2045/rfc2045rewrite.c b/rfc2045/rfc2045rewrite.c
new file mode 100644
index 0000000..89bc6e7
--- /dev/null
+++ b/rfc2045/rfc2045rewrite.c
@@ -0,0 +1,441 @@
+/*
+** Copyright 1998 - 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include "rfc2045.h"
+#include "rfc2045charset.h"
+#include "rfc822/encode.h"
+#include "rfc822/rfc822hdr.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+
+static char *rw_boundary_root;
+static int rw_boundary_cnt;
+static const char *rw_appname;
+
+static int fdout;
+static int (*fdout_func)(const char *, int, void *);
+static void *fdout_arg;
+
+static char fdout_buf[512];
+static char *fdout_ptr;
+static size_t fdout_left;
+static int conv_err;
+
+static int fdout_flush()
+{
+int n=fdout_ptr-fdout_buf;
+int i=0;
+char *p=fdout_buf;
+
+ while (n)
+ {
+ i=fdout_func ? (*fdout_func)(p, n, fdout_arg):
+ write(fdout, p, n);
+ if (i <= 0) return (-1);
+ p += i;
+ n -= i;
+ }
+ fdout_ptr=fdout_buf;
+ fdout_left=sizeof(fdout_buf);
+ return (0);
+}
+
+static int fdout_add(const char *p, size_t cnt)
+{
+ while (cnt)
+ {
+ if (cnt < fdout_left)
+ {
+ memcpy(fdout_ptr, p, cnt);
+ fdout_ptr += cnt;
+ fdout_left -= cnt;
+ return (0);
+ }
+ if (fdout_left == 0)
+ {
+ if (fdout_flush()) return (-1);
+ continue;
+ }
+ memcpy(fdout_ptr, p, fdout_left);
+ p += fdout_left;
+ cnt -= fdout_left;
+ fdout_ptr += fdout_left;
+ fdout_left=0;
+ }
+ return (0);
+}
+
+static int do_8bit(const char *p, size_t cnt, void *ptr)
+{
+ if (fdout_add(p, cnt))
+ conv_err=1;
+ return (0);
+}
+
+static int fdout_autoconverted(const char *oldte, const char *newte)
+{
+ if (fdout_add("X-Mime-Autoconverted: from ", 27) ||
+ fdout_add(oldte, strlen(oldte)) ||
+ fdout_add(" to ", 4) ||
+ fdout_add(newte, strlen(newte)) ||
+ (rw_appname && (fdout_add(" by ", 4) ||
+ fdout_add(rw_appname, strlen(rw_appname)))) ||
+ fdout_add("\n", 1)) return (-1);
+ return (0);
+}
+
+static int fdout_value(const char *);
+
+static int fdout_attr(const struct rfc2045attr *a)
+{
+ if (fdout_add(a->name, strlen(a->name))) return (-1);
+ if (a->value && (fdout_add("=", 1) || fdout_value(a->value)))
+ return (-1);
+ return (0);
+}
+
+static int fdout_value(const char *v)
+{
+size_t i,j;
+
+ for (i=0; v[i]; i++)
+ {
+ if ( !isalnum((int)(unsigned char)v[i]) && v[i] != '-')
+ {
+ if (fdout_add("\"", 1)) return (-1);
+ for (j=i=0; v[i]; i++)
+ if (v[i] == '\\' || v[i] == '"')
+ {
+ if (fdout_add(v+j, i-j) ||
+ fdout_add("\\", 1))
+ return (-1);
+ j=i;
+ }
+ if (fdout_add(v+j, i-j) || fdout_add("\"", 1))
+ return (-1);
+ return (0);
+ }
+ }
+ return (fdout_add(v, i));
+}
+
+static int fdout_add_qp(const char *ptr, size_t cnt, void *dummy)
+{
+ return (fdout_add(ptr, cnt));
+}
+
+static int qpe_do(const char *p, size_t i, void *ptr)
+{
+ return libmail_encode( (struct libmail_encode_info *)ptr, p, i);
+}
+
+#define TE(p) ((p)->rw_transfer_encoding ? \
+ (p)->rw_transfer_encoding: (p)->content_transfer_encoding)
+
+static int rwmime(struct rfc2045 *p)
+{
+static char mimever[]="Mime-Version: 1.0\n";
+const char *te;
+struct rfc2045attr *a;
+
+ if (!p->parent)
+ if (fdout_add(mimever, sizeof(mimever)-1)) return (-1);
+
+ if (p->content_type)
+ {
+ if (fdout_add("Content-Type: ", 14) ||
+ fdout_add(p->content_type, strlen(p->content_type)))
+ return (-1);
+
+ for (a=p->content_type_attr; a; a=a->next)
+ {
+ if (!a->name || strcmp(a->name, "boundary") == 0)
+ continue;
+ if ( fdout_add("; ", 2) ||
+ fdout_attr(a)) return (-1);
+ }
+ }
+
+ if (p->firstpart
+ && p->firstpart->next /* ADDED 8/30/99, see below */)
+ {
+ char buf[80];
+
+ ++rw_boundary_cnt;
+ sprintf(buf, "-%d", rw_boundary_cnt);
+ if ( fdout_add("; boundary=\"", 12) ||
+ fdout_add(rw_boundary_root, strlen(rw_boundary_root)) ||
+ fdout_add(buf, strlen(buf)) ||
+ fdout_add("\"", 1)) return (-1);
+ }
+ if (fdout_add("\n", 1)) return (-1);
+
+ /*
+ ** Show content transfer encoding, unless this is a multipart
+ ** section.
+ */
+
+ te=TE(p);
+ if (te && p->firstpart == NULL)
+ {
+ if (fdout_add("Content-Transfer-Encoding: ", 27) ||
+ fdout_add(te, strlen(te)) ||
+ fdout_add("\n", 1)) return (-1);
+ }
+ return (0);
+}
+
+static int dorw(struct rfc2045 *p, struct rfc2045src *src)
+{
+ int seen_mime=0;
+ char buf[BUFSIZ];
+ struct libmail_encode_info qp_encode;
+ int bcnt;
+ struct rfc2045headerinfo *hdr;
+ char *name, *value;
+
+ /* Slurp the untouchable portion of multipart/signed */
+
+#if 1
+ if (p->parent && p->parent->content_type &&
+ strcasecmp(p->parent->content_type, "multipart/signed") == 0 &&
+ p->next)
+ {
+ off_t ps=p->startpos;
+
+ if ((*src->seek_func)(ps, src->arg) == (off_t)-1)
+ return (-1);
+
+ while (ps < p->endbody)
+ {
+ ssize_t n;
+
+ if (p->endbody - ps > sizeof(buf))
+ n=sizeof(buf);
+ else n=p->endbody-ps;
+ n=(*src->read_func)(buf, n, src->arg);
+ if (n <= 0) return (-1);
+
+ if (fdout_add(buf, n)) return (-1);
+ ps += n;
+ }
+ return (0);
+ }
+#endif
+
+ if (p->parent)
+ {
+ seen_mime=1;
+ if (rwmime(p)) return (-1);
+ }
+
+ hdr=rfc2045header_start(src, p);
+
+ while (1)
+ {
+ if (rfc2045header_get(hdr, &name, &value,
+ RFC2045H_NOLC | RFC2045H_KEEPNL) < 0)
+ {
+ rfc2045header_end(hdr);
+ return (-1);
+ }
+
+ if (name == NULL)
+ break;
+
+ if (RFC2045_ISMIME1DEF(p->mime_version) &&
+ rfc822hdr_namecmp(name, "mime-version") == 0 &&
+ !seen_mime)
+ {
+ seen_mime=1;
+ rwmime(p);
+ continue;
+ }
+
+ if (!RFC2045_ISMIME1DEF(p->mime_version) ||
+ (rfc822hdr_namecmp(name, "mime-version") &&
+ rfc822hdr_namecmp(name, "content-type") &&
+ rfc822hdr_namecmp(name, "content-transfer-encoding")))
+ {
+ if (fdout_add(name, strlen(name)) ||
+ fdout_add(": ", 2) ||
+ fdout_add(value, strlen(value)) ||
+ fdout_add("\n", 1))
+ {
+ rfc2045header_end(hdr);
+ return (-1);
+ }
+ }
+ }
+ rfc2045header_end(hdr);
+
+ if (RFC2045_ISMIME1DEF(p->mime_version))
+ {
+ if (!seen_mime)
+ if (rwmime(p)) return (-1);
+
+ if (!p->firstpart && p->rw_transfer_encoding)
+ if (fdout_autoconverted(p->content_transfer_encoding,
+ p->rw_transfer_encoding)) return (-1);
+ }
+
+ if (fdout_add("\n", 1)) return (-1);
+
+ if ((*src->seek_func)(p->startbody, src->arg) == (off_t)-1)
+ return (-1);
+
+ /* For non-multipart section, just print the body */
+
+ if (!p->firstpart)
+ {
+ off_t ps=p->startbody;
+ int convmode=0;
+
+ if (p->rw_transfer_encoding)
+ {
+ if ( strcasecmp(p->rw_transfer_encoding,
+ "quoted-printable") == 0)
+ convmode=RFC2045_RW_7BIT;
+ else
+ convmode=RFC2045_RW_8BIT;
+ }
+
+ conv_err=0;
+ if (convmode == RFC2045_RW_7BIT)
+ {
+ libmail_encode_start(&qp_encode,
+ "quoted-printable",
+ fdout_add_qp, NULL);
+ rfc2045_cdecode_start(p, &qpe_do, &qp_encode);
+ }
+
+ if (convmode == RFC2045_RW_8BIT)
+ {
+ rfc2045_cdecode_start(p, &do_8bit, 0);
+ }
+
+ while (ps < p->endbody)
+ {
+ int n;
+
+ if (p->endbody - ps > sizeof(buf))
+ n=sizeof(buf);
+ else n=p->endbody-ps;
+ n=(*src->read_func)(buf, n, src->arg);
+ if (n <= 0) return (-1);
+ if (convmode)
+ rfc2045_cdecode(p, buf, n);
+ else if (fdout_add(buf, n)) conv_err=1;
+ ps += n;
+ if (conv_err) break;
+ }
+ if (convmode == RFC2045_RW_7BIT)
+ {
+ rfc2045_cdecode_end(p);
+ if (libmail_encode_end(&qp_encode))
+ conv_err=1;
+ }
+ if (convmode == RFC2045_RW_8BIT)
+ {
+ rfc2045_cdecode_end(p);
+ }
+ if (conv_err) return (-1);
+ return (0);
+ }
+
+ bcnt=rw_boundary_cnt;
+
+ /* Sam 8/30/99 fix - handle message/rfc822:
+
+ --boundary
+ Content-Type: message/rfc822
+
+ --><-- we're here, DON'T add RFC2045MIMEMSG and rest of crap here
+ */
+ if (p->firstpart->next == 0)
+ {
+ int rc;
+
+ p->firstpart->parent=0;
+ rc=dorw(p->firstpart, src);
+ p->firstpart->parent=p;
+ return (rc);
+ }
+
+ if (fdout_add(RFC2045MIMEMSG, sizeof(RFC2045MIMEMSG)-1))
+ return (-1);
+ for (p=p->firstpart; p; p=p->next)
+ {
+ if (p->isdummy) continue;
+ sprintf(buf, "\n--%s-%d\n", rw_boundary_root, bcnt);
+ if (fdout_add(buf, strlen(buf))) return (-1);
+ if (dorw(p, src) != 0) return(-1);
+ }
+ sprintf(buf, "\n--%s-%d--\n", rw_boundary_root, bcnt);
+ if (fdout_add(buf, strlen(buf))) return (-1);
+ return (0);
+}
+
+static int rfc2045_rewrite_common(struct rfc2045 *, struct rfc2045src *src,
+ const char *);
+
+int rfc2045_rewrite(struct rfc2045 *p, struct rfc2045src *src, int fdout_arg,
+ const char *appname)
+{
+ fdout=fdout_arg;
+ fdout_func=0;
+ return (rfc2045_rewrite_common(p, src, appname));
+}
+
+int rfc2045_rewrite_func(struct rfc2045 *p, struct rfc2045src *src,
+ int (*funcarg)(const char *, int, void *), void *funcargarg,
+ const char *appname)
+{
+ fdout= -1;
+ fdout_func=funcarg;
+ fdout_arg=funcargarg;
+ return (rfc2045_rewrite_common(p, src, appname));
+}
+
+static int rfc2045_rewrite_common(struct rfc2045 *p,
+ struct rfc2045src *src, const char *appname)
+{
+ int rc;
+
+ rw_appname=appname;
+
+ fdout_ptr=fdout_buf;
+ fdout_left=sizeof(fdout_buf);
+
+ rw_boundary_root=rfc2045_mk_boundary(p, src);
+ if (rw_boundary_root == 0)
+ rc= -1;
+ else
+ {
+ rw_boundary_cnt=1;
+ rc=dorw(p, src);
+ free(rw_boundary_root);
+ }
+ if (rc == 0 && fdout_ptr > fdout_buf
+ && fdout_ptr[-1] != '\n')
+ {
+ fdout_add("\n", 1);
+ }
+ if (rc == 0 && fdout_ptr > fdout_buf)
+ rc=fdout_flush();
+ return (rc);
+}
diff --git a/rfc2045/rfc2045searchcontenttype.c b/rfc2045/rfc2045searchcontenttype.c
new file mode 100644
index 0000000..50ce267
--- /dev/null
+++ b/rfc2045/rfc2045searchcontenttype.c
@@ -0,0 +1,41 @@
+/*
+** Copyright 2000 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc2045.h"
+
+
+/*
+** This function is generally called to find the "primary" text/plain
+** section in a MIME message which, presumably, contains the main message
+** text.
+**
+** Still the content type is set as a parameter in the event, I guess, we'll
+** ever be able to grok text/html.
+**
+** We'll return a NULL pointer if we can't find it.
+*/
+
+struct rfc2045 *rfc2045_searchcontenttype(struct rfc2045 *rfc, const char *ct)
+{
+ const char *content_type, *dummy;
+ struct rfc2045 *p;
+
+ rfc2045_mimeinfo(rfc, &content_type, &dummy, &dummy);
+ if (strcmp(content_type, ct) == 0)
+ return (rfc);
+
+ for (p=rfc->firstpart; p; p=p->next)
+ {
+ if (p->isdummy) continue;
+ rfc2045_mimeinfo(p, &content_type, &dummy, &dummy);
+ if (strcmp(content_type, ct) == 0)
+ break;
+ if (strncmp(content_type, "multipart/", 10) == 0)
+ return(rfc2045_searchcontenttype(p, ct));
+ }
+
+ return (p);
+}
diff --git a/rfc2045/rfc2045src.h b/rfc2045/rfc2045src.h
new file mode 100644
index 0000000..4cb56ea
--- /dev/null
+++ b/rfc2045/rfc2045src.h
@@ -0,0 +1,2 @@
+#define SRC_SEEK(src,pos) ((*(src)->seek_func)((pos), (src)->arg))
+#define SRC_READ(src,buf,cnt) ((*(src)->read_func)((buf),(cnt),(src)->arg))
diff --git a/rfc2045/rfc2045tryboundary.c b/rfc2045/rfc2045tryboundary.c
new file mode 100644
index 0000000..7b10340
--- /dev/null
+++ b/rfc2045/rfc2045tryboundary.c
@@ -0,0 +1,119 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include "rfc2045.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+
+
+extern void rfc2045_add_buf( char **, size_t *, size_t *,
+ const char *, size_t);
+
+static const char *boundary_chk_val;
+static size_t boundary_chk_val_len;
+static char *boundary_chk_buf;
+static size_t boundary_chk_bufsize, boundary_chk_buflen;
+static int boundary_chk_flag;
+
+static void boundary_chk_add(const char *p, size_t l)
+{
+ if (boundary_chk_buflen < boundary_chk_val_len+20)
+ rfc2045_add_buf( &boundary_chk_buf,
+ &boundary_chk_bufsize,
+ &boundary_chk_buflen, p, l);
+}
+
+static int boundary_chk(const char *p, size_t l, void *ptr)
+{
+static size_t i, j;
+
+ for (j=i=0; i<l; i++)
+ {
+ if (p[i] == '\n')
+ {
+ boundary_chk_add(p+j, i-j);
+
+ if (boundary_chk_buflen >= boundary_chk_val_len+2 &&
+ boundary_chk_buf[0] == '-' &&
+ boundary_chk_buf[1] == '-' &&
+ strncasecmp(boundary_chk_val,
+ boundary_chk_buf+2,
+ boundary_chk_val_len) == 0)
+ boundary_chk_flag=1;
+
+ boundary_chk_buflen=0;
+ j=i+1;
+ }
+ }
+ boundary_chk_add(p+j, l-j);
+ return (0);
+}
+
+static int try_boundary(struct rfc2045 *p, struct rfc2045src *src)
+{
+int rc;
+char buf[512];
+int n, cnt;
+off_t ps;
+
+ if (p->firstpart)
+ {
+ for (p=p->firstpart; p; p=p->next)
+ if ((rc=try_boundary(p, src)) != 0)
+ return (rc);
+ return (0);
+ }
+
+ if (p->content_transfer_encoding &&
+ strcmp(p->content_transfer_encoding, "base64") == 0)
+ return (0);
+
+ boundary_chk_flag=0;
+ boundary_chk_buflen=0;
+
+ if ((*src->seek_func)(p->startbody, src->arg) == -1) return (-1);
+ rfc2045_cdecode_start(p, boundary_chk, 0);
+
+ ps=p->startbody;
+ while (ps < p->endbody)
+ {
+ if (p->endbody - ps < sizeof(buf))
+ cnt=p->endbody-ps;
+ else cnt=sizeof(buf);
+ n=(*src->read_func)(buf, cnt, src->arg);
+ if (n <= 0) return (-1);
+ rfc2045_cdecode(p, buf, n);
+ ps += n;
+ if (boundary_chk_flag) break;
+ }
+ rfc2045_cdecode_end(p);
+ if (boundary_chk_buflen)
+ boundary_chk("\n", 1, 0); /* Flush out partial line */
+ return (boundary_chk_flag);
+}
+
+int rfc2045_try_boundary(struct rfc2045 *p, struct rfc2045src *src,
+ const char *boundary)
+{
+int n;
+
+ boundary_chk_val_len=strlen(boundary_chk_val=boundary);
+ boundary_chk_buf=0;
+ boundary_chk_bufsize=0;
+ n=try_boundary(p, src);
+ if (boundary_chk_buf) free(boundary_chk_buf);
+ return (n);
+}
diff --git a/rfc2045/rfc2045xdump.c b/rfc2045/rfc2045xdump.c
new file mode 100644
index 0000000..f1f96eb
--- /dev/null
+++ b/rfc2045/rfc2045xdump.c
@@ -0,0 +1,47 @@
+/*
+** Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+#include "rfc2045.h"
+#include <stdio.h>
+
+#define DUMP(s,n) if ( (s) ) { printf("%*s%s: %s\n", level*4, "", n, (s)); }
+
+/* Extended RFC2045 dump */
+
+static void dodump(struct rfc2045 *p, int level)
+{
+ if (!p->isdummy)
+ {
+ printf("%*sMessage start %ld, end %ld, startbody %ld, endbody %ld.\n", level*4,
+ "", (long)p->startpos, (long)p->endpos,
+ (long)p->startbody, (long)p->endbody);
+ DUMP(p->mime_version, "Mime-Version")
+ DUMP(p->content_type, "Content-Type")
+ DUMP(rfc2045_getattr(p->content_type_attr, "charset"),
+ "Charset")
+ DUMP(p->content_transfer_encoding, "Transfer Encoding")
+ DUMP(rfc2045_getattr(p->content_type_attr, "boundary"),
+ "Boundary")
+ DUMP(p->content_disposition, "Content Disposition")
+ DUMP(rfc2045_getattr(p->content_disposition_attr, "name"),
+ "Disposition Name")
+ DUMP(rfc2045_getattr(p->content_disposition_attr, "filename"),
+ "Disposition Filename")
+ }
+
+ for (p=p->firstpart; p; p=p->next)
+ {
+ printf("%*s{\n", level*4, "");
+ dodump(p, level+1);
+ printf("%*s}\n", level*4, "");
+ }
+}
+
+void rfc2045_xdump(struct rfc2045 *p)
+{
+ dodump(p, 0);
+}
diff --git a/rfc2045/rfc2231.c b/rfc2045/rfc2231.c
new file mode 100644
index 0000000..f0908e8
--- /dev/null
+++ b/rfc2045/rfc2231.c
@@ -0,0 +1,375 @@
+/*
+** Copyright 2002-2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "rfc2045.h"
+#include "rfc822/rfc822.h"
+#include "unicode/unicode.h"
+
+/*
+** Deallocate a link list of rfc2231param structures.
+*/
+
+void rfc2231_paramDestroy(struct rfc2231param *p)
+{
+ while (p)
+ {
+ struct rfc2231param *q=p->next;
+
+ free(p);
+ p=q;
+ }
+}
+
+int rfc2231_buildAttrList(struct rfc2231param **paramList,
+ const char *name,
+
+ const char *attrName,
+ const char *attrValue)
+{
+ int nameLen=strlen(name);
+
+ if (strncmp(attrName, name, nameLen) == 0 &&
+ (attrName[nameLen] == 0 ||
+ attrName[nameLen] == '*'))
+ {
+ struct rfc2231param *n
+ =malloc(sizeof(struct rfc2231param)), **o;
+
+ const char *p=attrName + nameLen;
+
+ if (!n)
+ {
+ rfc2231_paramDestroy(*paramList);
+ return -1;
+ }
+
+ /*
+ ** A non-rfc 2231 parameter has paramnum set to 0, an
+ ** rfc 2231 parameter has paramnum set to its number, plus 1.
+ */
+
+ if (*p == 0)
+ {
+ n->paramnum=0;
+ }
+ else
+ {
+ p++;
+
+ n->paramnum=atoi(p)+1;
+
+ if (n->paramnum <= 0)
+ n->paramnum=1;
+ }
+
+ p=strrchr(attrName, '*');
+
+ n->encoded=p && p[1] == 0;
+ n->value=attrValue;
+
+ for (o=paramList; *o; o= &(*o)->next)
+ if ( (*o)->paramnum > n->paramnum)
+ break;
+
+ n->next= *o;
+ *o=n;
+ }
+ return 0;
+}
+
+
+/*
+** Create a link list of rfc2231param structures for a specific attribute
+**
+** Returns: 0 - ok, < 0 - out of memory.
+*/
+
+static int rfc2231_paramCreate(struct rfc2045attr *attr,
+ const char *name,
+ struct rfc2231param **paramList)
+{
+ *paramList=NULL;
+
+ while (attr)
+ {
+ if (rfc2231_buildAttrList(paramList, name, attr->name,
+ attr->value) < 0)
+ return (-1);
+ attr=attr->next;
+ }
+
+ return (0);
+}
+
+static const char rfc2231_xdigit[]="0123456789ABCDEFabcdef";
+
+static int nyb(char c)
+{
+ const char *p=strchr(rfc2231_xdigit, c);
+ int n;
+
+ if (!p)
+ return 0;
+
+ n=p-rfc2231_xdigit;
+
+ if (n >= 16)
+ n -= 6;
+
+ return n;
+}
+
+/*
+** Decode an rfc2231param link list.
+**
+** charset, language, text, are decoded, if the corresponding args below are
+** not null. Their corresponding lengths (including the null bytes) are
+** always saved in the corresponding int args. rfc2231_decode() is called
+** twice to get the lengths, then once again after the buffers are allocated.
+*/
+
+void rfc2231_paramDecode(struct rfc2231param *paramList,
+ char *charsetPtr,
+ char *langPtr,
+ char *textPtr,
+ int *charsetLen,
+ int *langLen,
+ int *textLen)
+{
+ int first=1;
+
+ *charsetLen=*langLen=*textLen=1; /* null byte */
+
+ if (paramList && paramList->paramnum == 0 &&
+ paramList->next)
+ paramList=paramList->next;
+ /*
+ ** Both a non-rfc2231 and an rfc2231 parameter was specified, so
+ ** take the better one.
+ */
+
+ while (paramList)
+ {
+ const char *p=paramList->value;
+
+ if (first && paramList->encoded)
+ {
+ const char *q=strchr(p, '\'');
+
+ if (q && strchr(q+1, '\''))
+ {
+ while (*p != '\'')
+ {
+ if (charsetPtr)
+ *charsetPtr++ = *p;
+ p++;
+ (*charsetLen)++;
+ }
+ p++;
+ while (*p != '\'')
+ {
+ if (langPtr)
+ *langPtr++ = *p;
+ p++;
+ (*langLen)++;
+ }
+ p++;
+ }
+ }
+
+ first=0;
+
+ while (*p)
+ {
+ if (*p == '%' && p[1] && p[2] && paramList->encoded)
+ {
+ if (textPtr)
+ *textPtr++ = nyb(p[1]) * 16 +
+ nyb(p[2]);
+ p += 3;
+ }
+ else
+ {
+ if (textPtr)
+ *textPtr++ = *p;
+
+ p++;
+ }
+
+ (*textLen)++;
+ }
+
+ paramList=paramList->next;
+ }
+
+ if (charsetPtr)
+ *charsetPtr=0;
+ if (langPtr)
+ *langPtr=0;
+ if (textPtr)
+ *textPtr=0;
+}
+
+/*
+** Retrieve RFC 2231 information from a specific rfc2045attr list
+**
+** Returns 0 success, -1 for failure
+*/
+
+static int rfc2231_decode(struct rfc2045attr *attrList,
+ const char *name,
+
+ char **chsetPtr,
+ char **langPtr,
+ char **textPtr)
+{
+ int chsetLen;
+ int langLen;
+ int textLen;
+
+ struct rfc2231param *paramList;
+
+ if (rfc2231_paramCreate(attrList, name, &paramList) < 0)
+ return -1;
+
+ rfc2231_paramDecode(paramList, NULL, NULL, NULL,
+ &chsetLen,
+ &langLen,
+ &textLen);
+
+ if (chsetPtr)
+ *chsetPtr=NULL;
+
+ if (langPtr)
+ *langPtr=NULL;
+
+ if (textPtr)
+ *textPtr=NULL;
+
+
+ if ((chsetPtr && (*chsetPtr=malloc(chsetLen)) == NULL)
+ || (langPtr && (*langPtr=malloc(langLen)) == NULL)
+ || (textPtr && (*textPtr=malloc(textLen)) == NULL))
+ {
+ rfc2231_paramDestroy(paramList);
+
+ if (*chsetPtr)
+ free(*chsetPtr);
+
+ if (*langPtr)
+ free(*langPtr);
+
+ if (*textPtr)
+ free(*textPtr);
+ return (-1);
+ }
+
+ rfc2231_paramDecode(paramList,
+ chsetPtr ? *chsetPtr:NULL,
+ langPtr ? *langPtr:NULL,
+ textPtr ? *textPtr:NULL,
+ &chsetLen,
+ &langLen,
+ &textLen);
+ return 0;
+}
+
+int rfc2231_decodeType(struct rfc2045 *rfc, const char *name,
+ char **chsetPtr,
+ char **langPtr,
+ char **textPtr)
+{
+ return rfc2231_decode(rfc->content_type_attr, name,
+ chsetPtr, langPtr, textPtr);
+}
+
+int rfc2231_decodeDisposition(struct rfc2045 *rfc, const char *name,
+ char **chsetPtr,
+ char **langPtr,
+ char **textPtr)
+{
+ return rfc2231_decode(rfc->content_disposition_attr, name,
+ chsetPtr, langPtr, textPtr);
+}
+
+static int conv_unicode(char **text, const char *fromChset,
+ const char *toChset)
+{
+ int err;
+ char *p;
+
+ if (!toChset)
+ toChset=unicode_default_chset();
+
+ if (!fromChset || !*fromChset)
+ return 0;
+
+ p=libmail_u_convert_tobuf(*text, fromChset, toChset, &err);
+
+ if (p && err)
+ {
+ free(p);
+ p=NULL;
+ }
+
+ if (!p)
+ return (-1);
+
+ free(*text);
+ *text=p;
+ return (0);
+}
+
+int rfc2231_udecodeType(struct rfc2045 *rfc, const char *name,
+ const char *myCharset,
+ char **textPtr)
+{
+ char *text, *chset;
+
+ if (rfc2231_decodeType(rfc, name, &chset, NULL, &text) < 0)
+ return (-1);
+
+ if (conv_unicode(&text, chset, myCharset) < 0)
+ {
+ free(text);
+ free(chset);
+ return (-1);
+ }
+
+ *textPtr=text;
+ free(chset);
+ return (0);
+}
+
+int rfc2231_udecodeDisposition(struct rfc2045 *rfc, const char *name,
+ const char *myCharset,
+ char **textPtr)
+{
+ char *text, *chset;
+
+ if (rfc2231_decodeDisposition(rfc, name, &chset, NULL, &text) < 0)
+ return (-1);
+
+ if (conv_unicode(&text, chset, myCharset) < 0)
+ {
+ free(text);
+ free(chset);
+ return (-1);
+ }
+
+ *textPtr=text;
+ free(chset);
+ return (0);
+}
diff --git a/rfc2045/rfc2231encode.c b/rfc2045/rfc2231encode.c
new file mode 100644
index 0000000..941dffe
--- /dev/null
+++ b/rfc2045/rfc2231encode.c
@@ -0,0 +1,147 @@
+/*
+** Copyright 2002-2004 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+
+#if HAVE_CONFIG_H
+#include "rfc2045_config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "rfc2045.h"
+#include <errno.h>
+
+static const char xdigit[]="0123456789ABCDEFabcdef";
+
+#define DOENCODE(c) \
+ (strchr("()'\"\\%:;=", (c)) || (c) <= ' ' || (c) >= 127)
+
+static int docreate(const char *name,
+ char *attrvalue,
+ int (*cb_func)(const char *param,
+ const char *value,
+ void *void_arg),
+ void *cb_arg);
+
+int rfc2231_attrCreate(const char *name, const char *value,
+ const char *charset,
+ const char *language,
+ int (*cb_func)(const char *param,
+ const char *value,
+ void *void_arg),
+ void *cb_arg)
+{
+ size_t l;
+ const char *cp;
+ char *p, *q;
+ int rc;
+
+ if (strlen(name)>60)
+ {
+ errno=EINVAL;
+ return -1; /* You kidding me? */
+ }
+
+ for (l=0; value[l]; l++)
+ if (DOENCODE(value[l]))
+ break;
+
+ if (value[l] == 0 && strlen(name)+strlen(value)<75) /* No need to encode */
+ {
+ char *p=malloc(strlen(value)+3);
+
+ if (!p)
+ return -1;
+
+ strcat(strcat(strcpy(p, "\""), value), "\"");
+
+ rc=(*cb_func)(name, p, cb_arg);
+ free(p);
+ return rc;
+ }
+
+ if (!charset) charset="";
+ if (!language) language="";
+
+ l=strlen(charset)+strlen(language)+strlen(value)+3;
+
+ for (cp=value; *cp; cp++)
+ if (DOENCODE(*cp))
+ l += 2;
+
+ p=malloc(l);
+ if (!p)
+ return -1;
+
+ strcat(strcat(strcat(strcpy(p, charset), "'"),language), "'");
+ q=p+strlen(p);
+ for (cp=value; *cp; cp++)
+ {
+ if (DOENCODE(*cp))
+ {
+ *q++='%';
+ *q++ = xdigit[ ((unsigned char)*cp / 16) & 15];
+ *q++ = xdigit[ *cp & 15];
+ }
+ else
+ *q++= *cp;
+ }
+ *q=0;
+
+ rc=docreate(name, p, cb_func, cb_arg);
+ free(p);
+ return rc;
+}
+
+static int docreate(const char *name,
+ char *q,
+ int (*cb_func)(const char *param,
+ const char *value,
+ void *void_arg),
+ void *cb_arg)
+{
+ char c;
+ char *r;
+ int rc;
+ size_t l;
+ int n;
+
+ r=malloc(strlen(name)+20);
+ if (!r)
+ return -1;
+
+ rc=0;
+ n=0;
+
+ while (*q)
+ {
+ sprintf(r, "%s*%d*", name, n++);
+
+ l=strlen(q);
+ if (l > 70-strlen(r))
+ l=70-strlen(r);
+
+ if (q[l] == '%')
+ l += 3;
+ else if (l && q[l-1] == '%')
+ l += 2;
+ else if (l > 1 && q[l-2] == '%')
+ l += 1;
+
+ c=q[l];
+ q[l]=0;
+
+ rc=(*cb_func)(r, q, cb_arg);
+ if (rc)
+ break;
+ q[l]=c;
+ q += l;
+ }
+ free(r);
+ return rc;
+}
diff --git a/rfc2045/rfc3676parser.c b/rfc2045/rfc3676parser.c
new file mode 100644
index 0000000..332d22b
--- /dev/null
+++ b/rfc2045/rfc3676parser.c
@@ -0,0 +1,1005 @@
+/*
+** Copyright 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+#include "rfc2045_config.h"
+#include "rfc3676parser.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define NONFLOWED_WRAP_REDUCE 74
+
+#define NONFLOWED_THRESHOLD_EXCEEDED 30
+
+
+static void emit_line_begin(rfc3676_parser_t handle);
+
+static void emit_line_contents(rfc3676_parser_t handle,
+ const unicode_char *uc,
+ size_t cnt);
+
+static void emit_line_flowed_wrap(rfc3676_parser_t handle);
+
+static void emit_line_end(rfc3676_parser_t handle);
+
+
+static void nonflowed_line_begin(rfc3676_parser_t handle);
+
+static void nonflowed_line_contents(rfc3676_parser_t handle,
+ const unicode_char *uc,
+ size_t cnt);
+
+static void nonflowed_line_end(rfc3676_parser_t handle);
+
+static int nonflowed_line_process(int linebreak_opportunity,
+ unicode_char ch, void *dummy);
+
+#define EMIT_LINE_BEGIN(h) do { \
+ (*(h)->line_begin_handler)(h); \
+ } while (0)
+
+#define EMIT_LINE_CONTENTS(h, uc, cnt) do { \
+ (*(h)->line_content_handler)((h),(uc),(cnt)); \
+ } while (0)
+
+#define EMIT_LINE_END(h) do { \
+ (*(h)->line_end_handler)(h); \
+ } while (0)
+
+struct rfc3676_parser_struct {
+
+ struct rfc3676_parser_info info;
+ libmail_u_convert_handle_t uhandle;
+
+ int errflag;
+
+ /* Receive raw text stream, converted to unicode */
+ size_t (*line_handler)(rfc3676_parser_t,
+ const unicode_char *ptr, size_t cnt);
+
+ /*
+ ** Receive mostly raw text stream: CRs that precede an LF
+ ** are removed from the stream received by content_handler.
+ */
+ size_t (*content_handler)(rfc3676_parser_t,
+ const unicode_char *ptr, size_t cnt);
+
+ size_t quote_level;
+ size_t sig_block_index;
+
+ /*
+ ** Flag: previous line ended in a flowed space, and the previous
+ ** line's quoting level was this.
+ */
+ int has_previous_quote_level;
+ size_t previous_quote_level;
+
+ /*
+ ** Flag: current line was flowed into from a previous line with the
+ ** same quoting level.
+ */
+ int was_previous_quote_level;
+
+ /* A line has begun */
+ void (*line_begin_handler)(rfc3676_parser_t handle);
+
+ /* Content of this line */
+ void (*line_content_handler)(rfc3676_parser_t handle,
+ const unicode_char *uc,
+ size_t cnt);
+
+ /* End of this line */
+ void (*line_end_handler)(rfc3676_parser_t handle);
+
+
+ /*
+ ** When non-flowed text is getting rewrapped, we utilize the services
+ ** of the unicode_lbc_info API.
+ */
+
+ unicode_lbc_info_t lb;
+
+ struct unicode_buf nonflowed_line;
+ /* Collect unflowed line until it reaches the given size */
+
+ struct unicode_buf nonflowed_next_word;
+ /* Collects unicode stream until a linebreaking opportunity */
+
+ size_t nonflowed_line_target_width;
+ /* Targeted width of nonflowed lines */
+
+ size_t nonflowed_line_width; /* Width of nonflowed_line */
+
+ size_t nonflowed_next_word_width; /* Width of nonflowed_next_word */
+
+ /* Current handle of non-flowd content. */
+ void (*nonflowed_line_process)(struct rfc3676_parser_struct *handle,
+ int linebreak_opportunity,
+ unicode_char ch,
+ size_t ch_width);
+
+ void (*nonflowed_line_end)(struct rfc3676_parser_struct *handle);
+};
+
+static int parse_unicode(const char *, size_t, void *);
+
+static size_t scan_crlf(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t scan_crlf_seen_cr(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t start_of_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t count_quote_level(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t counted_quote_level(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t check_signature_block(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t start_content_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t scan_content_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t seen_sig_block(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t seen_notsig_block(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+static size_t seen_content_sp(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt);
+
+
+/*
+** The top layer initializes the conversion to unicode.
+*/
+
+rfc3676_parser_t rfc3676parser_init(const struct rfc3676_parser_info *info)
+{
+ rfc3676_parser_t handle=
+ (rfc3676_parser_t)calloc(1,
+ sizeof(struct rfc3676_parser_struct));
+
+ if (!handle)
+ return NULL;
+
+ handle->info=*info;
+ if ((handle->uhandle=libmail_u_convert_init(info->charset,
+ libmail_u_ucs4_native,
+ parse_unicode,
+ handle)) == NULL)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ if (!handle->info.isflowed)
+ handle->info.isdelsp=0; /* Sanity check */
+
+ handle->line_handler=scan_crlf;
+ handle->content_handler=start_of_line;
+ handle->has_previous_quote_level=0;
+ handle->previous_quote_level=0;
+
+ handle->line_begin_handler=emit_line_begin;
+ handle->line_content_handler=emit_line_contents;
+ handle->line_end_handler=emit_line_end;
+
+ unicode_buf_init(&handle->nonflowed_line, (size_t)-1);
+ unicode_buf_init(&handle->nonflowed_next_word, (size_t)-1);
+
+ if (!handle->info.isflowed)
+ {
+ handle->line_begin_handler=nonflowed_line_begin;
+ handle->line_content_handler=nonflowed_line_contents;
+ handle->line_end_handler=nonflowed_line_end;
+ }
+ return handle;
+}
+
+int rfc3676parser(rfc3676_parser_t handle,
+ const char *txt,
+ size_t txt_cnt)
+{
+ if (handle->errflag)
+ return handle->errflag; /* Error occured previously */
+
+ /* Convert to unicode and invoke parse_unicode() */
+
+ return libmail_u_convert(handle->uhandle, txt, txt_cnt);
+}
+
+/*
+** Convert char stream from iconv into unicode_chars, then pass them to the
+** current handler, until all converted unicode_chars are consumed.
+*/
+
+static int parse_unicode(const char *ucs4, size_t nbytes, void *arg)
+{
+ rfc3676_parser_t handle=(rfc3676_parser_t)arg;
+ unicode_char ucs4buf[128];
+ const unicode_char *p;
+
+ /* Keep going until there's an error, or everything is consumed. */
+
+ while (handle->errflag == 0 && nbytes)
+ {
+ /* Do it in pieces, using the temporary unicode_char buffer */
+
+ size_t cnt=nbytes;
+
+ if (cnt > sizeof(ucs4buf))
+ cnt=sizeof(ucs4buf);
+
+ memcpy(ucs4buf, ucs4, cnt);
+
+ ucs4 += cnt;
+ nbytes -= cnt;
+
+ cnt /= sizeof(unicode_char);
+ p=ucs4buf;
+
+ /* Keep feeding it to the current handler */
+
+ while (handle->errflag == 0 && cnt)
+ {
+ size_t n=(*handle->line_handler)(handle, p, cnt);
+
+ if (handle->errflag == 0)
+ {
+ cnt -= n;
+ p += n;
+ }
+ }
+ }
+
+ return handle->errflag;
+}
+
+int rfc3676parser_deinit(rfc3676_parser_t handle, int *errptr)
+{
+ /* Finish unicode conversion */
+
+ int rc=libmail_u_convert_deinit(handle->uhandle, errptr);
+
+ if (rc == 0)
+ rc=handle->errflag;
+
+ if (rc == 0)
+ {
+ (*handle->line_handler)(handle, NULL, 0);
+ rc=handle->errflag;
+ }
+
+ if (handle->lb)
+ {
+ int rc2=unicode_lbc_end(handle->lb);
+
+ if (rc2 && rc == 0)
+ rc=rc2;
+ }
+
+ unicode_buf_deinit(&handle->nonflowed_line);
+ unicode_buf_deinit(&handle->nonflowed_next_word);
+
+ free(handle);
+ return rc;
+}
+
+/*
+** Look for a CR that might precede an LF.
+*/
+
+static size_t scan_crlf(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ size_t i;
+
+ if (ptr == NULL)
+ {
+ if (handle->errflag == 0)
+ (*handle->content_handler)(handle, NULL, 0);
+ return 0;
+ }
+
+ for (i=0; ptr && i<cnt; ++i)
+ {
+ if (ptr[i] == '\r')
+ break;
+ }
+
+ if (i)
+ {
+ size_t consumed=0;
+
+ while (i && handle->errflag == 0)
+ {
+ size_t n=(*handle->content_handler)(handle, ptr, i);
+
+ ptr += n;
+ consumed += n;
+ i -= n;
+ }
+ return consumed;
+ }
+
+ /* Consume the first character, the CR */
+
+ handle->line_handler=scan_crlf_seen_cr;
+ return 1;
+}
+
+/*
+** Check the first character after a CR.
+*/
+
+static size_t scan_crlf_seen_cr(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ unicode_char cr='\r';
+
+ handle->line_handler=scan_crlf;
+
+ if (ptr == NULL || *ptr != '\n')
+ {
+ /*
+ ** CR was not followed by a NL.
+ ** Restore it in the char stream.
+ */
+
+ while (handle->errflag == 0)
+ if ((*handle->content_handler)(handle, &cr, 1))
+ break;
+ }
+
+ return scan_crlf(handle, ptr, cnt);
+}
+
+/*
+** From this point on, CRLF are collapsed into NLs, so don't need to worry
+** about them.
+*/
+
+
+/*
+** Check for an EOF indication at the start of the line.
+*/
+
+static size_t start_of_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ if (ptr == NULL)
+ {
+ if (handle->has_previous_quote_level)
+ EMIT_LINE_END(handle); /* Last line was flowed */
+
+ return cnt; /* EOF */
+ }
+
+ /* Begin counting the quote level */
+
+ handle->content_handler=count_quote_level;
+ handle->quote_level=0;
+ return count_quote_level(handle, ptr, cnt);
+}
+
+/*
+** Count leading > in flowed content.
+*/
+
+static size_t count_quote_level(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ size_t i;
+
+ if (ptr == NULL) /* EOF, pretend that the quote level was counted */
+ return (handle->content_handler=counted_quote_level)
+ (handle, ptr, cnt);
+
+ for (i=0; i<cnt; ++i)
+ {
+ if (ptr[i] != '>' || !handle->info.isflowed)
+ {
+ handle->content_handler=counted_quote_level;
+
+ if (i == 0)
+ return counted_quote_level(handle, ptr, cnt);
+ break;
+ }
+ ++handle->quote_level;
+ }
+
+ return i;
+}
+
+/*
+** This line's quote level has now been counted.
+*/
+
+static size_t counted_quote_level(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ handle->was_previous_quote_level=0;
+
+ /*
+ ** If the previous line was flowed and this line has the same
+ ** quote level, make the flow official.
+ */
+
+ if (handle->has_previous_quote_level &&
+ handle->quote_level == handle->previous_quote_level)
+ {
+ /* Remember that this line was flowed into */
+ handle->was_previous_quote_level=1;
+ }
+ else
+ {
+ /*
+ ** If the previous line was flowed, but this line carries
+ ** a different quote level, force-terminate the previous
+ ** line, before beginning this line.
+ */
+ if (handle->has_previous_quote_level)
+ EMIT_LINE_END(handle);
+
+ EMIT_LINE_BEGIN(handle);
+ }
+
+ handle->has_previous_quote_level=0;
+ /* Assume this line won't be flowed, until shown otherwise */
+
+
+ if (!handle->info.isflowed)
+ {
+ /*
+ ** No space-stuffing, or sig block checking, if this is not
+ ** flowed content.
+ */
+ handle->content_handler=scan_content_line;
+ return scan_content_line(handle, ptr, cnt);
+ }
+
+
+ handle->content_handler=start_content_line;
+
+ if (ptr != NULL && *ptr == ' ')
+ return 1; /* Remove stuffed space */
+
+ return start_content_line(handle, ptr, cnt);
+}
+
+/*
+** Minor deviation from RFC3676, but this fixes a lot of broken text.
+**
+** If the previous line was flowed, but this is an empty line (optionally
+** space-stuffed), unflow the last line (make it fixed), and this becomes
+** a fixed line too. Example:
+**
+** this is the last end of a paragraph[SPACE]
+** [SPACE]
+** This is the first line of the next paragraph.
+**
+** Strict RFC3676 rules will parse this as a flowed line, then a fixed line,
+** resulting in no paragraph breaks.
+*/
+
+static size_t start_content_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ /*
+ ** We'll start scanning for the signature block, as soon as
+ ** this check is done.
+ */
+ handle->content_handler=check_signature_block;
+ handle->sig_block_index=0;
+
+ if (ptr && *ptr == '\n' && handle->was_previous_quote_level)
+ {
+ EMIT_LINE_END(handle);
+ EMIT_LINE_BEGIN(handle);
+ handle->was_previous_quote_level=0;
+ }
+
+ return check_signature_block(handle, ptr, cnt);
+}
+
+
+static const unicode_char sig_block[]={'-', '-', ' '};
+
+/* Checking for a magical sig block */
+
+static size_t check_signature_block(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ if (ptr && *ptr == sig_block[handle->sig_block_index])
+ {
+ if (++handle->sig_block_index == sizeof(sig_block)
+ /sizeof(sig_block[0]))
+
+ /* Well, it's there, but does a NL follow? */
+ handle->content_handler=seen_sig_block;
+ return 1;
+ }
+
+ return seen_notsig_block(handle, ptr, cnt);
+}
+
+static size_t seen_sig_block(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ if (ptr == NULL || *ptr == '\n')
+ {
+ /*
+ ** If the previous line was flowed, the sig block is not
+ ** considered to be flowable-into content, so terminate
+ ** the previous line before emitting the sig block.
+ */
+
+ if (handle->was_previous_quote_level)
+ {
+ EMIT_LINE_END(handle);
+ EMIT_LINE_BEGIN(handle);
+ handle->was_previous_quote_level=0;
+ }
+
+ /* Pass through the sig block */
+
+ handle->content_handler=start_of_line;
+
+ EMIT_LINE_CONTENTS(handle, sig_block,
+ sizeof(sig_block)/sizeof(sig_block[0]));
+ EMIT_LINE_END(handle);
+ return ptr ? 1:0;
+ }
+
+ return seen_notsig_block(handle, ptr, cnt);
+}
+
+/* This is not a sig block line */
+
+static size_t seen_notsig_block(rfc3676_parser_t handle,
+ const unicode_char *newptr, size_t newcnt)
+{
+ const unicode_char *ptr;
+ size_t i;
+
+ if (handle->was_previous_quote_level)
+ emit_line_flowed_wrap(handle);
+
+ handle->content_handler=scan_content_line;
+
+ ptr=sig_block;
+ i=handle->sig_block_index;
+
+ while (i && handle->errflag == 0)
+ {
+ size_t n=(*handle->content_handler)(handle, ptr, i);
+
+ ptr += n;
+ i -= n;
+ }
+
+ return (*handle->content_handler)(handle, newptr, newcnt);
+}
+
+/*
+** Pass through the line, until encountering an NL, or a space in flowable
+** content.
+*/
+
+static size_t scan_content_line(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ size_t i;
+
+ for (i=0; ptr && i<cnt && ptr[i] != '\n' &&
+ (ptr[i] != ' ' || !handle->info.isflowed); ++i)
+ ;
+
+ /* Pass through anything before the NL or potentially flowable SP */
+
+ if (i)
+ EMIT_LINE_CONTENTS(handle, ptr, i);
+
+ if (i)
+ return i;
+
+ if (ptr && ptr[i] == ' ')
+ {
+ handle->content_handler=seen_content_sp;
+ return 1;
+ }
+
+ /* NL. This line does not flow */
+ EMIT_LINE_END(handle);
+
+ handle->content_handler=start_of_line;
+
+ return ptr ? 1:0;
+}
+
+static size_t seen_content_sp(rfc3676_parser_t handle,
+ const unicode_char *ptr, size_t cnt)
+{
+ unicode_char sp=' ';
+
+ handle->content_handler=scan_content_line;
+
+ if (ptr == NULL || *ptr != '\n')
+ {
+ /*
+ ** SP was not followed by the NL. Pass through the space,
+ ** then resume scanning.
+ */
+ EMIT_LINE_CONTENTS(handle, &sp, 1);
+ return scan_content_line(handle, ptr, cnt);
+ }
+
+ /* NL after a SP -- flowed line */
+
+ if (!handle->info.isdelsp)
+ EMIT_LINE_CONTENTS(handle, &sp, 1);
+
+ handle->has_previous_quote_level=1;
+ handle->previous_quote_level=handle->quote_level;
+ handle->content_handler=start_of_line;
+ return ptr ? 1:0;
+}
+
+/**************************************************************************/
+
+/*
+** At this point, the processing has reduced to the following API:
+**
+** + begin logical line
+**
+** + contents of the logical line (multiple consecutive invocations)
+**
+** + the logical line has flowed onto the next physical line
+**
+** + end of logical line
+**
+** The third one, logical line flowed, is normally used for flowed text,
+** by definition. But, it may also be get used if non-flowed text gets
+** rewrapped when broken formatting is detected.
+**
+** Provide default implementations of the other three API calls that
+** simply invoke the corresponding user callback.
+*/
+
+static void emit_line_begin(rfc3676_parser_t handle)
+{
+ if (handle->errflag == 0)
+ handle->errflag=(*handle->info.line_begin)(handle->quote_level,
+ handle->info.arg);
+}
+
+static void emit_line_flowed_wrap(rfc3676_parser_t handle)
+{
+ if (handle->errflag == 0 && handle->info.line_flowed_notify)
+ handle->errflag=(*handle->info.line_flowed_notify)
+ (handle->info.arg);
+}
+
+static void emit_line_contents(rfc3676_parser_t handle,
+ const unicode_char *uc,
+ size_t cnt)
+{
+ if (handle->errflag == 0 && cnt > 0)
+ handle->errflag=(*handle->info.line_contents)
+ (uc, cnt, handle->info.arg);
+}
+
+static void emit_line_end(rfc3676_parser_t handle)
+{
+ if (handle->errflag == 0)
+ handle->errflag=(*handle->info.line_end)(handle->info.arg);
+}
+
+/*
+** When processing a non-flowed text, handle broken mail formatters (I'm
+** looking at you, Apple Mail) that spew out quoted-printable content with
+** each decoded line forming a single paragraph. This is heuristically
+** detected by looking for lines that exceed a wrapping threshold, then
+** rewrapping them.
+**
+** Redefine the three line API calls to launder the logical line via
+** the linebreak API.
+*/
+
+static void initial_nonflowed_line(rfc3676_parser_t handle,
+ int linebreak_opportunity,
+ unicode_char ch,
+ size_t ch_width);
+
+static void initial_nonflowed_end(rfc3676_parser_t handle);
+
+static void begin_forced_rewrap(rfc3676_parser_t handle);
+
+/*
+** A non-flowed line begins. Initialize the linebreaking module.
+*/
+static void nonflowed_line_begin(rfc3676_parser_t handle)
+{
+ if (handle->lb)
+ {
+ /* Just in case */
+
+ int rc=unicode_lbc_end(handle->lb);
+
+ if (rc && handle->errflag == 0)
+ handle->errflag=rc;
+ }
+
+ if ((handle->lb=unicode_lbc_init(nonflowed_line_process, handle))
+ == NULL)
+ {
+ if (handle->errflag == 0)
+ handle->errflag=-1;
+ }
+
+ if (handle->lb)
+ unicode_lbc_set_opts(handle->lb,
+ UNICODE_LB_OPT_PRBREAK
+ | UNICODE_LB_OPT_SYBREAK);
+
+ unicode_buf_clear(&handle->nonflowed_line);
+ unicode_buf_clear(&handle->nonflowed_next_word);
+
+ handle->nonflowed_line_width=0;
+ handle->nonflowed_next_word_width=0;
+
+ handle->nonflowed_line_process=initial_nonflowed_line;
+ handle->nonflowed_line_end=initial_nonflowed_end;
+ emit_line_begin(handle); /* Fallthru - user callback */
+
+ handle->nonflowed_line_target_width=
+ handle->quote_level < NONFLOWED_WRAP_REDUCE - 20 ?
+ NONFLOWED_WRAP_REDUCE - handle->quote_level:20;
+}
+
+/*
+** Process contents of non-flowed lines. The contents are submitted to the
+** linebreaking API.
+*/
+
+static void nonflowed_line_contents(rfc3676_parser_t handle,
+ const unicode_char *uc,
+ size_t cnt)
+{
+ if (!handle->lb)
+ return;
+
+ while (cnt)
+ {
+ if (handle->errflag == 0)
+ handle->errflag=unicode_lbc_next(handle->lb, *uc);
+
+ ++uc;
+ --cnt;
+ }
+}
+
+/*
+** End of non-flowed content. Terminate the linebreaking API, then invoke
+** the current end-of-line handler.
+*/
+static void nonflowed_line_end(rfc3676_parser_t handle)
+{
+ if (handle->lb)
+ {
+ int rc=unicode_lbc_end(handle->lb);
+
+ if (rc && handle->errflag == 0)
+ handle->errflag=rc;
+
+ handle->lb=NULL;
+ }
+
+ (*handle->nonflowed_line_end)(handle);
+ emit_line_end(handle); /* FALLTHRU */
+}
+
+/*
+** Callback from the linebreaking API, gives us the next unicode character
+** and its linebreak property. Look up the unicode character's width, then
+** invoke the current handler.
+*/
+static int nonflowed_line_process(int linebreak_opportunity,
+ unicode_char ch, void *dummy)
+{
+ rfc3676_parser_t handle=(rfc3676_parser_t)dummy;
+
+ (*handle->nonflowed_line_process)(handle, linebreak_opportunity, ch,
+ unicode_wcwidth(ch));
+
+ return 0;
+}
+
+/*
+** Collecting initial nonflowed line.
+*/
+
+static void initial_nonflowed_line(rfc3676_parser_t handle,
+ int linebreak_opportunity,
+ unicode_char ch,
+ size_t ch_width)
+{
+ /*
+ ** Collect words into nonflowed_line as long as it fits within the
+ ** targeted width.
+ */
+ if (linebreak_opportunity != UNICODE_LB_NONE &&
+ handle->nonflowed_line_width + handle->nonflowed_next_word_width
+ <= handle->nonflowed_line_target_width)
+ {
+ unicode_buf_append_buf(&handle->nonflowed_line,
+ &handle->nonflowed_next_word);
+ handle->nonflowed_line_width +=
+ handle->nonflowed_next_word_width;
+
+ unicode_buf_clear(&handle->nonflowed_next_word);
+ handle->nonflowed_next_word_width=0;
+ }
+
+ /*
+ ** Add the character to the growing word.
+ **
+ ** If the line's size now exceeds the target width by quite a bit,
+ ** we've had enough!
+ */
+
+ unicode_buf_append(&handle->nonflowed_next_word, &ch, 1);
+ handle->nonflowed_next_word_width += ch_width;
+
+ if (handle->nonflowed_line_width + handle->nonflowed_next_word_width
+ > handle->nonflowed_line_target_width
+ + NONFLOWED_THRESHOLD_EXCEEDED)
+ begin_forced_rewrap(handle);
+}
+
+/*
+** End of line handler. The line did not reach its threshold, so output it.
+*/
+static void initial_nonflowed_end(rfc3676_parser_t handle)
+{
+ emit_line_contents(handle,
+ unicode_buf_ptr(&handle->nonflowed_line),
+ unicode_buf_len(&handle->nonflowed_line));
+
+ emit_line_contents(handle,
+ unicode_buf_ptr(&handle->nonflowed_next_word),
+ unicode_buf_len(&handle->nonflowed_next_word));
+}
+
+/*
+** Check for the abnormal situation where we're ready to wrap something but
+** nonflowed_line is empty because all this text did not have a linebreaking
+** opportunity.
+*/
+
+static void check_abnormal_line(rfc3676_parser_t handle)
+{
+ size_t n, i;
+ const unicode_char *p;
+
+ if (unicode_buf_len(&handle->nonflowed_line) > 0)
+ return;
+
+ /* Extreme times call for extreme measures */
+
+ n=unicode_buf_len(&handle->nonflowed_next_word);
+ p=unicode_buf_ptr(&handle->nonflowed_next_word);
+
+ for (i=n; i>0; --i)
+ {
+ if (i < n && unicode_grapheme_break(p[i-1], p[i]))
+ {
+ n=i;
+ break;
+ }
+ }
+
+ unicode_buf_append(&handle->nonflowed_line, p, n);
+ unicode_buf_remove(&handle->nonflowed_next_word, 0, n);
+
+ /*
+ ** Recalculate the width of the growing word, now.
+ */
+
+ handle->nonflowed_next_word_width=0;
+ p=unicode_buf_ptr(&handle->nonflowed_next_word);
+
+ for (i=0; i<unicode_buf_len(&handle->nonflowed_next_word); ++i)
+ handle->nonflowed_next_word_width +=
+ unicode_wcwidth(p[i]);
+}
+
+/*
+** We've decided that the line is too long, so begin rewrapping it.
+*/
+
+static void forced_rewrap_line(rfc3676_parser_t handle,
+ int linebreak_opportunity,
+ unicode_char ch,
+ size_t ch_width);
+
+static void forced_rewrap_end(rfc3676_parser_t handle);
+
+/*
+** Emit nonflowed_line as the rewrapped line. Clear the buffer.
+*/
+static void emit_rewrapped_line(rfc3676_parser_t handle)
+{
+ check_abnormal_line(handle);
+ emit_line_contents(handle, unicode_buf_ptr(&handle->nonflowed_line),
+ unicode_buf_len(&handle->nonflowed_line));
+
+ emit_line_flowed_wrap(handle);
+
+ /* nonflowed_line is now empty */
+ unicode_buf_clear(&handle->nonflowed_line);
+ handle->nonflowed_line_width=0;
+}
+
+static void begin_forced_rewrap(rfc3676_parser_t handle)
+{
+ handle->nonflowed_line_process=forced_rewrap_line;
+ handle->nonflowed_line_end=forced_rewrap_end;
+ emit_rewrapped_line(handle);
+}
+
+static void forced_rewrap_line(rfc3676_parser_t handle,
+ int linebreak_opportunity,
+ unicode_char ch,
+ size_t ch_width)
+{
+ if (linebreak_opportunity != UNICODE_LB_NONE)
+ {
+ /* Found a linebreaking opportunity */
+
+ if (handle->nonflowed_line_width
+ + handle->nonflowed_next_word_width
+ > handle->nonflowed_line_target_width)
+ {
+ /* Accumulated word is too long */
+ emit_rewrapped_line(handle);
+ }
+
+ unicode_buf_append_buf(&handle->nonflowed_line,
+ &handle->nonflowed_next_word);
+
+ handle->nonflowed_line_width +=
+ handle->nonflowed_next_word_width;
+ unicode_buf_clear(&handle->nonflowed_next_word);
+ handle->nonflowed_next_word_width=0;
+ }
+
+ /*
+ ** Check for another excessively long line.
+ */
+
+ if (handle->nonflowed_line_width == 0 &&
+ handle->nonflowed_next_word_width + ch_width
+ > handle->nonflowed_line_target_width)
+ {
+ emit_rewrapped_line(handle);
+ }
+
+ unicode_buf_append(&handle->nonflowed_next_word, &ch, 1);
+ handle->nonflowed_next_word_width += ch_width;
+}
+
+static void forced_rewrap_end(rfc3676_parser_t handle)
+{
+ initial_nonflowed_end(handle); /* Same logic, for now */
+}
+
diff --git a/rfc2045/rfc3676parser.h b/rfc2045/rfc3676parser.h
new file mode 100644
index 0000000..6125082
--- /dev/null
+++ b/rfc2045/rfc3676parser.h
@@ -0,0 +1,198 @@
+#ifndef rfc3676_h
+#define rfc3676_h
+/*
+** Copyright 2011 Double Precision, Inc. See COPYING for
+** distribution information.
+*/
+
+/*
+*/
+
+#include "rfc2045/rfc2045_config.h"
+#include "unicode/unicode.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if 0
+}
+#endif
+
+typedef struct rfc3676_parser_struct *rfc3676_parser_t;
+
+/*
+** Structure passed to rfc3676_parser_init().
+*/
+
+struct rfc3676_parser_info {
+
+ const char *charset;
+ /*
+ ** MIME charset parameter. String not used after rfc3676_parser_init()
+ ** returns.
+ */
+
+ /* MIME format flowed flag set */
+ int isflowed;
+
+ /* MIME delsp=yes flag is set */
+ int isdelsp;
+
+ /*
+ ** Callback - start of a line.
+ **
+ ** If this callback returns 0, normal parsing continues. If this
+ ** callback returns a non-0 value, parsing stops and
+ ** rfc3676_parse() or rfc3676_deinit() returns the non-0 value.
+ */
+
+ int (*line_begin)(size_t quote_level, /* Line's quote level */
+ void *arg);
+
+ /*
+ ** Callback - contents of the line, converted to unicode.
+ ** May be invoked multiple times, consecutively.
+ **
+ ** If this callback returns 0, normal parsing continues. If this
+ ** callback returns a non-0 value, parsing stops and
+ ** rfc3676_parse() or rfc3676_deinit() returns the non-0 value.
+ */
+
+ int (*line_contents)(const unicode_char *txt, /* Contents */
+ size_t txt_size,
+ /* Count of unicode chars in txt */
+ void *arg);
+ /*
+ ** Optional callback. If not NULL, it gets invoked when
+ ** a line is logically flowed into the next physical line.
+ */
+
+ int (*line_flowed_notify)(void *);
+
+ /*
+ ** End of the line's contents.
+ **
+ ** If this callback returns 0, normal parsing continues. If this
+ ** callback returns a non-0 value, parsing stops and
+ ** rfc3676_parse() or rfc3676_deinit() returns the non-0 value.
+ */
+
+ int (*line_end)(void *arg);
+
+ /* Argument passed through to the above callback methods */
+
+ void *arg;
+};
+
+/*
+** Begin parsing.
+**
+** Returns an opaque parsing handle.
+*/
+rfc3676_parser_t rfc3676parser_init(const struct rfc3676_parser_info *info);
+
+/*
+** Parse next part of rfc3676-encoded message.
+**
+** Returns non-0 value returned by any callback method, or 0 if all
+** invoked callback methods returned 0.
+*/
+
+int rfc3676parser(rfc3676_parser_t handle,
+ const char *txt,
+ size_t txt_cnt);
+
+/*
+** End parsing.
+**
+** The handle gets destroyed, and the parsing finishes.
+**
+** NOTE: rfc3676_deinit() WILL LIKELY invoke some leftover callback methods.
+**
+** Returns non-0 value returned by any callback method, or 0 if all
+** invoked callback methods returned 0.
+*/
+
+int rfc3676parser_deinit(rfc3676_parser_t handle,
+
+ /*
+ ** Optional, if not NULL, set to indicate unicode
+ ** error.
+ */
+ int *errptr);
+
+#if 0
+{
+#endif
+
+#ifdef __cplusplus
+}
+
+namespace mail {
+
+ extern "C" int tpp_trampoline_line_begin(size_t, void *);
+
+ extern "C" int tpp_trampoline_line_contents(const unicode_char *,
+ size_t, void *);
+
+ extern "C" int tpp_trampoline_line_flowed_notify(void *);
+
+ extern "C" int tpp_trampoline_line_end(void *);
+
+ /*
+ ** C++ binding for the parser logic
+ */
+ class textplainparser {
+
+ rfc3676_parser_t handle;
+
+ public:
+ textplainparser();
+ ~textplainparser();
+
+ /*
+ ** Begin parsing. Returns FALSE if the parsing could
+ ** not be initialized (probably unknown charset).
+ */
+ bool begin(const std::string &charset,
+ bool flowed,
+ bool delsp);
+
+ void end(
+ /*
+ ** Set to true if a unicode conversion error occured.
+ */
+ bool &unicode_errflag);
+
+ void end()
+ {
+ bool dummy;
+
+ return end(dummy);
+ }
+
+ /* Feed raw contents to be parsed */
+ void operator<<(const std::string &text)
+ {
+ if (handle)
+ rfc3676parser(handle, text.c_str(),
+ text.size());
+ }
+
+
+ virtual void line_begin(size_t);
+
+ virtual void line_contents(const unicode_char *,
+ size_t);
+
+ virtual void line_flowed_notify();
+
+ virtual void line_end();
+ };
+}
+#endif
+
+#endif
diff --git a/rfc2045/rfc3676parsercpp.C b/rfc2045/rfc3676parsercpp.C
new file mode 100644
index 0000000..cb67993
--- /dev/null
+++ b/rfc2045/rfc3676parsercpp.C
@@ -0,0 +1,117 @@
+/*
+** Copyright 2011 Double Precision, Inc.
+** See COPYING for distribution information.
+**
+*/
+
+#include "rfc3676parser.h"
+
+extern "C" {
+
+ int mail::tpp_trampoline_line_begin(size_t quote_level, void *arg)
+ {
+ reinterpret_cast<mail::textplainparser *>(arg)
+ ->line_begin(quote_level);
+
+ return 0;
+ }
+
+ int mail::tpp_trampoline_line_contents(const unicode_char *ptr,
+ size_t cnt, void *arg)
+ {
+ reinterpret_cast<mail::textplainparser *>(arg)
+ ->line_contents(ptr, cnt);
+ return 0;
+ }
+
+ int mail::tpp_trampoline_line_flowed_notify(void *arg)
+ {
+ reinterpret_cast<mail::textplainparser *>(arg)
+ ->line_flowed_notify();
+
+ return 0;
+ }
+
+ int mail::tpp_trampoline_line_end(void *arg)
+ {
+ reinterpret_cast<mail::textplainparser *>(arg)
+ ->line_end();
+ return 0;
+ }
+}
+
+mail::textplainparser::textplainparser() : handle(NULL)
+{
+}
+
+mail::textplainparser::~textplainparser()
+{
+ end();
+}
+
+bool mail::textplainparser::begin(const std::string &charset,
+ bool flowed,
+ bool delsp)
+{
+ end();
+
+ struct rfc3676_parser_info info=rfc3676_parser_info();
+
+ info.charset=charset.c_str();
+ info.isflowed=flowed == true;
+ info.isdelsp=delsp == true;
+
+ info.line_begin=&tpp_trampoline_line_begin;
+ info.line_contents=&tpp_trampoline_line_contents;
+ info.line_flowed_notify=&tpp_trampoline_line_flowed_notify;
+ info.line_end=&tpp_trampoline_line_end;
+
+ info.arg=reinterpret_cast<void *>(this);
+
+ if ((handle=rfc3676parser_init(&info)) == NULL)
+ return false;
+
+ return true;
+}
+
+void mail::textplainparser::end(bool &unicode_errflag)
+{
+ int rc=0;
+
+ if (handle)
+ {
+ rfc3676parser_deinit(handle, &rc);
+ handle=NULL;
+ }
+
+ unicode_errflag=rc != 0;
+}
+
+void mail::textplainparser::line_begin(size_t quote_level)
+{
+ if (quote_level)
+ {
+ std::vector<unicode_char> vec;
+
+ vec.reserve(quote_level+1);
+ vec.insert(vec.end(), quote_level, '>');
+ vec.push_back(' ');
+ line_contents(&vec[0], vec.size());
+ }
+}
+
+void mail::textplainparser::line_contents(const unicode_char *data,
+ size_t cnt)
+{
+}
+
+void mail::textplainparser::line_flowed_notify()
+{
+}
+
+void mail::textplainparser::line_end()
+{
+ unicode_char nl='\n';
+
+ line_contents(&nl, 1);
+}
diff --git a/rfc2045/testrfc3676parser.c b/rfc2045/testrfc3676parser.c
new file mode 100644
index 0000000..1a8c268
--- /dev/null
+++ b/rfc2045/testrfc3676parser.c
@@ -0,0 +1,70 @@
+/*
+** Copyright 2011 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#include "rfc3676parser.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+static int line_begin(size_t quote_level, void *arg)
+{
+ printf("[%d: ", (int)quote_level);
+ return 0;
+}
+
+static int line_contents(const unicode_char *txt,
+ size_t txt_size,
+ void *arg)
+{
+ while (txt_size--)
+ putchar(*txt++);
+ return 0;
+}
+
+static int line_flowed_notify(void *arg)
+{
+ printf("...");
+ return 0;
+}
+
+static int line_end(void *arg)
+{
+ printf("]\n");
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct rfc3676_parser_info info;
+ int n=0;
+ char buf[BUFSIZ];
+ rfc3676_parser_t parser;
+
+ if (argc > 1)
+ n=atoi(argv[1]);
+
+ memset(&info, 0, sizeof(info));
+
+ info.charset="utf-8";
+
+ info.isflowed=n != 0;
+ info.isdelsp= n == 2;
+
+ info.line_begin=line_begin;
+ info.line_contents=line_contents;
+ info.line_flowed_notify=line_flowed_notify;
+ info.line_end=line_end;
+
+ if ((parser=rfc3676parser_init(&info)) != NULL)
+ {
+ while (fgets(buf, sizeof(buf), stdin))
+ rfc3676parser(parser, buf, strlen(buf));
+ rfc3676parser_deinit(parser, NULL);
+ printf("\n");
+ }
+
+ return (0);
+}
diff --git a/rfc2045/testrfc3676parsersuite b/rfc2045/testrfc3676parsersuite
new file mode 100644
index 0000000..d2d826b
--- /dev/null
+++ b/rfc2045/testrfc3676parsersuite
@@ -0,0 +1,152 @@
+#! /bin/sh
+
+./testrfc3676parser <<EOF
+ line cr
+line
+linec
+line
+EOF
+
+./testrfc3676parser <<EOF
+Fixed
+Flowed 1
+Flowed 2
+> QuoteDepth1
+> QuoteDepth1
+> QuoteDepth1Flowed
+> QuoteDepth1Fixed
+>> QuoteDepth2Flowed
+>>QuoteDepth2Flowed
+> QuoteDepth1Fixed
+ Stuffed
+EOF
+
+./testrfc3676parser 1 <<EOF
+Fixed
+Flowed 1
+Flowed 2
+> QuoteDepth1
+> QuoteDepth1
+> QuoteDepth1Flowed
+> QuoteDepth1Fixed
+>> QuoteDepth2Flowed
+>>QuoteDepth2Flowed
+> QuoteDepth1Fixed
+ Stuffed
+EOF
+
+( echo "Fixed"; echo "Fixed" | tr -d '\010') | ./testrfc3676parser
+
+( echo "Flowed "; echo "Fixed" | tr -d '\010') | ./testrfc3676parser 1
+
+( echo "Flowed "; echo "Flowed " | tr -d '\010') | ./testrfc3676parser 1
+
+( echo "Flowed "; echo ">>" | tr -d '\010') | ./testrfc3676parser 1
+
+( echo "Flowed "; echo ">> " | tr -d '\010') | ./testrfc3676parser 1
+
+( echo "Flowed "; echo ">>" | tr -d '\010') | ./testrfc3676parser 2
+
+( echo "Flowed "; echo ">> " | tr -d '\010') | ./testrfc3676parser 2
+
+./testrfc3676parser 1 <<EOF
+Flowed text
+Fixed line
+
+Next flowed section
+EOF
+
+./testrfc3676parser 1 <<EOF
+Flowed text
+Spurious flowed line
+
+Next flowed section
+EOF
+
+
+./testrfc3676parser <<EOF
+fixedline
+--
+signature
+EOF
+
+./testrfc3676parser <<EOF
+flowed line
+flowed line
+--
+signature
+EOF
+
+./testrfc3676parser <<EOF
+fixedline
+> --
+signature
+EOF
+
+./testrfc3676parser <<EOF
+flowed line
+flowed line
+> --
+signature
+EOF
+
+
+./testrfc3676parser 1 <<EOF
+fixedline
+--
+signature
+EOF
+
+./testrfc3676parser 1 <<EOF
+flowed line
+flowed line
+--
+signature
+EOF
+
+./testrfc3676parser 1 <<EOF
+fixedline
+> --
+signature
+EOF
+
+./testrfc3676parser 1 <<EOF
+flowed line
+flowed line
+> --
+signature
+EOF
+
+( echo "Fixed"; echo "-- " | tr -d '\010') | ./testrfc3676parser
+
+( echo "Flowed "; echo "-- " | tr -d '\010') | ./testrfc3676parser
+
+
+( echo "Fixed"; echo "-- " | tr -d '\010') | ./testrfc3676parser 1
+
+( echo "Flowed "; echo "-- " | tr -d '\010') | ./testrfc3676parser 1
+
+
+./testrfc3676parser <<EOF
+fixed
+-a-
+flowed
+flowed
+-b-
+EOF
+
+./testrfc3676parser 1 <<EOF
+fixed
+-a-
+flowed
+flowed
+-b-
+EOF
+
+./testrfc3676parser 0 <<EOF
+1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890
+EOF
+
+./testrfc3676parser 0 <<EOF
+123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+EOF
diff --git a/rfc2045/testrfc3676parsersuite.txt b/rfc2045/testrfc3676parsersuite.txt
new file mode 100644
index 0000000..f523981
--- /dev/null
+++ b/rfc2045/testrfc3676parsersuite.txt
@@ -0,0 +1,113 @@
+[0: line cr]
+[0: line]
+[0: linec]
+[0: line]
+
+[0: Fixed]
+[0: Flowed 1 ]
+[0: Flowed 2 ]
+[0: > QuoteDepth1]
+[0: > QuoteDepth1]
+[0: > QuoteDepth1Flowed ]
+[0: > QuoteDepth1Fixed]
+[0: >> QuoteDepth2Flowed ]
+[0: >>QuoteDepth2Flowed ]
+[0: > QuoteDepth1Fixed]
+[0: Stuffed]
+
+[0: Fixed]
+[0: Flowed 1 ...Flowed 2 ]
+[1: QuoteDepth1]
+[1: QuoteDepth1]
+[1: QuoteDepth1Flowed ...QuoteDepth1Fixed]
+[2: QuoteDepth2Flowed ...QuoteDepth2Flowed ]
+[1: QuoteDepth1Fixed]
+[0: Stuffed]
+
+[0: Fixed]
+[0: Fixed]
+
+[0: Flowed ...Fixed]
+
+[0: Flowed ...Flowed ]
+
+[0: Flowed ]
+[2: ]
+
+[0: Flowed ]
+[2: ]
+
+[0: Flowed ]
+[2: ]
+
+[0: Flowed ]
+[2: ]
+
+[0: Flowed text ...Fixed line]
+[0: ]
+[0: Next flowed section]
+
+[0: Flowed text ...Spurious flowed line ]
+[0: ]
+[0: Next flowed section]
+
+[0: fixedline]
+[0: -- ]
+[0: signature]
+
+[0: flowed line ]
+[0: flowed line ]
+[0: -- ]
+[0: signature]
+
+[0: fixedline]
+[0: > -- ]
+[0: signature]
+
+[0: flowed line ]
+[0: flowed line ]
+[0: > -- ]
+[0: signature]
+
+[0: fixedline]
+[0: -- ]
+[0: signature]
+
+[0: flowed line ...flowed line ]
+[0: -- ]
+[0: signature]
+
+[0: fixedline]
+[1: -- ]
+[0: signature]
+
+[0: flowed line ...flowed line ]
+[1: -- ]
+[0: signature]
+
+[0: Fixed]
+[0: -- ]
+
+[0: Flowed ]
+[0: -- ]
+
+[0: Fixed]
+[0: -- ]
+
+[0: Flowed ]
+[0: -- ]
+
+[0: fixed]
+[0: -a-]
+[0: flowed ]
+[0: flowed ]
+[0: -b-]
+
+[0: fixed]
+[0: -a-]
+[0: flowed ...flowed ...-b-]
+
+[0: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 ...1234567890 1234567890 1234567890 1234567890 1234567890 1234567890]
+
+[0: 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234...1678901234567890123456789012345678901234567890123456789012345678901234567...190123456789012345678901234567890123456789012345678901234567890]
+
diff --git a/rfc2045/testsuite b/rfc2045/testsuite
new file mode 100644
index 0000000..4eb9656
--- /dev/null
+++ b/rfc2045/testsuite
@@ -0,0 +1,227 @@
+LANG=en_US.UTF-8
+LC_ALL=en_US.UTF-8
+unset LC_CTYPE
+unset LC_NUMERIC
+unset LC_TIME
+unset LC_COLLATE
+unset LC_MONETARY
+unset LC_MESSAGES
+unset LC_PAPER
+unset LC_NAME
+unset LC_ADDRESS
+unset LC_TELEPHONE
+unset LC_MEASUREMENT
+unset LC_IDENTIFICATION
+unset LC_ALL
+unset CHARSET
+unset MM_CHARSET
+export LANG
+export LC_ALL
+
+cat >testsuite.dat <<EOF
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary=aaa
+
+
+--aaa
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+=C3=81BC
+
+--aaa
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 8bit
+
+ABC
+
+--aaa
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+=C1
+
+--aaa
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+--aaa
+Content-Type: message/rfc822
+
+Subject: testing
+
+test message
+
+--aaa---
+EOF
+LANG=en_US.utf-8 ./reformime -r <testsuite.dat | sed 's/=_.*//;s/X-Mime-Autoconverted:.*//'
+LANG=en_US.utf-8 ./reformime -r7 <testsuite.dat | sed 's/=_.*//;s/X-Mime-Autoconverted:.*//'
+LANG=en_US.utf-8 ./reformime -r8 <testsuite.dat | sed 's/=_.*//;s/X-Mime-Autoconverted:.*//'
+
+cat >testsuite.dat <<EOF
+From: nobody
+To: nobody
+Subject: this is
+ a test
+
+testing
+EOF
+./headercheck 0 "" <testsuite.dat
+echo "--"
+./headercheck 2 "" <testsuite.dat
+echo "--"
+./headercheck 1 "" <testsuite.dat
+
+cat >testsuite.dat <<EOF
+From: nobody
+To: nobody
+Mime-Version: 1.0
+Content-Type: message/rfc822
+
+From: nobody@example.com
+To: nobody@example.com
+Message-ID: <a@b>
+Subject: Example
+ subject
+
+EOF
+echo "---"
+./headercheck 0 "1.1" <testsuite.dat
+echo "--"
+./headercheck 2 "1.1" <testsuite.dat
+echo "--"
+./headercheck 1 "1.1" <testsuite.dat
+
+cat >testsuite.dat <<EOF
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary=aaa
+
+
+--aaa
+Content-Type: text/plain; name*=n%41me
+Content-Disposition: attachment; filename*1=n%41me;
+ filename*2*=".%65xe";
+ filename*0*=iso8859-1'en_US'file
+
+Testing
+
+--aaa
+Content-Type: text/plain; name=name
+Content-Disposition: attachment; filename=name.exe; filename*1=n%41me;
+ filename*2*=".%65xe";
+ filename*0*=iso8859-1'en_US'file
+
+Testing
+
+--aaa---
+EOF
+LANG=en_US.utf-8 ./reformime -i <testsuite.dat | sed 's/^charset:.*/charset: iso-8859-1/'
+
+echo "foo" >confmdtest
+
+./makemime -c application/octet-stream -C utf-8 -N footest confmdtest
+
+./makemime -c application/octet-stream -C utf-8 -N footest:1 confmdtest
+./makemime -c application/octet-stream -C utf-8 -N f:::::::::::::::::::::::::1 confmdtest
+
+LANG=en_US.utf-8 ./reformime -h '=?iso-8859-1?Q?H=F3la!?= test =?iso-8859-1?Q?H=F3la!_?= =?iso-8859-1?Q?H=F3la!?= test'
+rm -f confmdtest
+
+cat >testsuite.dat <<EOF
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Description: =?iso-8859-1?Q?H=F3la!?= test =?iso-8859-1?Q?H=F3la!_?=
+ =?iso-8859-1?Q?H=F3la!?= test
+
+testing
+EOF
+LANG=en_US.utf-8 ./reformime -i <./testsuite.dat
+LANG=en_US.utf-8 ./reformime -H '=?iso-8859-1?Q?H=F3la!?= test <test1@example.com>'
+LANG=en_US.utf-8 ./reformime -H '=?iso-8859-1?Q?H=F3la=22?= test <test1@example.com>'
+LANG=en_US.utf-8 ./reformime -O 'Hóla <user@Hóla.example.com>, user@example.com (Hóla), John Smith <user@example.com>, user2@example.com, "John Smith (Accounting)" <user3@example.com>, "Hóla Smith (Accounting)" <user4@example.com>, john smith <user@hóla.example.com>'
+
+LANG=en_US.utf-8 ./reformime -H '=?UTF-8?B?SMOzbGE=?= <user@xn--hla-gna.example.com>, =?UTF-8?B?SMOzbGE=?= <user@example.com>, John Smith <user@example.com>, user2@example.com, "John Smith (Accounting)" <user3@example.com>, =?UTF-8?B?SMOzbGE=?= Smith =?UTF-8?B?KEFjY291bnRpbmcp?= <user4@example.com>, john smith <user@xn--hla-gna.example.com>'
+
+LANG=en_US.utf-8 ./reformime -H 'list: address1 <address1@example.com>, address2 <address2@example.com>;'
+
+LANG=en_US.utf-8 ./reformime -O 'list: address1 <address1@example.com>, address2 <address2@example.com>;'
+
+./reformime -u <<EOF
+From: Sam =?iso-8859-1?B?SOhsbG8=?= <nobody@xn--8ca.example.com>
+Subject: =?iso-8859-1?B?SOhsbG8=?=
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="aaa"
+
+Ignore this
+
+--aaa
+Content-Type: message/rfc822
+
+Subject: embedded message
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="bbb"
+
+Ignore this too
+
+--bbb
+Content-Type: TEXT/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+H=C2llo!
+
+--bbb--
+
+--aaa--
+EOF
+./reformime -u <<EOF
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset="utf-7"
+
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AMM
+EOF
+
+./reformime -u >/dev/null <<EOF
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset="utf-7"
+
+AA+AM
+BB
+EOF
+
+x="XXXXXXXX"
+x="
+$x$x$x$x$x$x$x$x"
+x="$x$x$x$x"
+x="$x$x$x$x"
+x="$x$x"
+
+(cat <<EOF
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset='iso-8859-15'
+$x
+EOF
+) | tr 'X' '\244' | ./reformime -u | tail -32
+
+rm testsuite.dat
diff --git a/rfc2045/testsuite.txt.idn b/rfc2045/testsuite.txt.idn
new file mode 100644
index 0000000..1be89bc
--- /dev/null
+++ b/rfc2045/testsuite.txt.idn
@@ -0,0 +1,332 @@
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+=C3=81BC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+=C1
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+=C3=81BC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+=C1
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+
+=C3=81
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+ÁBC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 8bit
+
+
+
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Header: from
+Value: nobody
+Header: to
+Value: nobody
+Header: subject
+Value: this is a test
+--
+Header: from
+Value: nobody
+Header: to
+Value: nobody
+Header: subject
+Value: this is
+ a test
+--
+Header: From
+Value: nobody
+Header: To
+Value: nobody
+Header: Subject
+Value: this is a test
+---
+Header: from
+Value: nobody@example.com
+Header: to
+Value: nobody@example.com
+Header: message-id
+Value: <a@b>
+Header: subject
+Value: Example subject
+--
+Header: from
+Value: nobody@example.com
+Header: to
+Value: nobody@example.com
+Header: message-id
+Value: <a@b>
+Header: subject
+Value: Example
+ subject
+--
+Header: From
+Value: nobody@example.com
+Header: To
+Value: nobody@example.com
+Header: Message-ID
+Value: <a@b>
+Header: Subject
+Value: Example subject
+section: 1
+content-type: multipart/mixed
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+starting-pos: 0
+starting-pos-body: 63
+ending-pos: 433
+line-count: 21
+body-line-count: 18
+
+section: 1.1
+content-type: text/plain
+content-name: nAme
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+content-disposition: attachment
+content-disposition-filename: filen%41me.exe
+starting-pos: 70
+starting-pos-body: 227
+ending-pos: 235
+line-count: 6
+body-line-count: 1
+
+section: 1.2
+content-type: text/plain
+content-name: name
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+content-disposition: attachment
+content-disposition-filename: filen%41me.exe
+starting-pos: 242
+starting-pos-body: 415
+ending-pos: 423
+line-count: 6
+body-line-count: 1
+
+Content-Type: application/octet-stream; charset="utf-8";
+ name="footest"
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: application/octet-stream; charset="utf-8";
+ name*0*=utf-8''footest%3A1
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: application/octet-stream; charset="utf-8";
+ name*0*=utf-8''f%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A;
+ name*1*=%3A%3A%3A%3A%3A%3A1
+Content-Transfer-Encoding: 7bit
+
+foo
+Hóla! test Hóla! Hóla! test
+section: 1
+content-type: text/plain
+content-transfer-encoding: 8bit
+charset: utf-8
+content-description: Hóla! test Hóla! Hóla! test
+starting-pos: 0
+starting-pos-body: 188
+ending-pos: 196
+line-count: 7
+body-line-count: 1
+
+Hóla! test <test1@example.com>
+"Hóla\" test" <test1@example.com>
+=?UTF-8?B?SMOzbGE=?= <user@xn--hla-gna.example.com>,
+ =?UTF-8?B?SMOzbGE=?= <user@example.com>,
+ John Smith <user@example.com>,
+ user2@example.com,
+ "John Smith (Accounting)" <user3@example.com>,
+ =?UTF-8?B?SMOzbGE=?= Smith =?UTF-8?B?KEFjY291bnRpbmcp?= <user4@example.com>,
+ john smith <user@xn--hla-gna.example.com>
+Hóla <user@hóla.example.com>,
+Hóla <user@example.com>,
+John Smith <user@example.com>,
+user2@example.com,
+"John Smith (Accounting)" <user3@example.com>,
+"Hóla Smith (Accounting)" <user4@example.com>,
+john smith <user@hóla.example.com>
+list:
+address1 <address1@example.com>,
+address2 <address2@example.com>;
+list:
+ address1 <address1@example.com>,
+ address2 <address2@example.com>;
+From: Sam Hèllo <nobody@è.example.com>
+Subject: Hèllo
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="aaa"
+Content-Type: message/rfc822
+Subject: embedded message
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="bbb"
+Content-Type: TEXT/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+HÂllo!
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset="utf-7"
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÃ
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
diff --git a/rfc2045/testsuite.txt.noidn b/rfc2045/testsuite.txt.noidn
new file mode 100644
index 0000000..a8bf485
--- /dev/null
+++ b/rfc2045/testsuite.txt.noidn
@@ -0,0 +1,332 @@
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+=C3=81BC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+=C1
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+=C3=81BC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+=C1
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+
+=C3=81
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Subject: test
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="
+
+This is a MIME-formatted message. If you see this text it means that your
+E-mail software does not support MIME-formatted messages.
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+ÁBC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+
+
+ABC
+
+--
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 8bit
+
+
+
+
+--
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+
+--
+Content-Type: message/rfc822
+
+Subject: testing
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+test message
+
+
+--
+Header: from
+Value: nobody
+Header: to
+Value: nobody
+Header: subject
+Value: this is a test
+--
+Header: from
+Value: nobody
+Header: to
+Value: nobody
+Header: subject
+Value: this is
+ a test
+--
+Header: From
+Value: nobody
+Header: To
+Value: nobody
+Header: Subject
+Value: this is a test
+---
+Header: from
+Value: nobody@example.com
+Header: to
+Value: nobody@example.com
+Header: message-id
+Value: <a@b>
+Header: subject
+Value: Example subject
+--
+Header: from
+Value: nobody@example.com
+Header: to
+Value: nobody@example.com
+Header: message-id
+Value: <a@b>
+Header: subject
+Value: Example
+ subject
+--
+Header: From
+Value: nobody@example.com
+Header: To
+Value: nobody@example.com
+Header: Message-ID
+Value: <a@b>
+Header: Subject
+Value: Example subject
+section: 1
+content-type: multipart/mixed
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+starting-pos: 0
+starting-pos-body: 63
+ending-pos: 433
+line-count: 21
+body-line-count: 18
+
+section: 1.1
+content-type: text/plain
+content-name: nAme
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+content-disposition: attachment
+content-disposition-filename: filen%41me.exe
+starting-pos: 70
+starting-pos-body: 227
+ending-pos: 235
+line-count: 6
+body-line-count: 1
+
+section: 1.2
+content-type: text/plain
+content-name: name
+content-transfer-encoding: 8bit
+charset: iso-8859-1
+content-disposition: attachment
+content-disposition-filename: filen%41me.exe
+starting-pos: 242
+starting-pos-body: 415
+ending-pos: 423
+line-count: 6
+body-line-count: 1
+
+Content-Type: application/octet-stream; charset="utf-8";
+ name="footest"
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: application/octet-stream; charset="utf-8";
+ name*0*=utf-8''footest%3A1
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: application/octet-stream; charset="utf-8";
+ name*0*=utf-8''f%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A%3A;
+ name*1*=%3A%3A%3A%3A%3A%3A1
+Content-Transfer-Encoding: 7bit
+
+foo
+Hóla! test Hóla! Hóla! test
+section: 1
+content-type: text/plain
+content-transfer-encoding: 8bit
+charset: utf-8
+content-description: Hóla! test Hóla! Hóla! test
+starting-pos: 0
+starting-pos-body: 188
+ending-pos: 196
+line-count: 7
+body-line-count: 1
+
+Hóla! test <test1@example.com>
+"Hóla\" test" <test1@example.com>
+=?UTF-8?B?SMOzbGE=?= <user@Hóla.example.com>,
+ =?UTF-8?B?SMOzbGE=?= <user@example.com>,
+ John Smith <user@example.com>,
+ user2@example.com,
+ "John Smith (Accounting)" <user3@example.com>,
+ =?UTF-8?B?SMOzbGE=?= Smith =?UTF-8?B?KEFjY291bnRpbmcp?= <user4@example.com>,
+ john smith <user@hóla.example.com>
+Hóla <user@xn--hla-gna.example.com>,
+Hóla <user@example.com>,
+John Smith <user@example.com>,
+user2@example.com,
+"John Smith (Accounting)" <user3@example.com>,
+"Hóla Smith (Accounting)" <user4@example.com>,
+john smith <user@xn--hla-gna.example.com>
+list:
+address1 <address1@example.com>,
+address2 <address2@example.com>;
+list:
+ address1 <address1@example.com>,
+ address2 <address2@example.com>;
+From: Sam Hèllo <nobody@xn--8ca.example.com>
+Subject: Hèllo
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="aaa"
+Content-Type: message/rfc822
+Subject: embedded message
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="bbb"
+Content-Type: TEXT/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+HÂllo!
+Subject: test
+Mime-Version: 1.0
+Content-Type: text/plain; charset="utf-7"
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÃ
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€
diff --git a/rfc2045/testsuitemm b/rfc2045/testsuitemm
new file mode 100644
index 0000000..8b861b7
--- /dev/null
+++ b/rfc2045/testsuitemm
@@ -0,0 +1,57 @@
+LANG=en_US.UTF-8
+LC_ALL=en_US.UTF-8
+unset LC_CTYPE
+unset LC_NUMERIC
+unset LC_TIME
+unset LC_COLLATE
+unset LC_MONETARY
+unset LC_MESSAGES
+unset LC_PAPER
+unset LC_NAME
+unset LC_ADDRESS
+unset LC_TELEPHONE
+unset LC_MEASUREMENT
+unset LC_IDENTIFICATION
+unset LC_ALL
+unset CHARSET
+unset MM_CHARSET
+export LANG
+export LC_ALL
+
+echo "foo" >confmdtest
+
+./makemime -c application/octet-stream -C utf-8 -N footest confmdtest
+./makemime -c auto -C utf-8 -N text7bit confmdtest
+echo "foox" | tr 'x' '\000' >confmdtest
+./makemime -c auto -C utf-8 -N binarybase64 confmdtest
+echo "Tést" >confmdtest
+./makemime -c auto -C utf-8 -N text8bit confmdtest
+echo "Test Test Test Test Tést" >confmdtest
+./makemime -c auto -C utf-8 -N text8bit confmdtest
+
+x="1234567890"
+x="$x$x$x$x$x$x$x$x$x$x"
+x="$x$x$x$x$x$x$x$x$x$x"
+
+echo $x >confmdtest
+./makemime -c auto -C utf-8 -N textqp confmdtest
+
+x="ééééé"
+x="$x$x$x$x$x$x$x$x$x$x"
+x="$x$x$x$x$x$x$x$x$x$x"
+echo $x >confmdtest
+./makemime -c auto -C utf-8 -N textbase64 confmdtest
+rm -f confmdtest
+
+./reformime -o 'дададададададададада'
+./reformime -h '=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?='
+
+./reformime -o 'дададададададададада дададададададададада'
+./reformime -h '=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsCDQtNCw0LQ=?= =?UTF-8?B?0LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?='
+
+./reformime -o 'дададададададададада foo дададададададададада'
+./reformime -h '=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?= foo =?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?='
+
+./reformime -o 'дададададададададада foo bar дададададададададада'
+./reformime -h '=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?= foo bar =?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?='
+
diff --git a/rfc2045/testsuitemm.txt b/rfc2045/testsuitemm.txt
new file mode 100644
index 0000000..a68e3b9
--- /dev/null
+++ b/rfc2045/testsuitemm.txt
@@ -0,0 +1,73 @@
+Content-Type: application/octet-stream; charset="utf-8";
+ name="footest"
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: text/plain; charset="utf-8";
+ name="text7bit"
+Content-Transfer-Encoding: 7bit
+
+foo
+Content-Type: application/octet-stream;
+ name="binarybase64"
+Content-Transfer-Encoding: base64
+
+Zm9vAAo=
+Content-Type: text/plain; charset="utf-8";
+ name="text8bit"
+Content-Transfer-Encoding: 8bit
+
+Tést
+Content-Type: text/plain; charset="utf-8";
+ name="text8bit"
+Content-Transfer-Encoding: 8bit
+
+Test Test Test Test Tést
+Content-Type: text/plain; charset="utf-8";
+ name="textqp"
+Content-Transfer-Encoding: quoted-printable
+
+1234567890123456789012345678901234567890123456789012345678901234567890123=
+4567890123456789012345678901234567890123456789012345678901234567890123456=
+7890123456789012345678901234567890123456789012345678901234567890123456789=
+0123456789012345678901234567890123456789012345678901234567890123456789012=
+3456789012345678901234567890123456789012345678901234567890123456789012345=
+6789012345678901234567890123456789012345678901234567890123456789012345678=
+9012345678901234567890123456789012345678901234567890123456789012345678901=
+2345678901234567890123456789012345678901234567890123456789012345678901234=
+5678901234567890123456789012345678901234567890123456789012345678901234567=
+8901234567890123456789012345678901234567890123456789012345678901234567890=
+1234567890123456789012345678901234567890123456789012345678901234567890123=
+4567890123456789012345678901234567890123456789012345678901234567890123456=
+7890123456789012345678901234567890123456789012345678901234567890123456789=
+012345678901234567890123456789012345678901234567890
+Content-Type: text/plain; charset="utf-8";
+ name="textbase64"
+Content-Transfer-Encoding: base64
+
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOp
+w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nD
+qcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqQo=
+=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?=
+дададададададададада
+=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsCDQtNCw0LQ=?= =?UTF-8?B?0LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?=
+дададададададададада дададададададададада
+=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?= foo =?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?=
+дададададададададада foo дададададададададада
+=?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?= foo bar =?UTF-8?B?0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsNC00LDQtNCw0LTQsA==?=
+дададададададададада foo bar дададададададададада