X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=uip%2Fmhbuild.c;h=12e654897dd30147b3383d95d1d38afa97f710ef;hp=1c5abdc3c42c3211d7b039be7009a12612ae28fe;hb=32b2354dbaf4bf934936eb5b102a4a3d2fdd209a;hpb=9a33ff618b5901a3af815650d4b84d39ee96e529 diff --git a/uip/mhbuild.c b/uip/mhbuild.c index 1c5abdc..12e6548 100644 --- a/uip/mhbuild.c +++ b/uip/mhbuild.c @@ -1,387 +1,1643 @@ +/* +** mhbuild.c -- expand/translate MIME composition files +** +** This code is Copyright (c) 2002, by the authors of nmh. See the +** COPYRIGHT file in the root directory of the nmh distribution for +** complete copyright information. +*/ /* - * mhbuild.c -- expand/translate MIME composition files - * - * $Id$ - * - * This code is Copyright (c) 2002, by the authors of nmh. See the - * COPYRIGHT file in the root directory of the nmh distribution for - * complete copyright information. - */ +** This code was originally part of mhn.c. I split it into +** a separate program (mhbuild.c). But the code still has some of +** the mhn.c code in it. This program needs additional +** streamlining and removal of unneeded code. +*/ #include #include #include -#include #include #include -#include #include #include #include -#include #include -#ifdef HAVE_SYS_WAIT_H -# include +#ifdef HAVE_SYS_TIME_H +# include #endif +#include static struct swit switches[] = { -#define CHECKSW 0 - { "check", 0 }, -#define NCHECKSW 1 - { "nocheck", 0 }, -#define EBCDICSW 2 - { "ebcdicsafe", 0 }, -#define NEBCDICSW 3 - { "noebcdicsafe", 0 }, -#define HEADSW 4 - { "headers", 0 }, -#define NHEADSW 5 - { "noheaders", 0 }, -#define LISTSW 6 - { "list", 0 }, -#define NLISTSW 7 - { "nolist", 0 }, -#define SIZESW 8 - { "realsize", 0 }, -#define NSIZESW 9 - { "norealsize", 0 }, -#define RFC934SW 10 - { "rfc934mode", 0 }, -#define NRFC934SW 11 - { "norfc934mode", 0 }, -#define VERBSW 12 - { "verbose", 0 }, -#define NVERBSW 13 - { "noverbose", 0 }, -#define RCACHESW 14 - { "rcache policy", 0 }, -#define WCACHESW 15 - { "wcache policy", 0 }, -#define VERSIONSW 16 - { "version", 0 }, -#define HELPSW 17 - { "help", 0 }, -#define DEBUGSW 18 - { "debug", -5 }, - { NULL, 0 } +#define VERBSW 0 + { "verbose", 0 }, +#define NVERBSW 1 + { "noverbose", 0 }, +#define VERSIONSW 2 + { "Version", 0 }, +#define HELPSW 3 + { "help", 0 }, +#define DEBUGSW 4 + { "debug", -5 }, + { NULL, 0 } }; -/* mhbuildsbr.c */ -extern int checksw; -extern char *tmp; /* directory to place temp files */ +/* +** Directory to place tmp files. This must +** be set before these routines are called. +*/ +char *tmp; + +pid_t xpid = 0; + +static char prefix[] = "----- =_aaaaaaaaaa"; + + +/* +** prototypes +*/ + +/* mhmisc.c */ +int make_intermediates(char *); +void content_error(char *, CT, char *, ...); + +/* mhfree.c */ +void free_content(CT); +void free_ctinfo(CT); +void free_encoding(CT, int); + +/* +** static prototypes +*/ +static int init_decoded_content(CT); +static char *fgetstr(char *, int, FILE *); +static int user_content(FILE *, char *, char *, CT *); +static void set_id(CT, int); +static int compose_content(CT); +static int scan_content(CT); +static int build_headers(CT); +static CT build_mime(char *); -/* mhcachesbr.c */ -extern int rcachesw; -extern int wcachesw; -extern char *cache_public; -extern char *cache_private; int debugsw = 0; int verbosw = 0; -int ebcdicsw = 0; -int listsw = 0; -int rfc934sw = 0; - /* - * Temporary files - */ +** Temporary files +*/ static char infile[BUFSIZ]; static int unlink_infile = 0; static char outfile[BUFSIZ]; static int unlink_outfile = 0; +static void unlink_done(int) NORETURN; -/* mhbuildsbr.c */ -CT build_mime (char *); -int output_message (CT, char *); - -/* mhlistsbr.c */ -int list_all_messages (CT *, int, int, int, int); +/* mhoutsbr.c */ +int output_message(CT, char *); +int output_message_fp(CT, FILE *, char*); /* mhmisc.c */ -void set_endian (void); +void set_endian(void); /* mhfree.c */ -void free_content (CT); +void free_content(CT); int -main (int argc, char **argv) +main(int argc, char **argv) { - int sizesw = 1, headsw = 1; - int *icachesw; - char *cp, buf[BUFSIZ]; - char buffer[BUFSIZ], *compfile = NULL; - char **argp, **arguments; - CT ct, cts[2]; - FILE *fp; + char *cp, buf[BUFSIZ]; + char buffer[BUFSIZ], *compfile = NULL; + char **argp, **arguments; + CT ct, cts[2]; + FILE *fp = NULL; + FILE *fp_out = NULL; + + done = unlink_done; #ifdef LOCALE - setlocale(LC_ALL, ""); + setlocale(LC_ALL, ""); #endif - invo_name = r1bindex (argv[0], '/'); - - /* read user profile/context */ - context_read(); - - arguments = getarguments (invo_name, argc, argv, 1); - argp = arguments; - - while ((cp = *argp++)) { - if (cp[0] == '-' && cp[1] == '\0') { - if (compfile) - adios (NULL, "cannot specify both standard input and a file"); - else - compfile = cp; - listsw = 0; /* turn off -list if using standard in/out */ - verbosw = 0; /* turn off -verbose listings */ - break; - } - if (*cp == '-') { - switch (smatch (++cp, switches)) { - case AMBIGSW: - ambigsw (cp, switches); - done (1); - case UNKWNSW: - adios (NULL, "-%s unknown", cp); - - case HELPSW: - snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); - print_help (buf, switches, 1); - done (1); - case VERSIONSW: - print_version(invo_name); - done (1); - - case RCACHESW: - icachesw = &rcachesw; - goto do_cache; - case WCACHESW: - icachesw = &wcachesw; - do_cache: ; - if (!(cp = *argp++) || *cp == '-') - adios (NULL, "missing argument to %s", argp[-2]); - switch (*icachesw = smatch (cp, caches)) { - case AMBIGSW: - ambigsw (cp, caches); - done (1); - case UNKWNSW: - adios (NULL, "%s unknown", cp); - default: - break; + invo_name = mhbasename(argv[0]); + + /* read user profile/context */ + context_read(); + + arguments = getarguments(invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (cp[0] == '-' && cp[1] == '\0') { + if (compfile) + adios(NULL, "cannot specify both standard input and a file"); + else + compfile = cp; + verbosw = 0; /* turn off -verbose listings */ + break; } - continue; - - case CHECKSW: - checksw++; - continue; - case NCHECKSW: - checksw = 0; - continue; - - case EBCDICSW: - ebcdicsw++; - continue; - case NEBCDICSW: - ebcdicsw = 0; - continue; - - case HEADSW: - headsw++; - continue; - case NHEADSW: - headsw = 0; - continue; - - case LISTSW: - listsw++; - continue; - case NLISTSW: - listsw = 0; - continue; - - case RFC934SW: - rfc934sw++; - continue; - case NRFC934SW: - rfc934sw = 0; - continue; - - case SIZESW: - sizesw++; - continue; - case NSIZESW: - sizesw = 0; - continue; - - case VERBSW: - verbosw++; - continue; - case NVERBSW: - verbosw = 0; - continue; - case DEBUGSW: - debugsw = 1; - continue; - } - } - if (compfile) - adios (NULL, "only one composition file allowed"); + if (*cp == '-') { + switch (smatch(++cp, switches)) { + case AMBIGSW: + ambigsw(cp, switches); + done(1); + case UNKWNSW: + adios(NULL, "-%s unknown", cp); + + case HELPSW: + snprintf(buf, sizeof(buf), "%s [switches] file", invo_name); + print_help(buf, switches, 1); + done(1); + case VERSIONSW: + print_version(invo_name); + done(1); + + case VERBSW: + verbosw++; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (compfile) + adios(NULL, "only one composition file allowed"); + else + compfile = cp; + } + + set_endian(); + + /* + ** Check if we've specified an additional profile + */ + if ((cp = getenv("MHBUILD"))) { + if ((fp = fopen(cp, "r"))) { + readconfig((struct node **) 0, fp, cp, 0); + fclose(fp); + } else { + admonish("", "unable to read $MHBUILD profile (%s)", + cp); + } + } + + /* + ** Read the standard profile setup + */ + if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) { + readconfig((struct node **) 0, fp, cp, 0); + fclose(fp); + } + + /* + ** Check for storage directory. If defined, we + ** will store temporary files there. Else we + ** store them in standard nmh directory. + */ + if ((cp = context_find(nmhstorage)) && *cp) + tmp = concat(cp, "/", invo_name, NULL); else - compfile = cp; - } + tmp = getcpy(toabsdir(invo_name)); - set_endian (); + /* Check if we have a file to process */ + if (!compfile) + adios(NULL, "need to specify a %s composition file", + invo_name); - if ((cp = getenv ("MM_NOASK")) && !strcmp (cp, "1")) - listsw = 0; + /* + ** Process the composition file from standard input. + */ + if (compfile[0] == '-' && compfile[1] == '\0') { + /* copy standard input to temporary file */ + strncpy(infile, m_mktemp(invo_name, NULL, &fp), + sizeof(infile)); + while (fgets(buffer, BUFSIZ, stdin)) + fputs(buffer, fp); + fclose(fp); + unlink_infile = 1; - /* - * Check if we've specified an additional profile - */ - if ((cp = getenv ("MHBUILD"))) { - if ((fp = fopen (cp, "r"))) { - readconfig ((struct node **) 0, fp, cp, 0); - fclose (fp); - } else { - admonish ("", "unable to read $MHBUILD profile (%s)", cp); - } - } - - /* - * Read the standard profile setup - */ - if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) { - readconfig ((struct node **) 0, fp, cp, 0); - fclose (fp); - } - - /* Check for public cache location */ - if ((cache_public = context_find (nmhcache)) && *cache_public != '/') - cache_public = NULL; - - /* Check for private cache location */ - if (!(cache_private = context_find (nmhprivcache))) - cache_private = ".cache"; - cache_private = getcpy (m_maildir (cache_private)); - - /* - * Check for storage directory. If defined, we - * will store temporary files there. Else we - * store them in standard nmh directory. - */ - if ((cp = context_find (nmhstorage)) && *cp) - tmp = concat (cp, "/", invo_name, NULL); - else - tmp = add (m_maildir (invo_name), NULL); - - if (!context_find ("path")) - free (path ("./", TFOLDER)); - - /* Check if we have a file to process */ - if (!compfile) - adios (NULL, "need to specify a %s composition file", invo_name); - - /* - * Process the composition file from standard input. - */ - if (compfile[0] == '-' && compfile[1] == '\0') { - - /* copy standard input to temporary file */ - strncpy (infile, m_scratch ("", invo_name), sizeof(infile)); - if ((fp = fopen (infile, "w")) == NULL) - adios (infile, "unable to open"); - while (fgets (buffer, BUFSIZ, stdin)) - fputs (buffer, fp); - fclose (fp); - unlink_infile = 1; + /* build the content structures for MIME message */ + ct = build_mime(infile); + cts[0] = ct; + cts[1] = NULL; + + /* output MIME message to this temporary file */ + strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out), + sizeof(outfile)); + unlink_outfile = 1; + + /* output the message */ + output_message_fp(ct, fp_out, outfile); + fclose(fp_out); + + /* output the temp file to standard output */ + if ((fp = fopen(outfile, "r")) == NULL) + adios(outfile, "unable to open"); + while (fgets(buffer, BUFSIZ, fp)) + fputs(buffer, stdout); + fclose(fp); + + unlink(infile); + unlink_infile = 0; + + unlink(outfile); + unlink_outfile = 0; + + free_content(ct); + done(0); + } + + /* + ** Process the composition file from a file. + */ /* build the content structures for MIME message */ - ct = build_mime (infile); + ct = build_mime(compfile); cts[0] = ct; cts[1] = NULL; /* output MIME message to this temporary file */ - strncpy (outfile, m_scratch ("", invo_name), sizeof(outfile)); + strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out), + sizeof(outfile)); unlink_outfile = 1; /* output the message */ - output_message (ct, outfile); + output_message_fp(ct, fp_out, outfile); + fclose(fp_out); - /* output the temp file to standard output */ - if ((fp = fopen (outfile, "r")) == NULL) - adios (outfile, "unable to open"); - while (fgets (buffer, BUFSIZ, fp)) - fputs (buffer, stdout); - fclose (fp); - - unlink (infile); - unlink_infile = 0; + /* Rename composition draft */ + snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile)); + if (rename(compfile, buffer) == NOTOK) { + adios(compfile, "unable to rename comp draft %s to", buffer); + } - unlink (outfile); + /* Rename output file to take its place */ + if (rename(outfile, compfile) == NOTOK) { + advise(outfile, "unable to rename output %s to", compfile); + rename(buffer, compfile); + done(1); + } unlink_outfile = 0; - free_content (ct); - done (0); - } - - /* - * Process the composition file from a file. - */ - - /* build the content structures for MIME message */ - ct = build_mime (compfile); - cts[0] = ct; - cts[1] = NULL; - - /* output MIME message to this temporary file */ - strncpy (outfile, m_scratch (compfile, invo_name), sizeof(outfile)); - unlink_outfile = 1; - - /* output the message */ - output_message (ct, outfile); - - /* - * List the message info - */ - if (listsw) - list_all_messages (cts, headsw, sizesw, verbosw, debugsw); - - /* Rename composition draft */ - snprintf (buffer, sizeof(buffer), "%s.orig", m_backup (compfile)); - if (rename (compfile, buffer) == NOTOK) - adios (compfile, "unable to rename %s to", buffer); - - /* Rename output file to take its place */ - if (rename (outfile, compfile) == NOTOK) { - advise (outfile, "unable to rename %s to", compfile); - rename (buffer, compfile); - done (1); - } - unlink_outfile = 0; - - free_content (ct); - return done (0); + free_content(ct); + done(0); + return 1; } -int -done (int status) +static void +unlink_done(int status) +{ + /* + ** Check if we need to remove stray temporary files. + */ + if (unlink_infile) + unlink(infile); + if (unlink_outfile) + unlink(outfile); + + exit(status); +} + +/* +** Main routine for translating composition file +** into valid MIME message. It translates the draft +** into a content structure (actually a tree of content +** structures). This message then can be manipulated +** in various ways, including being output via +** output_message(). +*/ +static CT +build_mime(char *infile) +{ + int compnum, state; + char buf[BUFSIZ], name[NAMESZ]; + char *cp, *np, *vp; + struct multipart *m; + struct part **pp; + CT ct; + FILE *in; + + umask(~m_gmprot()); + + /* open the composition draft */ + if ((in = fopen(infile, "r")) == NULL) + adios(infile, "unable to open for reading"); + + /* + ** Allocate space for primary (outside) content + */ + if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL) + adios(NULL, "out of memory"); + + /* + ** Allocate structure for handling decoded content + ** for this part. We don't really need this, but + ** allocate it to remain consistent. + */ + init_decoded_content(ct); + + /* + ** Parse some of the header fields in the composition + ** draft into the linked list of header fields for + ** the new MIME message. + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld(state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* abort if draft has Mime-Version header field */ + if (!mh_strcasecmp(name, VRSN_FIELD)) + adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD); + + /* + ** abort if draft has Content-Transfer-Encoding + ** header field + */ + if (!mh_strcasecmp(name, ENCODING_FIELD)) + adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD); + + /* ignore any Content-Type fields in the header */ + if (!mh_strcasecmp(name, TYPE_FIELD)) { + while (state == FLDPLUS) + state = m_getfld(state, name, buf, + sizeof(buf), in); + goto finish_field; + } + + /* get copies of the buffers */ + np = getcpy(name); + vp = getcpy(buf); + + /* if necessary, get rest of field */ + while (state == FLDPLUS) { + state = m_getfld(state, name, buf, + sizeof(buf), in); + vp = add(buf, vp); /* add to prev value */ + } + + /* Now add the header data to the list */ + add_header(ct, np, vp); + +finish_field: + /* if this wasn't the last hdr field, then continue */ + if (state != FLDEOF) + continue; + /* else fall... */ + + case FILEEOF: + adios(NULL, "draft has empty body -- no directives!"); + /* NOTREACHED */ + + case BODY: + case BODYEOF: + fseek(in, (long) (-strlen(buf)), SEEK_CUR); + break; + + case LENERR: + case FMTERR: + adios(NULL, "message format error in component #%d", + compnum); + + default: + adios(NULL, "getfld() returned %d", state); + } + break; + } + + /* + ** Now add the MIME-Version header field + ** to the list of header fields. + */ + np = getcpy(VRSN_FIELD); + vp = concat(" ", VRSN_VALUE, "\n", NULL); + add_header(ct, np, vp); + + /* + ** We initally assume we will find multiple contents in the + ** draft. So create a multipart/mixed content to hold everything. + ** We can remove this later, if it is not needed. + */ + if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK) + done(1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_MIXED; + ct->c_file = getcpy(infile); + + if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL) + adios(NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + + /* + ** read and parse the composition file + ** and the directives it contains. + */ + while (fgetstr(buf, sizeof(buf) - 1, in)) { + struct part *part; + CT p; + + if (user_content(in, infile, buf, &p) == DONE) { + admonish(NULL, "ignoring spurious #end"); + continue; + } + if (!p) + continue; + + if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL) + adios(NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + + /* + ** close the composition draft since + ** it's not needed any longer. + */ + fclose(in); + + /* check if any contents were found */ + if (!m->mp_parts) + adios(NULL, "no content directives found"); + + /* + ** If only one content was found, then remove and + ** free the outer multipart content. + */ + if (!m->mp_parts->mp_next) { + CT p; + + p = m->mp_parts->mp_part; + m->mp_parts->mp_part = NULL; + + /* move header fields */ + p->c_first_hf = ct->c_first_hf; + p->c_last_hf = ct->c_last_hf; + ct->c_first_hf = NULL; + ct->c_last_hf = NULL; + + free_content(ct); + ct = p; + } else { + set_id(ct, 1); + } + + /* + ** Fill out, or expand directives. Parse and execute + ** commands specified by profile composition strings. + */ + compose_content(ct); + + if ((cp = strchr(prefix, 'a')) == NULL) + adios(NULL, "internal error(4)"); + + /* + ** Scan the contents. Choose a transfer encoding, and + ** check if prefix for multipart boundary clashes with + ** any of the contents. + */ + while (scan_content(ct) == NOTOK) { + if (*cp < 'z') { + (*cp)++; + } else { + if (*++cp == 0) + adios(NULL, "giving up trying to find a unique delimiter string"); + else + (*cp)++; + } + } + + /* Build the rest of the header field structures */ + build_headers(ct); + + return ct; +} + + +/* +** Set up structures for placing unencoded +** content when building parts. +*/ + +static int +init_decoded_content(CT ct) +{ + CE ce; + + if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL) + adios(NULL, "out of memory"); + + ct->c_cefile = ce; + ct->c_ceopenfnx = open7Bit; /* since unencoded */ + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = NULL; /* since unencoded */ + + return OK; +} + + +static char * +fgetstr(char *s, int n, FILE *stream) +{ + char *cp, *ep; + + for (ep = (cp = s) + n; cp < ep; ) { + int i; + + if (!fgets(cp, n, stream)) + return (cp != s ? s : NULL); + if (cp == s && *cp != '#') + return s; + + cp += (i = strlen(cp)) - 1; + if (i <= 1 || *cp-- != '\n' || *cp != '\\') + break; + *cp = '\0'; + n -= (i - 2); + } + + return s; +} + + +/* +** Parse the composition draft for text and directives. +** Do initial setup of Content structure. +*/ + +static int +user_content(FILE *in, char *file, char *buf, CT *ctp) +{ + int vrsn; + unsigned char *cp; + char **ap; + char buffer[BUFSIZ]; + struct multipart *m; + struct part **pp; + struct str2init *s2i; + CI ci; + CT ct; + CE ce; + + if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) { + *ctp = NULL; + return OK; + } + + /* allocate basic Content structure */ + if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL) + adios(NULL, "out of memory"); + *ctp = ct; + + /* allocate basic structure for handling decoded content */ + init_decoded_content(ct); + ce = ct->c_cefile; + + ci = &ct->c_ctinfo; + set_id(ct, 0); + + /* + ** Handle inline text. Check if line + ** is one of the following forms: + ** + ** 1) doesn't begin with '#' (implicit directive) + ** 2) begins with "##" (implicit directive) + ** 3) begins with "#<" + */ + if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') { + int headers; + int inlineD; + long pos; + char content[BUFSIZ]; + FILE *out; + char *cp; + + cp = m_mktemp2(NULL, invo_name, NULL, &out); + if (cp == NULL) + adios("mhbuild", "unable to create temporary file"); + + /* use a temp file to collect the plain text lines */ + ce->ce_file = getcpy(cp); + ce->ce_unlink = 1; + + if (buf[0] == '#' && buf[1] == '<') { + strncpy(content, buf + 2, sizeof(content)); + inlineD = 1; + goto rock_and_roll; + } else { + inlineD = 0; + } + + /* the directive is implicit */ + strncpy(content, "text/plain", sizeof(content)); + headers = 0; + strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer)); + for (;;) { + int i; + + if (headers >= 0 && uprf(buffer, DESCR_FIELD) && + buffer[i=strlen(DESCR_FIELD)] == ':') { + headers = 1; + +again_descr: + ct->c_descr = add(buffer + i + 1, ct->c_descr); + if (!fgetstr(buffer, sizeof(buffer) - 1, in)) + adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD); + switch (buffer[0]) { + case ' ': + case '\t': + i = -1; + goto again_descr; + + case '#': + adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD); + /* NOTREACHED */ + + default: + break; + } + } + + if (headers >= 0 && uprf(buffer, DISPO_FIELD) + && buffer[i = strlen(DISPO_FIELD)] == ':') { + headers = 1; + +again_dispo: + ct->c_dispo = add(buffer + i + 1, ct->c_dispo); + if (!fgetstr(buffer, sizeof(buffer) - 1, in)) + adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD); + switch (buffer[0]) { + case ' ': + case '\t': + i = -1; + goto again_dispo; + + case '#': + adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD); + /* NOTREACHED */ + + default: + break; + } + } + + if (headers != 1 || buffer[0] != '\n') + fputs(buffer, out); + +rock_and_roll: + headers = -1; + pos = ftell(in); + if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in)) + == NULL) + break; + if (buffer[0] == '#') { + char *bp; + + if (buffer[1] != '#') + break; + for (cp = (bp = buffer) + 1; *cp; cp++) + *bp++ = *cp; + *bp = '\0'; + } + } + + fclose(out); + + /* parse content type */ + if (get_ctinfo(content, ct, inlineD) == NOTOK) + done(1); + + for (s2i = str2cts; s2i->si_key; s2i++) + if (!mh_strcasecmp(ci->ci_type, s2i->si_key)) + break; + if (!s2i->si_key && !uprf(ci->ci_type, "X-")) + s2i++; + + /* + ** check type specified (possibly implicitly) + */ + switch (ct->c_type = s2i->si_val) { + case CT_MESSAGE: + if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) { + ct->c_encoding = CE_7BIT; + goto call_init; + } + /* else fall... */ + case CT_MULTIPART: + adios(NULL, "it doesn't make sense to define an in-line %s content", + ct->c_type == CT_MESSAGE ? "message" : + "multipart"); + /* NOTREACHED */ + + default: +call_init: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; + } + + if (cp) + fseek(in, pos, SEEK_SET); + return OK; + } + + /* + ** If we've reached this point, the next line + ** must be some type of explicit directive. + */ + + if (buf[1] == '@') { + adios(NULL, "The #@ directive i.e. message/external-body " + "is not supported anymore."); + } + + /* parse directive */ + if (get_ctinfo(buf+1, ct, 1) == NOTOK) + done(1); + + /* check directive against the list of MIME types */ + for (s2i = str2cts; s2i->si_key; s2i++) + if (!mh_strcasecmp(ci->ci_type, s2i->si_key)) + break; + + /* + ** Check if the directive specified a valid type. + ** This will happen if it was one of the following forms: + ** + ** #type/subtype + */ + if (s2i->si_key) { + if (!ci->ci_subtype) + adios(NULL, "missing subtype in \"#%s\"", ci->ci_type); + + switch (ct->c_type = s2i->si_val) { + case CT_MULTIPART: + adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ + + case CT_MESSAGE: + if (!mh_strcasecmp(ci->ci_subtype, "partial") || + !mh_strcasecmp(ci->ci_subtype, + "external-body")) { + adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype); + } +use_forw: + adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ + + default: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; + } + + /* Handle [file] argument */ + if (ci->ci_magic) { + /* check if specifies command to execute */ + if (*ci->ci_magic == '|' || *ci->ci_magic == '!') { + for (cp = ci->ci_magic + 1; isspace(*cp); cp++) + continue; + if (!*cp) + adios(NULL, "empty pipe command for #%s directive", ci->ci_type); + cp = getcpy(cp); + free(ci->ci_magic); + ci->ci_magic = cp; + } else { + /* record filename of decoded contents */ + ce->ce_file = ci->ci_magic; + if (access(ce->ce_file, R_OK) == NOTOK) + adios("reading", "unable to access %s for", ce->ce_file); + ci->ci_magic = NULL; + } + return OK; + } + + /* + ** No [file] argument, so check profile for + ** method to compose content. + */ + snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find(buffer)) == NULL || *cp == '\0') { + snprintf(buffer, sizeof(buffer), "%s-compose-%s", + invo_name, ci->ci_type); + if ((cp = context_find(buffer)) == NULL || + *cp == '\0') { + content_error(NULL, ct, "don't know how to compose content"); + done(1); + } + } + ci->ci_magic = getcpy(cp); + return OK; + } + + /* + ** Message directive + ** #forw [+folder] [msgs] + */ + if (!mh_strcasecmp(ci->ci_type, "forw")) { + int msgnum; + char *folder, *arguments[MAXARGS]; + struct msgs *mp; + + if (ci->ci_magic) { + int i; + + ap = brkstring(ci->ci_magic, " ", "\n"); + for (i=0; ap[i] && inumsel > 1) { + /* we are forwarding multiple messages */ + if (get_ctinfo("multipart/digest", ct, 0) == NOTOK) + done(1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_DIGEST; + + if ((m = (struct multipart *) + calloc(1, sizeof(*m))) == NULL) + adios(NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + struct part *part; + CT p; + CE pe; + + if ((p = (CT) calloc(1, sizeof(*p))) + == NULL) + adios(NULL, "out of memory"); + init_decoded_content(p); + pe = p->c_cefile; + if (get_ctinfo("message/rfc822", p, 0) + == NOTOK) + done(1); + p->c_type = CT_MESSAGE; + p->c_subtype = MESSAGE_RFC822; + + snprintf(buffer, sizeof(buffer), + "%s/%d", mp->foldpath, + msgnum); + pe->ce_file = getcpy(buffer); + + if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL) + adios(NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + } + } else { + /* we are forwarding one message */ + if (get_ctinfo("message/rfc822", ct, 0) == NOTOK) + done(1); + ct->c_type = CT_MESSAGE; + ct->c_subtype = MESSAGE_RFC822; + + msgnum = mp->lowsel; + snprintf(buffer, sizeof(buffer), "%s/%d", + mp->foldpath, msgnum); + ce->ce_file = getcpy(buffer); + } + + folder_free(mp); /* free folder/message structure */ + return OK; + } + + /* + ** #end + */ + if (!mh_strcasecmp(ci->ci_type, "end")) { + free_content(ct); + *ctp = NULL; + return DONE; + } + + /* + ** #begin [ alternative | parallel ] + */ + if (!mh_strcasecmp(ci->ci_type, "begin")) { + if (!ci->ci_magic) { + vrsn = MULTI_MIXED; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) { + vrsn = MULTI_ALTERNATE; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) { + vrsn = MULTI_PARALLEL; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (uprf(ci->ci_magic, "digest")) { + goto use_forw; + } else { + vrsn = MULTI_UNKNOWN; + cp = ci->ci_magic; + } + + free_ctinfo(ct); + snprintf(buffer, sizeof(buffer), "multipart/%s", cp); + if (get_ctinfo(buffer, ct, 0) == NOTOK) + done(1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = vrsn; + + if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL) + adios(NULL, "out of memory"); + ct->c_ctparams = (void *) m; + + pp = &m->mp_parts; + while (fgetstr(buffer, sizeof(buffer) - 1, in)) { + struct part *part; + CT p; + + if (user_content(in, file, buffer, &p) == DONE) { + if (!m->mp_parts) + adios(NULL, "empty \"#begin ... #end\" sequence"); + return OK; + } + if (!p) + continue; + + if ((part = (struct part *) + calloc(1, sizeof(*part))) == NULL) + adios(NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + admonish(NULL, "premature end-of-file, missing #end"); + return OK; + } + + /* + ** Unknown directive + */ + adios(NULL, "unknown directive \"#%s\"", ci->ci_type); + return NOTOK; /* NOT REACHED */ +} + + +static void +set_id(CT ct, int top) { - /* - * Check if we need to remove stray - * temporary files. - */ - if (unlink_infile) - unlink (infile); - if (unlink_outfile) - unlink (outfile); - - exit (status); - return 1; /* dead code to satisfy the compiler */ + char msgid[BUFSIZ]; + static int partno; + static time_t clock = 0; + static char *msgfmt; + + if (clock == 0) { + time(&clock); + snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n", + (int) getpid(), (long) clock, LocalName()); + partno = 0; + msgfmt = getcpy(msgid); + } + snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno); + ct->c_id = getcpy(msgid); +} + + +/* +** Fill out, or expand the various contents in the composition +** draft. Read-in any necessary files. Parse and execute any +** commands specified by profile composition strings. +*/ + +static int +compose_content(CT ct) +{ + CE ce = ct->c_cefile; + + switch (ct->c_type) { + case CT_MULTIPART: + { + int partnum; + char *pp; + char partnam[BUFSIZ]; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + if (ct->c_partno) { + snprintf(partnam, sizeof(partnam), "%s.", + ct->c_partno); + pp = partnam + strlen(partnam); + } else { + pp = partnam; + } + + /* first, we call compose_content on all the subparts */ + for (part = m->mp_parts, partnum = 1; part; + part = part->mp_next, partnum++) { + CT p = part->mp_part; + + sprintf(pp, "%d", partnum); + p->c_partno = getcpy(partnam); + if (compose_content(p) == NOTOK) + return NOTOK; + } + } + break; + + case CT_MESSAGE: + /* Nothing to do for type message */ + break; + + /* + ** Discrete types (text/application/audio/image/video) + */ + default: + if (!ce->ce_file) { + pid_t child_id; + int xstdout, len, buflen; + char *bp, **ap, *cp; + char *vec[4], buffer[BUFSIZ]; + FILE *out; + CI ci = &ct->c_ctinfo; + char *tfile = NULL; + + if (!(cp = ci->ci_magic)) + adios(NULL, "internal error(5)"); + + tfile = m_mktemp2(NULL, invo_name, NULL, NULL); + if (tfile == NULL) { + adios("mhbuild", "unable to create temporary file"); + } + ce->ce_file = getcpy(tfile); + ce->ce_unlink = 1; + + xstdout = 0; + + /* Get buffer ready to go */ + bp = buffer; + bp[0] = '\0'; + buflen = sizeof(buffer); + + /* + ** Parse composition string into buffer + */ + for ( ; *cp; cp++) { + if (*cp == '%') { + switch (*++cp) { + case 'a': + { + /* + ** insert parameters from + ** directive + */ + char **ep; + char *s = ""; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + len = strlen(bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'F': + /* %f, and stdout is not-redirected */ + xstdout = 1; + /* and fall... */ + + case 'f': + /* + ** insert temporary filename + ** where content should be + ** written + */ + snprintf(bp, buflen, "%s", ce->ce_file); + break; + + case 's': + /* insert content subtype */ + strncpy(bp, ci->ci_subtype, buflen); + break; + + case '%': + /* insert character % */ + goto raw; + + default: + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } + len = strlen(bp); + bp += len; + buflen -= len; + } else { +raw: + *bp++ = *cp; + *bp = '\0'; + buflen--; + } + } + + if (verbosw) + printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer); + + fflush(stdout); /* not sure if need for -noverbose */ + + vec[0] = "/bin/sh"; + vec[1] = "-c"; + vec[2] = buffer; + vec[3] = NULL; + + if ((out = fopen(ce->ce_file, "w")) == NULL) + adios(ce->ce_file, "unable to open for writing"); + + switch (child_id = fork()) { + case NOTOK: + adios("fork", "unable to fork"); + /* NOTREACHED */ + + case OK: + if (!xstdout) + dup2(fileno(out), 1); + close(fileno(out)); + execvp("/bin/sh", vec); + fprintf(stderr, "unable to exec "); + perror("/bin/sh"); + _exit(-1); + /* NOTREACHED */ + + default: + fclose(out); + if (pidXwait(child_id, NULL)) + done(1); + break; + } + } + break; + } + + return OK; +} + + +/* +** Scan the content. +** +** 1) choose a transfer encoding. +** 2) check for clashes with multipart boundary string. +** 3) for text content, figure out which character set is being used. +** +** If there is a clash with one of the contents and the multipart boundary, +** this function will exit with NOTOK. This will cause the scanning process +** to be repeated with a different multipart boundary. It is possible +** (although highly unlikely) that this scan will be repeated multiple times. +*/ + +static int +scan_content(CT ct) +{ + int len; + int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */ + int checklinelen = 0, linelen = 0; /* check for long lines */ + int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */ + int checklinespace = 0, linespace = 0; /* check if any line ends with space */ + unsigned char *cp = NULL, buffer[BUFSIZ]; + struct text *t = NULL; + FILE *in = NULL; + CE ce = ct->c_cefile; + + /* + ** handle multipart by scanning all subparts + ** and then checking their encoding. + */ + if (ct->c_type == CT_MULTIPART) { + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + /* initially mark the domain of enclosing multipart as 7bit */ + ct->c_encoding = CE_7BIT; + + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if (scan_content(p) == NOTOK) { + /* choose encoding for subpart */ + return NOTOK; + } + + /* + ** if necessary, enlarge encoding for enclosing + ** multipart + */ + if (p->c_encoding == CE_BINARY) + ct->c_encoding = CE_BINARY; + if (p->c_encoding == CE_8BIT && + ct->c_encoding != CE_BINARY) + ct->c_encoding = CE_8BIT; + } + + return OK; + } + + /* + ** Decide what to check while scanning this content. + */ + switch (ct->c_type) { + case CT_TEXT: + check8bit = 1; + checkboundary = 1; + if (ct->c_subtype == TEXT_PLAIN) { + checklinelen = 0; + checklinespace = 0; + } else { + checklinelen = 1; + checklinespace = 1; + } + break; + + case CT_APPLICATION: + check8bit = 1; + checklinelen = 1; + checklinespace = 1; + checkboundary = 1; + break; + + case CT_MESSAGE: + check8bit = 0; + checklinelen = 0; + checklinespace = 0; + checkboundary = 1; + break; + + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + /* + ** Don't check anything for these types, + ** since we are forcing use of base64. + */ + check8bit = 0; + checklinelen = 0; + checklinespace = 0; + checkboundary = 0; + break; + } + + /* + ** Scan the unencoded content + */ + if (check8bit || checklinelen || checklinespace || checkboundary) { + if ((in = fopen(ce->ce_file, "r")) == NULL) + adios(ce->ce_file, "unable to open for reading"); + len = strlen(prefix); + + while (fgets(buffer, sizeof(buffer) - 1, in)) { + /* + ** Check for 8bit data. + */ + if (check8bit) { + for (cp = buffer; *cp; cp++) { + if (!isascii(*cp)) { + contains8bit = 1; + /* no need to keep checking */ + check8bit = 0; + } + } + } + + /* + ** Check line length. + */ + if (checklinelen && (strlen(buffer) > CPERLIN + 1)) { + linelen = 1; + checklinelen = 0; /* no need to keep checking */ + } + + /* + ** Check if line ends with a space. + */ + if (checklinespace && + (cp = buffer + strlen(buffer) - 2) > + buffer && isspace(*cp)) { + linespace = 1; + /* no need to keep checking */ + checklinespace = 0; + } + + /* + ** Check if content contains a line that clashes + ** with our standard boundary for multipart messages. + */ + if (checkboundary && buffer[0] == '-' && + buffer[1] == '-') { + for (cp = buffer + strlen(buffer) - 1; + cp >= buffer; cp--) + if (!isspace(*cp)) + break; + *++cp = '\0'; + if (strncmp(buffer + 2, prefix, len)==0 && + isdigit(buffer[2 + len])) { + boundaryclash = 1; + /* no need to keep checking */ + checkboundary = 0; + } + } + } + fclose(in); + } + + /* + ** Decide which transfer encoding to use. + */ + switch (ct->c_type) { + case CT_TEXT: + /* + ** If the text content didn't specify a character + ** set, we need to figure out which one was used. + */ + t = (struct text *) ct->c_ctparams; + if (t->tx_charset == CHARSET_UNSPECIFIED) { + CI ci = &ct->c_ctinfo; + char **ap, **ep; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + continue; + + if (contains8bit) { + t->tx_charset = CHARSET_UNKNOWN; + *ap = concat("charset=", write_charset_8bit(), + NULL); + } else { + t->tx_charset = CHARSET_USASCII; + *ap = getcpy("charset=us-ascii"); + } + + cp = strchr(*ap++, '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; + } + + if (contains8bit || linelen || linespace) + ct->c_encoding = CE_QUOTED; + else + ct->c_encoding = CE_7BIT; + break; + + case CT_APPLICATION: + /* For application type, use base64, except when postscript */ + if (contains8bit || linelen || linespace) + ct->c_encoding = (ct->c_subtype == + APPLICATION_POSTSCRIPT) ? + CE_QUOTED : CE_BASE64; + else + ct->c_encoding = CE_7BIT; + break; + + case CT_MESSAGE: + ct->c_encoding = CE_7BIT; + break; + + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + /* For audio, image, and video contents, just use base64 */ + ct->c_encoding = CE_BASE64; + break; + } + + return (boundaryclash ? NOTOK : OK); +} + + +/* +** Scan the content structures, and build header +** fields that will need to be output into the +** message. +*/ + +static int +build_headers(CT ct) +{ + int cc, len; + char **ap, **ep; + char *np, *vp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* + ** If message is type multipart, then add the multipart + ** boundary to the list of attribute/value pairs. + */ + if (ct->c_type == CT_MULTIPART) { + char *cp; + static int level = 0; /* store nesting level */ + + ap = ci->ci_attrs; + ep = ci->ci_values; + snprintf(buffer, sizeof(buffer), "boundary=%s%d", + prefix, level++); + cp = strchr(*ap++ = getcpy(buffer), '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; + } + + /* + ** output the content type and subtype + */ + np = getcpy(TYPE_FIELD); + vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL); + + /* keep track of length of line */ + len = strlen(TYPE_FIELD) + strlen(ci->ci_type) + + strlen(ci->ci_subtype) + 3; + + /* + ** Append the attribute/value pairs to + ** the end of the Content-Type line. + */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + vp = add(";", vp); + len++; + + snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); + if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) { + vp = add("\n\t", vp); + len = 8; + } else { + vp = add(" ", vp); + len++; + } + vp = add(buffer, vp); + len += cc; + } + + /* + ** Append any RFC-822 comment to the end of + ** the Content-Type line. + */ + if (ci->ci_comment) { + snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment); + if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) { + vp = add("\n\t", vp); + len = 8; + } else { + vp = add(" ", vp); + len++; + } + vp = add(buffer, vp); + len += cc; + } + vp = add("\n", vp); + add_header(ct, np, vp); + + /* + ** output the Content-ID + */ + if (ct->c_id) { + np = getcpy(ID_FIELD); + vp = concat(" ", ct->c_id, NULL); + add_header(ct, np, vp); + } + + /* + ** output the Content-Description + */ + if (ct->c_descr) { + np = getcpy(DESCR_FIELD); + vp = concat(" ", ct->c_descr, NULL); + add_header(ct, np, vp); + } + + /* + ** output the Content-Disposition + */ + if (ct->c_dispo) { + np = getcpy(DISPO_FIELD); + vp = concat(" ", ct->c_dispo, NULL); + add_header(ct, np, vp); + } + + /* + ** output the Content-Transfer-Encoding + */ + switch (ct->c_encoding) { + case CE_7BIT: + /* Nothing to output */ + break; + + case CE_8BIT: + if (ct->c_type == CT_MESSAGE) + adios(NULL, "internal error, invalid encoding"); + + np = getcpy(ENCODING_FIELD); + vp = concat(" ", "8bit", "\n", NULL); + add_header(ct, np, vp); + break; + + case CE_QUOTED: + if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) + adios(NULL, "internal error, invalid encoding"); + + np = getcpy(ENCODING_FIELD); + vp = concat(" ", "quoted-printable", "\n", NULL); + add_header(ct, np, vp); + break; + + case CE_BASE64: + if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) + adios(NULL, "internal error, invalid encoding"); + + np = getcpy(ENCODING_FIELD); + vp = concat(" ", "base64", "\n", NULL); + add_header(ct, np, vp); + break; + + case CE_BINARY: + if (ct->c_type == CT_MESSAGE) + adios(NULL, "internal error, invalid encoding"); + + np = getcpy(ENCODING_FIELD); + vp = concat(" ", "binary", "\n", NULL); + add_header(ct, np, vp); + break; + + default: + adios(NULL, "unknown transfer encoding in content"); + break; + } + + /* + ** Additional content specific header processing + */ + switch (ct->c_type) { + case CT_MULTIPART: + { + struct multipart *m; + struct part *part; + + m = (struct multipart *) ct->c_ctparams; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p; + + p = part->mp_part; + build_headers(p); + } + } + break; + + default: + /* Nothing to do */ + break; + } + + return OK; }