diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /rfc2045 | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'rfc2045')
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">@&<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">@&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 "&<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 +"&<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">@&<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ó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 <rfc822.h> +#include <rfc2045.h> + +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 <rfc2045.h> + +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->next</structfield> gets you +the second MIME section, <structfield>firstpart->next->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, + &content_type, &content_transfer_encoding, + &content_character_set); + +off_t start_pos, end_pos, start_body, nlines, nbodylines; + +void rfc2045_mimepos(const struct rfc2045 *ptr, + &start_pos, &end_pos, &start_body, &nlines, + &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->content_disposition; + +char *charset; +char *language; +char *value; + +int error; + +error=rfc2231_decodeType(rfc, "name", &charset, + &language, &value); +error=rfc2231_decodeDisposition(rfc, "name", &charset, + &language, &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, "eChar, 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, ¶mList) < 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 дададададададададада |
