From 636b3bab53e3fc2154bb86fb02aeecf433cc977a Mon Sep 17 00:00:00 2001 From: Eric Gillespie Date: Thu, 14 Aug 2008 00:56:38 +0000 Subject: [PATCH] * h/mhparse.h: Move interfaces also used by mhbuildsbr.c here from mhparse.c. * uip/Makefile.in (mhbuild): Build with mhparse.o. * uip/mhbuildsbr.c: Drop the duplication of mhparse.c. * uip/mhbuild.c, uip/mhlist.c, uip/mhn.c, uip/mhshow.c, uip/mhstore.c, uip/mhtest.c: Drop check swdeclaration, which is now in mhparse.h. * uip/mhparse.c: Move some declarations to mhparse.h, and remove 'static' from the definitions here. (get_ctinfo): Take magic mhbuild support from mhbuildsbr.c's copy, and adapt callers. (incl_name_value, extract_name_value): Move from mhbuildsbr.c to support get_ctinfo change). (get_content, open7Bit): Add Content-Disposition support, added to mhbuildsbr.c's copy in mhbuildsbr.c r1.12 (PatchSet 549). (InitText): Default to CHARSET_UNSPECIFIED instead of "US-ASCII", as mhbuildsbr.c's copy did. Explain termproc. (openBase64, openQuoted, open7Bit): Fix comment from r1.5: suffixes, not prefixes. (openMail): Explain showproc. * test/tests/mhshow/test-cte-binary: Add test for Content-Transfer-Encoding: binary (Debian #136976). * test/tests/mhbuild/test-forw: Add an mhbuild test, testing forw with and without RFC-934 mode. More mhbuild tests would be nice. --- ChangeLog | 33 + h/mhparse.h | 39 + test/tests/mhbuild/test-forw | 114 ++ test/tests/mhshow/test-cte-binary | 42 + uip/Makefile.in | 4 +- uip/mhbuild.c | 1 - uip/mhbuildsbr.c | 3004 ++----------------------------------- uip/mhlist.c | 1 - uip/mhn.c | 1 - uip/mhparse.c | 296 +++- uip/mhshow.c | 1 - uip/mhstore.c | 1 - uip/mhtest.c | 1 - 13 files changed, 601 insertions(+), 2937 deletions(-) create mode 100644 test/tests/mhbuild/test-forw create mode 100644 test/tests/mhshow/test-cte-binary diff --git a/ChangeLog b/ChangeLog index 2e03ab6..2cc1ba7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,38 @@ 2008-08-13 Eric Gillespie + * h/mhparse.h: Move interfaces also used by mhbuildsbr.c here + from mhparse.c. + + * uip/Makefile.in (mhbuild): Build with mhparse.o. + + * uip/mhbuildsbr.c: Drop the duplication of mhparse.c. + + * uip/mhbuild.c, uip/mhlist.c, uip/mhn.c, uip/mhshow.c, + uip/mhstore.c, uip/mhtest.c: Drop check swdeclaration, which is + now in mhparse.h. + + * uip/mhparse.c: Move some declarations to mhparse.h, and remove + 'static' from the definitions here. + (get_ctinfo): Take magic mhbuild support from mhbuildsbr.c's copy, + and adapt callers. + (incl_name_value, extract_name_value): Move from mhbuildsbr.c to + support get_ctinfo change). + (get_content, open7Bit): Add Content-Disposition support, added to + mhbuildsbr.c's copy in mhbuildsbr.c r1.12 (PatchSet 549). + (InitText): Default to CHARSET_UNSPECIFIED instead of "US-ASCII", + as mhbuildsbr.c's copy did. Explain termproc. + (openBase64, openQuoted, open7Bit): Fix comment from r1.5: + suffixes, not prefixes. + (openMail): Explain showproc. + + * test/tests/mhshow/test-cte-binary: Add test for + Content-Transfer-Encoding: binary (Debian #136976). + + * test/tests/mhbuild/test-forw: Add an mhbuild test, testing forw + with and without RFC-934 mode. More mhbuild tests would be nice. + +2008-08-13 Eric Gillespie + * test/tests/bad-input/test-header: Add test for it. * sbr/m_getfld.c: If we reach the end of the line without finding diff --git a/h/mhparse.h b/h/mhparse.h index 812b78f..eb7cb18 100644 --- a/h/mhparse.h +++ b/h/mhparse.h @@ -244,3 +244,42 @@ struct exbody { #define APPLICATION_OCTETS 0x01 #define APPLICATION_POSTSCRIPT 0x02 + +/* + * Structures for mapping types to their internal flags + */ +struct k2v { + char *kv_key; + int kv_value; +}; +extern struct k2v SubText[]; +extern struct k2v Charset[]; +extern struct k2v SubMultiPart[]; +extern struct k2v SubMessage[]; +extern struct k2v SubApplication[]; + +/* + * Structures for mapping (content) types to + * the functions to handle them. + */ +struct str2init { + char *si_key; + int si_val; + InitFunc si_init; +}; +extern struct str2init str2cts[]; +extern struct str2init str2ces[]; +extern struct str2init str2methods[]; + +/* + * prototypes + */ +int pidcheck (int); +CT parse_mime (char *); +int add_header (CT, char *, char *); +int get_ctinfo (unsigned char *, CT, int); +int params_external (CT, int); +int open7Bit (CT, char **); +void close_encoding (CT); + +extern int checksw; /* Add Content-MD5 field */ diff --git a/test/tests/mhbuild/test-forw b/test/tests/mhbuild/test-forw new file mode 100644 index 0000000..1dece26 --- /dev/null +++ b/test/tests/mhbuild/test-forw @@ -0,0 +1,114 @@ +#!/bin/sh +###################################################### +# +# Test forwarding messages (including RFC-934 mode). +# +###################################################### + +# TODO: Move to a common file tests can source; need more framework... +failed=0 +check() { + diff -u $expected $actual + if [ $? -ne 0 ]; then + failed=$((failed + 1)) + fi +} + +# Make a draft file forwarding two messages. +mkdraft() { + cat > $draft <][^>]*>/\1 /' $draft > $actual +} + +draft=$MH_TEST_DIR/$$.draft +expected=$MH_TEST_DIR/$$.expected +actual=$MH_TEST_DIR/$$.actual + +# check mhbuild +cat > $expected < +Content-Description: forwarded messages + +------- =_aaaaaaaaaa0 +Content-Type: message/rfc822 + +From: Test1 +To: Some User +Date: Fri, 29 Sep 2006 00:00:00 +Subject: Testing message 1 + +This is message number 1 + +------- =_aaaaaaaaaa0 +Content-Type: message/rfc822 + +From: Test2 +To: Some User +Date: Fri, 29 Sep 2006 00:00:00 +Subject: Testing message 2 + +This is message number 2 + +------- =_aaaaaaaaaa0-- +EOF +mkdraft +mhbuild $draft +mungedraft +check + +# check mhbuild -rfc934 +cat > $expected < +Content-Description: forwarded messages + + +------- =_aaaaaaaaaa0 + +From: Test1 +To: Some User +Date: Fri, 29 Sep 2006 00:00:00 +Subject: Testing message 1 + +This is message number 1 + +------- =_aaaaaaaaaa0 + +From: Test2 +To: Some User +Date: Fri, 29 Sep 2006 00:00:00 +Subject: Testing message 2 + +This is message number 2 + +------- =_aaaaaaaaaa0-- +EOF +mkdraft +mhbuild -rfc934 $draft +mungedraft +check + +exit $failed diff --git a/test/tests/mhshow/test-cte-binary b/test/tests/mhshow/test-cte-binary new file mode 100644 index 0000000..359d42e --- /dev/null +++ b/test/tests/mhshow/test-cte-binary @@ -0,0 +1,42 @@ +#!/bin/sh +###################################################### +# +# Test Content-Transfer-Encoding: binary (Debian #136976). +# +###################################################### + +expected=$MH_TEST_DIR/$$.expected +actual=$MH_TEST_DIR/$$.actual + +# Write message with Content-Transfer-Encoding: binary text. +msgfile=$(mhpath new) +msgnum=$(basename $msgfile) +cat > $msgfile < +Date: Tue, 05 Mar 2002 18:20:35 +0000 + +This is a test; this is the body. +EOF + +# check it +cat > $expected < $actual 2>&1 +diff -u $expected $actual diff --git a/uip/Makefile.in b/uip/Makefile.in index e58ac4f..abd7cf4 100644 --- a/uip/Makefile.in +++ b/uip/Makefile.in @@ -147,8 +147,8 @@ install-mh: install-mh.o $(LOCALLIBS) mark: mark.o $(LOCALLIBS) $(LINK) mark.o $(LINKLIBS) -mhbuild: mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) - $(LINK) mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o md5.o $(LINKLIBS) $(TERMLIB) +mhbuild: mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o mhparse.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o mhparse.o ftpsbr.o md5.o $(LINKLIBS) $(TERMLIB) mhl: mhl.o mhlsbr.o termsbr.o $(LOCALLIBS) $(LINK) mhl.o mhlsbr.o termsbr.o $(LINKLIBS) $(TERMLIB) diff --git a/uip/mhbuild.c b/uip/mhbuild.c index a47022e..9d9f553 100644 --- a/uip/mhbuild.c +++ b/uip/mhbuild.c @@ -74,7 +74,6 @@ static struct swit switches[] = { /* mhbuildsbr.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ diff --git a/uip/mhbuildsbr.c b/uip/mhbuildsbr.c index 76866fe..b75034a 100644 --- a/uip/mhbuildsbr.c +++ b/uip/mhbuildsbr.c @@ -59,8 +59,6 @@ extern int endian; /* mhmisc.c */ extern int rcachesw; /* mhcachesbr.c */ extern int wcachesw; /* mhcachesbr.c */ -int checksw = 0; /* Add Content-MD5 field */ - /* * Directory to place tmp files. This must * be set before these routines are called. @@ -71,60 +69,6 @@ pid_t xpid = 0; static char prefix[] = "----- =_aaaaaaaaaa"; -/* - * Structure for mapping types to their internal flags - */ -struct k2v { - char *kv_key; - int kv_value; -}; - -/* - * Structures for TEXT messages - */ -static struct k2v SubText[] = { - { "plain", TEXT_PLAIN }, - { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */ - { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */ - { NULL, TEXT_UNKNOWN } /* this one must be last! */ -}; - -static struct k2v Charset[] = { - { "us-ascii", CHARSET_USASCII }, - { "iso-8859-1", CHARSET_LATIN }, - { NULL, CHARSET_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structures for MULTIPART messages - */ -static struct k2v SubMultiPart[] = { - { "mixed", MULTI_MIXED }, - { "alternative", MULTI_ALTERNATE }, - { "digest", MULTI_DIGEST }, - { "parallel", MULTI_PARALLEL }, - { NULL, MULTI_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structures for MESSAGE messages - */ -static struct k2v SubMessage[] = { - { "rfc822", MESSAGE_RFC822 }, - { "partial", MESSAGE_PARTIAL }, - { "external-body", MESSAGE_EXTERNAL }, - { NULL, MESSAGE_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structure for APPLICATION messages - */ -static struct k2v SubApplication[] = { - { "octet-stream", APPLICATION_OCTETS }, - { "postscript", APPLICATION_POSTSCRIPT }, - { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ -}; - /* mhmisc.c */ int make_intermediates (char *); @@ -145,39 +89,11 @@ void free_encoding (CT, int); * prototypes */ CT build_mime (char *); -int pidcheck (int); /* * static prototypes */ -static CT get_content (FILE *, char *, int); -static int add_header (CT, char *, char *); -static int get_ctinfo (unsigned char *, CT, int); -static int get_comment (CT, unsigned char **, int); -static int InitGeneric (CT); -static int InitText (CT); -static int InitMultiPart (CT); -static void reverse_parts (CT); -static int InitMessage (CT); -static int params_external (CT, int); -static int InitApplication (CT); static int init_decoded_content (CT); -static int init_encoding (CT, OpenCEFunc); -static void close_encoding (CT); -static unsigned long size_encoding (CT); -static int InitBase64 (CT); -static int openBase64 (CT, char **); -static int InitQuoted (CT); -static int openQuoted (CT, char **); -static int Init7Bit (CT); -static int open7Bit (CT, char **); -static int openExternal (CT, CT, CE, char **, int *); -static int InitFile (CT); -static int openFile (CT, char **); -static int InitFTP (CT); -static int openFTP (CT, char **); -static int InitMail (CT); -static int openMail (CT, char **); static char *fgetstr (char *, int, FILE *); static int user_content (FILE *, char *, char *, CT *); static void set_id (CT, int); @@ -185,68 +101,6 @@ static int compose_content (CT); static int scan_content (CT); static int build_headers (CT); static char *calculate_digest (CT, int); -static int readDigest (CT, char *); -static char *incl_name_value (unsigned char *, char *, char *); -static char *extract_name_value (char *, char *); - -/* - * Structures for mapping (content) types to - * the functions to handle them. - */ -struct str2init { - char *si_key; - int si_val; - InitFunc si_init; -}; - -static struct str2init str2cts[] = { - { "application", CT_APPLICATION, InitApplication }, - { "audio", CT_AUDIO, InitGeneric }, - { "image", CT_IMAGE, InitGeneric }, - { "message", CT_MESSAGE, InitMessage }, - { "multipart", CT_MULTIPART, InitMultiPart }, - { "text", CT_TEXT, InitText }, - { "video", CT_VIDEO, InitGeneric }, - { NULL, CT_EXTENSION, NULL }, /* these two must be last! */ - { NULL, CT_UNKNOWN, NULL }, -}; - -static struct str2init str2ces[] = { - { "base64", CE_BASE64, InitBase64 }, - { "quoted-printable", CE_QUOTED, InitQuoted }, - { "8bit", CE_8BIT, Init7Bit }, - { "7bit", CE_7BIT, Init7Bit }, - { "binary", CE_BINARY, NULL }, - { NULL, CE_EXTENSION, NULL }, /* these two must be last! */ - { NULL, CE_UNKNOWN, NULL }, -}; - -/* - * NOTE WELL: si_key MUST NOT have value of NOTOK - * - * si_key is 1 if access method is anonymous. - */ -static struct str2init str2methods[] = { - { "afs", 1, InitFile }, - { "anon-ftp", 1, InitFTP }, - { "ftp", 0, InitFTP }, - { "local-file", 0, InitFile }, - { "mail-server", 0, InitMail }, - { NULL, 0, NULL } -}; - - -int -pidcheck (int status) -{ - if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT) - return status; - - fflush (stdout); - fflush (stderr); - done (1); - return 1; -} /* @@ -293,2603 +147,195 @@ build_mime (char *infile) * 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 = add (name, NULL); - vp = add (buf, NULL); - - /* if necessary, get rest of field */ - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - vp = add (buf, vp); /* add to previous value */ - } - - /* Now add the header data to the list */ - add_header (ct, np, vp); - -finish_field: - /* if this wasn't the last header 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 = add (VRSN_FIELD, NULL); - 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 = add (infile, NULL); - - 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; -} - - -/* - * Main routine for reading/parsing the headers - * of a message content. - * - * toplevel = 1 # we are at the top level of the message - * toplevel = 0 # we are inside message type or multipart type - * # other than multipart/digest - * toplevel = -1 # we are inside multipart/digest - */ - -static CT -get_content (FILE *in, char *file, int toplevel) -{ - int compnum, state; - char buf[BUFSIZ], name[NAMESZ]; - CT ct; - - if (!(ct = (CT) calloc (1, sizeof(*ct)))) - adios (NULL, "out of memory"); - - ct->c_fp = in; - ct->c_file = add (file, NULL); - ct->c_begin = ftell (ct->c_fp) + 1; - - /* - * Read the content headers - */ - for (compnum = 1, state = FLD;;) { - switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { - case FLD: - case FLDPLUS: - case FLDEOF: - compnum++; - - /* Get MIME-Version field */ - if (!mh_strcasecmp (name, VRSN_FIELD)) { - int ucmp; - char c; - unsigned char *cp, *dp; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } - - if (ct->c_vrsn) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, VRSN_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } - - ct->c_vrsn = cp; - while (isspace (*cp)) - cp++; - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - if (debugsw) - fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); - - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) - goto out; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ucmp = !mh_strcasecmp (cp, VRSN_VALUE); - *dp = c; - if (!ucmp) - admonish (NULL, - "message %s has unknown value for %s: field (%s)", - ct->c_file, VRSN_FIELD, cp); - goto got_header; - } - - /* Get Content-Type field */ - if (!mh_strcasecmp (name, TYPE_FIELD)) { - char *cp; - struct str2init *s2i; - CI ci = &ct->c_ctinfo; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } - - /* Check if we've already seen a Content-Type header */ - if (ct->c_ctline) { - char *dp = trimcpy (cp); - - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, TYPE_FIELD, dp); - free (dp); - free (cp); - goto out; - } - - /* Parse the Content-Type field */ - if (get_ctinfo (cp, ct, 0) == NOTOK) - goto out; - - /* - * Set the Init function and the internal - * flag for this content type. - */ - 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++; - ct->c_type = s2i->si_val; - ct->c_ctinitfnx = s2i->si_init; - goto got_header; - } - - /* Get Content-Transfer-Encoding field */ - if (!mh_strcasecmp (name, ENCODING_FIELD)) { - unsigned char *cp, *dp; - char c; - struct str2init *s2i; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } - - /* - * Check if we've already seen the - * Content-Transfer-Encoding field - */ - if (ct->c_celine) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, ENCODING_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } - - ct->c_celine = cp; /* Save copy of this field */ - while (isspace (*cp)) - cp++; - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp; - *dp = '\0'; - - /* - * Find the internal flag and Init function - * for this transfer encoding. - */ - for (s2i = str2ces; s2i->si_key; s2i++) - if (!mh_strcasecmp (cp, s2i->si_key)) - break; - if (!s2i->si_key && !uprf (cp, "X-")) - s2i++; - *dp = c; - ct->c_encoding = s2i->si_val; - - /* Call the Init function for this encoding */ - if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) - goto out; - goto got_header; - } - - /* Get Content-ID field */ - if (!mh_strcasecmp (name, ID_FIELD)) { - ct->c_id = add (buf, ct->c_id); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - ct->c_id = add (buf, ct->c_id); - } - goto got_header; - } - - /* Get Content-Description field */ - if (!mh_strcasecmp (name, DESCR_FIELD)) { - ct->c_descr = add (buf, ct->c_descr); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - ct->c_descr = add (buf, ct->c_descr); - } - goto got_header; - } - - /* Get Content-Disposition field */ - if (!mh_strcasecmp (name, DISPO_FIELD)) { - ct->c_dispo = add (buf, ct->c_dispo); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - ct->c_dispo = add (buf, ct->c_dispo); - } - goto got_header; - } - - /* Get Content-MD5 field */ - if (!mh_strcasecmp (name, MD5_FIELD)) { - unsigned char *cp, *dp; - char *ep; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } - - if (!checksw) { - free (cp); - goto got_header; - } - - if (ct->c_digested) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, MD5_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } - - ep = cp; - while (isspace (*cp)) - cp++; - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - if (debugsw) - fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); - - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { - free (ep); - goto out; - } - - for (dp = cp; *dp && !isspace (*dp); dp++) - continue; - *dp = '\0'; - - readDigest (ct, cp); - free (ep); - ct->c_digested++; - goto got_header; - } - -#if 0 - if (uprf (name, XXX_FIELD_PRF)) - advise (NULL, "unknown field (%s) in message %s", - name, ct->c_file); - /* and fall... */ -#endif - - while (state == FLDPLUS) - state = m_getfld (state, name, buf, sizeof(buf), in); - -got_header: - if (state != FLDEOF) { - ct->c_begin = ftell (in) + 1; - continue; - } - /* else fall... */ - - case BODY: - case BODYEOF: - ct->c_begin = ftell (in) - strlen (buf); - break; - - case FILEEOF: - ct->c_begin = ftell (in); - break; - - case LENERR: - case FMTERR: - adios (NULL, "message format error in component #%d", compnum); - - default: - adios (NULL, "getfld() returned %d", state); - } - break; - } - - /* - * Check if we saw a Content-Type field. - * If not, then assign a default value for - * it, and the Init function. - */ - if (!ct->c_ctline) { - /* - * If we are inside a multipart/digest message, - * so default type is message/rfc822 - */ - if (toplevel < 0) { - if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) - goto out; - ct->c_type = CT_MESSAGE; - ct->c_ctinitfnx = InitMessage; - } else { - /* - * Else default type is text/plain - */ - if (get_ctinfo ("text/plain", ct, 0) == NOTOK) - goto out; - ct->c_type = CT_TEXT; - ct->c_ctinitfnx = InitText; - } - } - - /* Use default Transfer-Encoding, if necessary */ - if (!ct->c_celine) { - ct->c_encoding = CE_7BIT; - Init7Bit (ct); - } - - return ct; - -out: - free_content (ct); - return NULL; -} - - -/* - * small routine to add header field to list - */ - -static int -add_header (CT ct, char *name, char *value) -{ - HF hp; - - /* allocate header field structure */ - hp = mh_xmalloc (sizeof(*hp)); - - /* link data into header structure */ - hp->name = name; - hp->value = value; - hp->next = NULL; - - /* link header structure into the list */ - if (ct->c_first_hf == NULL) { - ct->c_first_hf = hp; /* this is the first */ - ct->c_last_hf = hp; - } else { - ct->c_last_hf->next = hp; /* add it to the end */ - ct->c_last_hf = hp; - } - - return 0; -} - - -/* - * Used to parse both: - * 1) Content-Type line - * 2) composition directives - * - * and fills in the information of the CTinfo structure. - */ - -static int -get_ctinfo (unsigned char *cp, CT ct, int magic) -{ - int i; - unsigned char *dp; - char **ap, **ep; - char c; - CI ci; - - ci = &ct->c_ctinfo; - i = strlen (invo_name) + 2; - - /* store copy of Content-Type line */ - cp = ct->c_ctline = add (cp, NULL); - - while (isspace (*cp)) /* trim leading spaces */ - cp++; - - /* change newlines to spaces */ - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - - /* trim trailing spaces */ - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - - if (debugsw) - fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ci->ci_type = add (cp, NULL); /* store content type */ - *dp = c, cp = dp; - - if (!*ci->ci_type) { - advise (NULL, "invalid %s: field in message %s (empty type)", - TYPE_FIELD, ct->c_file); - return NOTOK; - } - - /* down case the content type string */ - for (dp = ci->ci_type; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - if (*cp != '/') { - if (!magic) - ci->ci_subtype = add ("", NULL); - goto magic_skip; - } - - cp++; - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ci->ci_subtype = add (cp, NULL); /* store the content subtype */ - *dp = c, cp = dp; - - if (!*ci->ci_subtype) { - advise (NULL, - "invalid %s: field in message %s (empty subtype for \"%s\")", - TYPE_FIELD, ct->c_file, ci->ci_type); - return NOTOK; - } - - /* down case the content subtype string */ - for (dp = ci->ci_subtype; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - -magic_skip: - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - /* - * Parse attribute/value pairs given with Content-Type - */ - ep = (ap = ci->ci_attrs) + NPARMS; - while (*cp == ';') { - char *vp; - unsigned char *up; - - if (ap >= ep) { - advise (NULL, - "too many parameters in message %s's %s: field (%d max)", - ct->c_file, TYPE_FIELD, NPARMS); - return NOTOK; - } - - cp++; - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - if (*cp == 0) { - advise (NULL, - "extraneous trailing ';' in message %s's %s: parameter list", - ct->c_file, TYPE_FIELD); - return OK; - } - - /* down case the attribute name */ - for (dp = cp; istoken (*dp); dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - - for (up = dp; isspace (*dp); ) - dp++; - if (dp == cp || *dp != '=') { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)", - ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp); - return NOTOK; - } - - vp = (*ap = add (cp, NULL)) + (up - cp); - *vp = '\0'; - for (dp++; isspace (*dp); ) - dp++; - - /* now add the attribute value */ - ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); - - if (*dp == '"') { - for (cp = ++dp, dp = vp;;) { - switch (c = *cp++) { - case '\0': -bad_quote: - advise (NULL, - "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - - case '\\': - *dp++ = c; - if ((c = *cp++) == '\0') - goto bad_quote; - /* else fall... */ - - default: - *dp++ = c; - continue; - - case '"': - *dp = '\0'; - break; - } - break; - } - } else { - for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) - continue; - *dp = '\0'; - } - if (!*vp) { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - } - ap++; - - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - } - - /* - * Get any given in buffer - */ - if (magic && *cp == '<') { - if (ct->c_id) { - free (ct->c_id); - ct->c_id = NULL; - } - if (!(dp = strchr(ct->c_id = ++cp, '>'))) { - advise (NULL, "invalid ID in message %s", ct->c_file); - return NOTOK; - } - c = *dp; - *dp = '\0'; - if (*ct->c_id) - ct->c_id = concat ("<", ct->c_id, ">\n", NULL); - else - ct->c_id = NULL; - *dp++ = c; - cp = dp; - - while (isspace (*cp)) - cp++; - } - - /* - * Get any [Content-Description] given in buffer. - */ - if (magic && *cp == '[') { - ct->c_descr = ++cp; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (*dp == ']') - break; - if (dp < cp) { - advise (NULL, "invalid description in message %s", ct->c_file); - ct->c_descr = NULL; - return NOTOK; - } - - c = *dp; - *dp = '\0'; - if (*ct->c_descr) - ct->c_descr = concat (ct->c_descr, "\n", NULL); - else - ct->c_descr = NULL; - *dp++ = c; - cp = dp; - - while (isspace (*cp)) - cp++; - } - - /* - * Get any {Content-Disposition} given in buffer. - */ - if (magic && *cp == '{') { - ct->c_dispo = ++cp; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (*dp == '}') - break; - if (dp < cp) { - advise (NULL, "invalid disposition in message %s", ct->c_file); - ct->c_dispo = NULL; - return NOTOK; - } - - c = *dp; - *dp = '\0'; - if (*ct->c_dispo) - ct->c_dispo = concat (ct->c_dispo, "\n", NULL); - else - ct->c_dispo = NULL; - *dp++ = c; - cp = dp; - - while (isspace (*cp)) - cp++; - } - - /* - * Check if anything is left over - */ - if (*cp) { - if (magic) { - ci->ci_magic = add (cp, NULL); - - /* If there is a Content-Disposition header and it doesn't - have a *filename=, extract it from the magic contents. - The r1bindex call skips any leading directory - components. */ - if (ct->c_dispo) - ct->c_dispo = - incl_name_value (ct->c_dispo, - "filename", - r1bindex (extract_name_value ("name", - ci-> - ci_magic), - '/')); - } - else - advise (NULL, - "extraneous information in message %s's %s: field\n%*.*s(%s)", - ct->c_file, TYPE_FIELD, i, i, "", cp); - } - - return OK; -} - - -static int -get_comment (CT ct, unsigned char **ap, int istype) -{ - int i; - char *bp; - unsigned char *cp; - char c, buffer[BUFSIZ], *dp; - CI ci; - - ci = &ct->c_ctinfo; - cp = *ap; - bp = buffer; - cp++; - - for (i = 0;;) { - switch (c = *cp++) { - case '\0': -invalid: - advise (NULL, "invalid comment in message %s's %s: field", - ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); - return NOTOK; - - case '\\': - *bp++ = c; - if ((c = *cp++) == '\0') - goto invalid; - *bp++ = c; - continue; - - case '(': - i++; - /* and fall... */ - default: - *bp++ = c; - continue; - - case ')': - if (--i < 0) - break; - *bp++ = c; - continue; - } - break; - } - *bp = '\0'; - - if (istype) { - if ((dp = ci->ci_comment)) { - ci->ci_comment = concat (dp, " ", buffer, NULL); - free (dp); - } else { - ci->ci_comment = add (buffer, NULL); - } - } - - while (isspace (*cp)) - cp++; - - *ap = cp; - return OK; -} - - -/* - * CONTENTS - * - * Handles content types audio, image, and video. - * There's not much to do right here. - */ - -static int -InitGeneric (CT ct) -{ - return OK; /* not much to do here */ -} - - -/* - * TEXT - */ - -static int -InitText (CT ct) -{ - char **ap, **ep; - struct k2v *kv; - struct text *t; - CI ci = &ct->c_ctinfo; - - /* check for missing subtype */ - if (!*ci->ci_subtype) - ci->ci_subtype = add ("plain", ci->ci_subtype); - - /* match subtype */ - for (kv = SubText; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - /* allocate text character set structure */ - if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) t; - - /* initially mark character set as unspecified */ - t->tx_charset = CHARSET_UNSPECIFIED; - - /* scan for charset parameter */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) - if (!mh_strcasecmp (*ap, "charset")) - break; - - /* check if content specified a character set */ - if (*ap) { - /* match character set or set to CHARSET_UNKNOWN */ - for (kv = Charset; kv->kv_key; kv++) - if (!mh_strcasecmp (*ep, kv->kv_key)) - break; - t->tx_charset = kv->kv_value; - } - - return OK; -} - - -/* - * MULTIPART - */ - -static int -InitMultiPart (CT ct) -{ - int inout; - long last, pos; - unsigned char *cp, *dp; - char **ap, **ep; - char *bp, buffer[BUFSIZ]; - struct multipart *m; - struct k2v *kv; - struct part *part, **next; - CI ci = &ct->c_ctinfo; - CT p; - FILE *fp; - - /* - * The encoding for multipart messages must be either - * 7bit, 8bit, or binary (per RFC2045). - */ - if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT - && ct->c_encoding != CE_BINARY) { - admonish (NULL, - "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary", - ci->ci_type, ci->ci_subtype, ct->c_file); - return NOTOK; - } - - /* match subtype */ - for (kv = SubMultiPart; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - /* - * Check for "boundary" parameter, which is - * required for multipart messages. - */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "boundary")) { - bp = *ep; - break; - } - } - - /* complain if boundary parameter is missing */ - if (!*ap) { - advise (NULL, - "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - /* allocate primary structure for multipart info */ - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) m; - - /* check if boundary parameter contains only whitespace characters */ - for (cp = bp; isspace (*cp); cp++) - continue; - if (!*cp) { - advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - /* remove trailing whitespace from boundary parameter */ - for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - - /* record boundary separators */ - m->mp_start = concat (bp, "\n", NULL); - m->mp_stop = concat (bp, "--\n", NULL); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - advise (ct->c_file, "unable to open for reading"); - return NOTOK; - } - - fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET); - last = ct->c_end; - next = &m->mp_parts; - part = NULL; - inout = 1; - - while (fgets (buffer, sizeof(buffer) - 1, fp)) { - if (pos > last) - break; - - pos += strlen (buffer); - if (buffer[0] != '-' || buffer[1] != '-') - continue; - if (inout) { - if (strcmp (buffer + 2, m->mp_start)) - continue; -next_part: - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) - adios (NULL, "out of memory"); - *next = part; - next = &part->mp_next; - - if (!(p = get_content (fp, ct->c_file, - rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; - } - p->c_fp = NULL; - part->mp_part = p; - pos = p->c_begin; - fseek (fp, pos, SEEK_SET); - inout = 0; - } else { - if (strcmp (buffer + 2, m->mp_start) == 0) { - inout = 1; -end_part: - p = part->mp_part; - p->c_end = ftell(fp) - (strlen(buffer) + 1); - if (p->c_end < p->c_begin) - p->c_begin = p->c_end; - if (inout) - goto next_part; - goto last_part; - } else { - if (strcmp (buffer + 2, m->mp_stop) == 0) - goto end_part; - } - } - } - - advise (NULL, "bogus multipart content in message %s", ct->c_file); - if (!inout && part) { - p = part->mp_part; - p->c_end = ct->c_end; - - if (p->c_begin >= p->c_end) { - for (next = &m->mp_parts; *next != part; - next = &((*next)->mp_next)) - continue; - *next = NULL; - free_content (p); - free ((char *) part); - } - } - -last_part: - /* reverse the order of the parts for multipart/alternative */ - if (ct->c_subtype == MULTI_ALTERNATE) - reverse_parts (ct); - - /* - * label all subparts with part number, and - * then initialize the content of the subpart. - */ - { - int partnum; - char *pp; - char partnam[BUFSIZ]; - - if (ct->c_partno) { - snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno); - pp = partnam + strlen (partnam); - } else { - pp = partnam; - } - - for (part = m->mp_parts, partnum = 1; part; - part = part->mp_next, partnum++) { - p = part->mp_part; - - sprintf (pp, "%d", partnum); - p->c_partno = add (partnam, NULL); - - /* initialize the content of the subparts */ - if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; - } - } - } - - fclose (ct->c_fp); - ct->c_fp = NULL; - return OK; -} - - -/* - * reverse the order of the parts of a multipart - */ - -static void -reverse_parts (CT ct) -{ - int i; - struct multipart *m; - struct part **base, **bmp, **next, *part; - - m = (struct multipart *) ct->c_ctparams; - - /* if only one part, just return */ - if (!m->mp_parts || !m->mp_parts->mp_next) - return; - - /* count number of parts */ - i = 0; - for (part = m->mp_parts; part; part = part->mp_next) - i++; - - /* allocate array of pointers to the parts */ - if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base)))) - adios (NULL, "out of memory"); - bmp = base; - - /* point at all the parts */ - for (part = m->mp_parts; part; part = part->mp_next) - *bmp++ = part; - *bmp = NULL; - - /* reverse the order of the parts */ - next = &m->mp_parts; - for (bmp--; bmp >= base; bmp--) { - part = *bmp; - *next = part; - next = &part->mp_next; - } - *next = NULL; - - /* free array of pointers */ - free ((char *) base); -} - - -/* - * MESSAGE - */ - -static int -InitMessage (CT ct) -{ - struct k2v *kv; - CI ci = &ct->c_ctinfo; - - if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) { - admonish (NULL, - "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit", - ci->ci_type, ci->ci_subtype, ct->c_file); - return NOTOK; - } - - /* check for missing subtype */ - if (!*ci->ci_subtype) - ci->ci_subtype = add ("rfc822", ci->ci_subtype); - - /* match subtype */ - for (kv = SubMessage; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - switch (ct->c_subtype) { - case MESSAGE_RFC822: - break; - - case MESSAGE_PARTIAL: - { - char **ap, **ep; - struct partial *p; - - if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) p; - - /* scan for parameters "id", "number", and "total" */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "id")) { - p->pm_partid = add (*ep, NULL); - continue; - } - if (!mh_strcasecmp (*ap, "number")) { - if (sscanf (*ep, "%d", &p->pm_partno) != 1 - || p->pm_partno < 1) { -invalid_param: - advise (NULL, - "invalid %s parameter for \"%s/%s\" type in message %s's %s field", - *ap, ci->ci_type, ci->ci_subtype, - ct->c_file, TYPE_FIELD); - return NOTOK; - } - continue; - } - if (!mh_strcasecmp (*ap, "total")) { - if (sscanf (*ep, "%d", &p->pm_maxno) != 1 - || p->pm_maxno < 1) - goto invalid_param; - continue; - } - } - - if (!p->pm_partid - || !p->pm_partno - || (p->pm_maxno && p->pm_partno > p->pm_maxno)) { - advise (NULL, - "invalid parameters for \"%s/%s\" type in message %s's %s field", - ci->ci_type, ci->ci_subtype, - ct->c_file, TYPE_FIELD); - return NOTOK; - } - } - break; - - case MESSAGE_EXTERNAL: - { - int exresult; - struct exbody *e; - CT p; - FILE *fp; - - if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) e; - - if (!ct->c_fp - && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - advise (ct->c_file, "unable to open for reading"); - return NOTOK; - } - - fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET); - - if (!(p = get_content (fp, ct->c_file, 0))) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; - } - - e->eb_parent = ct; - e->eb_content = p; - p->c_ctexbody = e; - if ((exresult = params_external (ct, 0)) != NOTOK - && p->c_ceopenfnx == openMail) { - int cc, size; - char *bp; - - if ((size = ct->c_end - p->c_begin) <= 0) { - if (!e->eb_subject) - content_error (NULL, ct, - "empty body for access-type=mail-server"); - goto no_body; - } - - e->eb_body = bp = mh_xmalloc ((unsigned) size); - fseek (p->c_fp, p->c_begin, SEEK_SET); - while (size > 0) - switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) { - case NOTOK: - adios ("failed", "fread"); - - case OK: - adios (NULL, "unexpected EOF from fread"); - - default: - bp += cc, size -= cc; - break; - } - *bp = 0; - } -no_body: - p->c_fp = NULL; - p->c_end = p->c_begin; - - fclose (ct->c_fp); - ct->c_fp = NULL; - - if (exresult == NOTOK) - return NOTOK; - if (e->eb_flags == NOTOK) - return OK; - - switch (p->c_type) { - case CT_MULTIPART: - break; - - case CT_MESSAGE: - if (p->c_subtype != MESSAGE_RFC822) - break; - /* else fall... */ - default: - e->eb_partno = ct->c_partno; - if (p->c_ctinitfnx) - (*p->c_ctinitfnx) (p); - break; - } - } - break; - - default: - break; - } - - return OK; -} - - -static int -params_external (CT ct, int composing) -{ - char **ap, **ep; - struct exbody *e = (struct exbody *) ct->c_ctparams; - CI ci = &ct->c_ctinfo; - - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "access-type")) { - struct str2init *s2i; - CT p = e->eb_content; - - for (s2i = str2methods; s2i->si_key; s2i++) - if (!mh_strcasecmp (*ep, s2i->si_key)) - break; - - if (!s2i->si_key) { - e->eb_access = *ep; - e->eb_flags = NOTOK; - p->c_encoding = CE_EXTERNAL; - continue; - } - e->eb_access = s2i->si_key; - e->eb_flags = s2i->si_val; - p->c_encoding = CE_EXTERNAL; - - /* Call the Init function for this external type */ - if ((*s2i->si_init)(p) == NOTOK) - return NOTOK; - continue; - } - if (!mh_strcasecmp (*ap, "name")) { - e->eb_name = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "permission")) { - e->eb_permission = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "site")) { - e->eb_site = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "directory")) { - e->eb_dir = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "mode")) { - e->eb_mode = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "size")) { - sscanf (*ep, "%lu", &e->eb_size); - continue; - } - if (!mh_strcasecmp (*ap, "server")) { - e->eb_server = *ep; - continue; - } - if (!mh_strcasecmp (*ap, "subject")) { - e->eb_subject = *ep; - continue; - } - if (composing && !mh_strcasecmp (*ap, "body")) { - e->eb_body = getcpy (*ep); - continue; - } - } - - if (!e->eb_access) { - advise (NULL, - "invalid parameters for \"%s/%s\" type in message %s's %s field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - return OK; -} - - -/* - * APPLICATION - */ - -static int -InitApplication (CT ct) -{ - struct k2v *kv; - CI ci = &ct->c_ctinfo; - - /* match subtype */ - for (kv = SubApplication; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - return OK; -} - - -/* - * 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; -} - - -/* - * TRANSFER ENCODINGS - */ - -static int -init_encoding (CT ct, OpenCEFunc openfnx) -{ - CE ce; - - if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) - adios (NULL, "out of memory"); - - ct->c_cefile = ce; - ct->c_ceopenfnx = openfnx; - ct->c_ceclosefnx = close_encoding; - ct->c_cesizefnx = size_encoding; - - return OK; -} - - -static void -close_encoding (CT ct) -{ - CE ce; - - if (!(ce = ct->c_cefile)) - return; - - if (ce->ce_fp) { - fclose (ce->ce_fp); - ce->ce_fp = NULL; - } -} - - -static unsigned long -size_encoding (CT ct) -{ - int fd; - unsigned long size; - char *file; - CE ce; - struct stat st; - - if (!(ce = ct->c_cefile)) - return (ct->c_end - ct->c_begin); - - if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK) - return (long) st.st_size; - - if (ce->ce_file) { - if (stat (ce->ce_file, &st) != NOTOK) - return (long) st.st_size; - else - return 0L; - } - - if (ct->c_encoding == CE_EXTERNAL) - return (ct->c_end - ct->c_begin); - - file = NULL; - if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) - return (ct->c_end - ct->c_begin); - - if (fstat (fd, &st) != NOTOK) - size = (long) st.st_size; - else - size = 0L; - - (*ct->c_ceclosefnx) (ct); - return size; -} - - -/* - * BASE64 - */ - -static unsigned char b642nib[0x80] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, - 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff -}; - - -static int -InitBase64 (CT ct) -{ - return init_encoding (ct, openBase64); -} - - -static int -openBase64 (CT ct, char **file) -{ - int bitno, cc, digested; - int fd, len, skip; - unsigned long bits; - unsigned char value, *b, *b1, *b2, *b3; - unsigned char *cp, *ep; - char buffer[BUFSIZ]; - CE ce; - MD5_CTX mdContext; - - b = (unsigned char *) &bits; - b1 = &b[endian > 0 ? 1 : 2]; - b2 = &b[endian > 0 ? 2 : 1]; - b3 = &b[endian > 0 ? 3 : 0]; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(1)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - if ((digested = ct->c_digested)) - MD5Init (&mdContext); - - bitno = 18; - bits = 0L; - skip = 0; - - lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); - while (len > 0) { - switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { - case NOTOK: - content_error (ct->c_file, ct, "error reading from"); - goto clean_up; - - case OK: - content_error (NULL, ct, "premature eof"); - goto clean_up; - - default: - if (cc > len) - cc = len; - len -= cc; - - for (ep = (cp = buffer) + cc; cp < ep; cp++) { - switch (*cp) { - default: - if (isspace (*cp)) - break; - if (skip || (*cp & 0x80) - || (value = b642nib[*cp & 0x7f]) > 0x3f) { - if (debugsw) { - fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", - *cp, - (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)), - skip); - } - content_error (NULL, ct, - "invalid BASE64 encoding -- continuing"); - continue; - } - - bits |= value << bitno; -test_end: - if ((bitno -= 6) < 0) { - putc ((char) *b1, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b1, 1); - if (skip < 2) { - putc ((char) *b2, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b2, 1); - if (skip < 1) { - putc ((char) *b3, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b3, 1); - } - } - - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, - "error writing to"); - goto clean_up; - } - bitno = 18, bits = 0L, skip = 0; - } - break; - - case '=': - if (++skip > 3) - goto self_delimiting; - goto test_end; - } - } - } - } - - if (bitno != 18) { - if (debugsw) - fprintf (stderr, "premature ending (bitno %d)\n", bitno); - - content_error (NULL, ct, "invalid BASE64 encoding"); - goto clean_up; - } - -self_delimiting: - fseek (ct->c_fp, 0L, SEEK_SET); - - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - - if (digested) { - unsigned char digest[16]; - - MD5Final (digest, &mdContext); - if (memcmp((char *) digest, (char *) ct->c_digest, - sizeof(digest) / sizeof(digest[0]))) - content_error (NULL, ct, - "content integrity suspect (digest mismatch) -- continuing"); - else - if (debugsw) - fprintf (stderr, "content integrity confirmed\n"); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; -} - - -/* - * QUOTED PRINTABLE - */ - -static char hex2nib[0x80] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - - -static int -InitQuoted (CT ct) -{ - return init_encoding (ct, openQuoted); -} - - -static int -openQuoted (CT ct, char **file) -{ - int cc, digested, len, quoted; - unsigned char *cp, *ep; - char buffer[BUFSIZ]; - unsigned char mask; - CE ce; - MD5_CTX mdContext; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(2)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - if ((digested = ct->c_digested)) - MD5Init (&mdContext); - - quoted = 0; -#ifdef lint - mask = 0; -#endif - - fseek (ct->c_fp, ct->c_begin, SEEK_SET); - while (len > 0) { - char *dp; - - if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) { - content_error (NULL, ct, "premature eof"); - goto clean_up; - } - - if ((cc = strlen (buffer)) > len) - cc = len; - len -= cc; - - for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) - if (!isspace (*ep)) - break; - *++ep = '\n', ep++; - - for (; cp < ep; cp++) { - if (quoted) { - if (quoted > 1) { - if (!isxdigit (*cp)) { -invalid_hex: - dp = "expecting hexidecimal-digit"; - goto invalid_encoding; - } - mask <<= 4; - mask |= hex2nib[*cp & 0x7f]; - putc (mask, ce->ce_fp); - if (digested) - MD5Update (&mdContext, &mask, 1); - } else { - switch (*cp) { - case ':': - putc (*cp, ce->ce_fp); - if (digested) - MD5Update (&mdContext, (unsigned char *) ":", 1); - break; - - default: - if (!isxdigit (*cp)) - goto invalid_hex; - mask = hex2nib[*cp & 0x7f]; - quoted = 2; - continue; - } - } - - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - quoted = 0; - continue; - } - - switch (*cp) { - default: - if (*cp < '!' || *cp > '~') { - int i; - dp = "expecting character in range [!..~]"; - -invalid_encoding: - i = strlen (invo_name) + 2; - content_error (NULL, ct, - "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x", - dp, i, i, "", *cp); - goto clean_up; - } - /* and fall...*/ - case ' ': - case '\t': - case '\n': - putc (*cp, ce->ce_fp); - if (digested) { - if (*cp == '\n') - MD5Update (&mdContext, (unsigned char *) "\r\n",2); - else - MD5Update (&mdContext, (unsigned char *) cp, 1); - } - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - break; - - case '=': - if (*++cp != '\n') { - quoted = 1; - cp--; - } - break; - } - } - } - if (quoted) { - content_error (NULL, ct, - "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting"); - goto clean_up; - } - - fseek (ct->c_fp, 0L, SEEK_SET); - - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - - if (digested) { - unsigned char digest[16]; - - MD5Final (digest, &mdContext); - if (memcmp((char *) digest, (char *) ct->c_digest, - sizeof(digest) / sizeof(digest[0]))) - content_error (NULL, ct, - "content integrity suspect (digest mismatch) -- continuing"); - else - if (debugsw) - fprintf (stderr, "content integrity confirmed\n"); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; -} - - -/* - * 7BIT - */ - -static int -Init7Bit (CT ct) -{ - if (init_encoding (ct, open7Bit) == NOTOK) - return NOTOK; - - ct->c_cesizefnx = NULL; /* no need to decode for real size */ - return OK; -} - - -static int -open7Bit (CT ct, char **file) -{ - int cc, fd, len; - char buffer[BUFSIZ]; - CE ce; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if (ct->c_type == CT_MULTIPART) { - char **ap, **ep; - CI ci = &ct->c_ctinfo; - - len = 0; - fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype); - len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type) - + 1 + strlen (ci->ci_subtype); - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - putc (';', ce->ce_fp); - len++; - - snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); - - if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { - fputs ("\n\t", ce->ce_fp); - len = 8; - } else { - putc (' ', ce->ce_fp); - len++; - } - fprintf (ce->ce_fp, "%s", buffer); - len += cc; - } - - if (ci->ci_comment) { - if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) { - fputs ("\n\t", ce->ce_fp); - len = 8; - } - else { - putc (' ', ce->ce_fp); - len++; - } - fprintf (ce->ce_fp, "(%s)", ci->ci_comment); - len += cc; - } - fprintf (ce->ce_fp, "\n"); - if (ct->c_id) - fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id); - if (ct->c_descr) - fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr); - if (ct->c_dispo) - fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo); - fprintf (ce->ce_fp, "\n"); - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(3)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); - while (len > 0) - switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { - case NOTOK: - content_error (ct->c_file, ct, "error reading from"); - goto clean_up; - - case OK: - content_error (NULL, ct, "premature eof"); - goto clean_up; - - default: - if (cc > len) - cc = len; - len -= cc; - - fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp); - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - } - - fseek (ct->c_fp, 0L, SEEK_SET); - - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; -} - - -/* - * External - */ - -static int -openExternal (CT ct, CT cb, CE ce, char **file, int *fd) -{ - char cachefile[BUFSIZ]; - - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_already; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_already; - } - - if (find_cache (ct, rcachesw, (int *) 0, cb->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - if ((ce->ce_fp = fopen (cachefile, "r"))) { - ce->ce_file = getcpy (cachefile); - ce->ce_unlink = 0; - goto ready_already; - } else { - admonish (cachefile, "unable to fopen for reading"); - } - } - - return OK; - -ready_already: - *file = ce->ce_file; - *fd = fileno (ce->ce_fp); - return DONE; -} - -/* - * File - */ - -static int -InitFile (CT ct) -{ - return init_encoding (ct, openFile); -} - - -static int -openFile (CT ct, char **file) -{ - int fd, cachetype; - char cachefile[BUFSIZ]; - struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; - - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; - - case OK: - break; - - case DONE: - return fd; - } - - if (!e->eb_name) { - content_error (NULL, ct, "missing name parameter"); - return NOTOK; - } - - ce->ce_file = getcpy (e->eb_name); - ce->ce_unlink = 0; - - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - - if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write")) - && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - int mask; - FILE *fp; - - mask = umask (cachetype ? ~m_gmprot () : 0222); - if ((fp = fopen (cachefile, "w"))) { - int cc; - char buffer[BUFSIZ]; - FILE *gp = ce->ce_fp; - - fseek (gp, 0L, SEEK_SET); - - while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) - > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); - fflush (fp); - - if (ferror (gp)) { - admonish (ce->ce_file, "error reading"); - unlink (cachefile); - } - else - if (ferror (fp)) { - admonish (cachefile, "error writing"); - unlink (cachefile); - } - fclose (fp); - } - umask (mask); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); -} + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; -/* - * FTP - */ + /* abort if draft has Mime-Version header field */ + if (!mh_strcasecmp (name, VRSN_FIELD)) + adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD); -static int -InitFTP (CT ct) -{ - return init_encoding (ct, openFTP); -} + /* 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; + } -static int -openFTP (CT ct, char **file) -{ - int cachetype, caching, fd; - int len, buflen; - char *bp, *ftp, *user, *pass; - char buffer[BUFSIZ], cachefile[BUFSIZ]; - struct exbody *e; - CE ce; - static char *username = NULL; - static char *password = NULL; + /* get copies of the buffers */ + np = add (name, NULL); + vp = add (buf, NULL); - e = ct->c_ctexbody; - ce = ct->c_cefile; + /* if necessary, get rest of field */ + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + vp = add (buf, vp); /* add to previous value */ + } - if ((ftp = context_find (nmhaccessftp)) && !*ftp) - ftp = NULL; + /* Now add the header data to the list */ + add_header (ct, np, vp); -#ifndef BUILTIN_FTP - if (!ftp) - return NOTOK; -#endif +finish_field: + /* if this wasn't the last header field, then continue */ + if (state != FLDEOF) + continue; + /* else fall... */ - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; + case FILEEOF: + adios (NULL, "draft has empty body -- no directives!"); + /* NOTREACHED */ - case OK: + case BODY: + case BODYEOF: + fseek (in, (long) (-strlen (buf)), SEEK_CUR); break; - case DONE: - return fd; - } - - if (!e->eb_name || !e->eb_site) { - content_error (NULL, ct, "missing %s parameter", - e->eb_name ? "site": "name"); - return NOTOK; - } + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); - if (xpid) { - if (xpid < 0) - xpid = -xpid; - pidcheck (pidwait (xpid, NOTOK)); - xpid = 0; + default: + adios (NULL, "getfld() returned %d", state); + } + break; } - /* Get the buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer); + /* + * Now add the MIME-Version header field + * to the list of header fields. + */ + np = add (VRSN_FIELD, NULL); + vp = concat (" ", VRSN_VALUE, "\n", NULL); + add_header (ct, np, vp); /* - * Construct the query message for user + * 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. */ - snprintf (bp, buflen, "Retrieve %s", e->eb_name); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_partno) { - snprintf (bp, buflen, " (content %s)", e->eb_partno); - len = strlen (bp); - bp += len; - buflen -= len; - } + if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_MIXED; + ct->c_file = add (infile, NULL); - snprintf (bp, buflen, "\n using %sFTP from site %s", - e->eb_flags ? "anonymous " : "", e->eb_site); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_size > 0) { - snprintf (bp, buflen, " (%lu octets)", e->eb_size); - len = strlen (bp); - bp += len; - buflen -= len; - } - snprintf (bp, buflen, "? "); + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; /* - * Now, check the answer + * read and parse the composition file + * and the directives it contains. */ - if (!getanswer (buffer)) - return NOTOK; - - if (e->eb_flags) { - user = "anonymous"; - snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ()); - pass = buffer; - } else { - ruserpass (e->eb_site, &username, &password); - user = username; - pass = password; - } + while (fgetstr (buf, sizeof(buf) - 1, in)) { + struct part *part; + CT p; - ce->ce_unlink = (*file == NULL); - caching = 0; - cachefile[0] = '\0'; - if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write")) - && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - if (*file == NULL) { - ce->ce_unlink = 0; - caching = 1; + if (user_content (in, infile, buf, &p) == DONE) { + admonish (NULL, "ignoring spurious #end"); + continue; } - } - - if (*file) - ce->ce_file = add (*file, NULL); - else if (caching) - ce->ce_file = add (cachefile, NULL); - else - ce->ce_file = add (m_scratch ("", tmp), NULL); + if (!p) + continue; - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; } -#ifdef BUILTIN_FTP - if (ftp) -#endif - { - int child_id, i, vecp; - char *vec[9]; - - vecp = 0; - vec[vecp++] = r1bindex (ftp, '/'); - vec[vecp++] = e->eb_site; - vec[vecp++] = user; - vec[vecp++] = pass; - vec[vecp++] = e->eb_dir; - vec[vecp++] = e->eb_name; - vec[vecp++] = ce->ce_file, - vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii") - ? "ascii" : "binary"; - vec[vecp] = NULL; - - fflush (stdout); - - for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) - sleep (5); - switch (child_id) { - case NOTOK: - adios ("fork", "unable to"); - /* NOTREACHED */ + /* + * close the composition draft since + * it's not needed any longer. + */ + fclose (in); - case OK: - close (fileno (ce->ce_fp)); - execvp (ftp, vec); - fprintf (stderr, "unable to exec "); - perror (ftp); - _exit (-1); - /* NOTREACHED */ + /* check if any contents were found */ + if (!m->mp_parts) + adios (NULL, "no content directives found"); - default: - if (pidXwait (child_id, NULL)) { -#ifdef BUILTIN_FTP -losing_ftp: -#endif - username = password = NULL; - ce->ce_unlink = 1; - return NOTOK; - } - break; - } - } -#ifdef BUILTIN_FTP - else - if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name, - ce->ce_file, - e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0) - == NOTOK) - goto losing_ftp; -#endif + /* + * 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; - if (cachefile[0]) { - if (caching) - chmod (cachefile, cachetype ? m_gmprot () : 0444); - else { - int mask; - FILE *fp; + /* 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; - mask = umask (cachetype ? ~m_gmprot () : 0222); - if ((fp = fopen (cachefile, "w"))) { - int cc; - FILE *gp = ce->ce_fp; + free_content (ct); + ct = p; + } else { + set_id (ct, 1); + } - fseek (gp, 0L, SEEK_SET); + /* + * Fill out, or expand directives. Parse and execute + * commands specified by profile composition strings. + */ + compose_content (ct); - while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) - > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); - fflush (fp); + if ((cp = strchr(prefix, 'a')) == NULL) + adios (NULL, "internal error(4)"); - if (ferror (gp)) { - admonish (ce->ce_file, "error reading"); - unlink (cachefile); - } - else - if (ferror (fp)) { - admonish (cachefile, "error writing"); - unlink (cachefile); - } - fclose (fp); - } - umask (mask); + /* + * 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)++; } } - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); + /* Build the rest of the header field structures */ + build_headers (ct); + + return ct; } /* - * Mail + * Set up structures for placing unencoded + * content when building parts. */ static int -InitMail (CT ct) -{ - return init_encoding (ct, openMail); -} - - -static int -openMail (CT ct, char **file) +init_decoded_content (CT ct) { - int child_id, fd, i, vecp; - int len, buflen; - char *bp, buffer[BUFSIZ], *vec[7]; - struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; - - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; - - case OK: - break; - - case DONE: - return fd; - } - - if (!e->eb_server) { - content_error (NULL, ct, "missing server parameter"); - return NOTOK; - } - - if (xpid) { - if (xpid < 0) - xpid = -xpid; - pidcheck (pidwait (xpid, NOTOK)); - xpid = 0; - } - - /* Get buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer); - - /* Now construct query message */ - snprintf (bp, buflen, "Retrieve content"); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_partno) { - snprintf (bp, buflen, " %s", e->eb_partno); - len = strlen (bp); - bp += len; - buflen -= len; - } - - snprintf (bp, buflen, " by asking %s\n\n%s\n? ", - e->eb_server, - e->eb_subject ? e->eb_subject : e->eb_body); - - /* Now, check answer */ - if (!getanswer (buffer)) - return NOTOK; - - vecp = 0; - vec[vecp++] = r1bindex (mailproc, '/'); - vec[vecp++] = e->eb_server; - vec[vecp++] = "-subject"; - vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request"; - vec[vecp++] = "-body"; - vec[vecp++] = e->eb_body; - vec[vecp] = NULL; - - for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) - sleep (5); - switch (child_id) { - case NOTOK: - advise ("fork", "unable to"); - return NOTOK; - - case OK: - execvp (mailproc, vec); - fprintf (stderr, "unable to exec "); - perror (mailproc); - _exit (-1); - /* NOTREACHED */ - - default: - if (pidXwait (child_id, NULL) == OK) - advise (NULL, "request sent"); - break; - } + CE ce; - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } + if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) + adios (NULL, "out of memory"); - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } + ct->c_cefile = ce; + ct->c_ceopenfnx = open7Bit; /* since unencoded */ + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = NULL; /* since unencoded */ - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); + return OK; } @@ -4243,169 +1689,3 @@ calculate_digest (CT ct, int asciiP) vp = concat (" ", outbuf, "\n", NULL); return vp; } - - -static int -readDigest (CT ct, char *cp) -{ - int bitno, skip; - unsigned long bits; - char *bp = cp; - unsigned char *dp, value, *ep; - unsigned char *b, *b1, *b2, *b3; - - b = (unsigned char *) &bits, - b1 = &b[endian > 0 ? 1 : 2], - b2 = &b[endian > 0 ? 2 : 1], - b3 = &b[endian > 0 ? 3 : 0]; - bitno = 18; - bits = 0L; - skip = 0; - - for (ep = (dp = ct->c_digest) - + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++) - switch (*cp) { - default: - if (skip - || (*cp & 0x80) - || (value = b642nib[*cp & 0x7f]) > 0x3f) { - if (debugsw) - fprintf (stderr, "invalid BASE64 encoding\n"); - return NOTOK; - } - - bits |= value << bitno; -test_end: - if ((bitno -= 6) < 0) { - if (dp + (3 - skip) > ep) - goto invalid_digest; - *dp++ = *b1; - if (skip < 2) { - *dp++ = *b2; - if (skip < 1) - *dp++ = *b3; - } - bitno = 18; - bits = 0L; - skip = 0; - } - break; - - case '=': - if (++skip > 3) - goto self_delimiting; - goto test_end; - } - if (bitno != 18) { - if (debugsw) - fprintf (stderr, "premature ending (bitno %d)\n", bitno); - - return NOTOK; - } -self_delimiting: - if (dp != ep) { -invalid_digest: - if (debugsw) { - while (*cp) - cp++; - fprintf (stderr, "invalid MD5 digest (got %d octets)\n", - (int)(cp - bp)); - } - - return NOTOK; - } - - if (debugsw) { - fprintf (stderr, "MD5 digest="); - for (dp = ct->c_digest; dp < ep; dp++) - fprintf (stderr, "%02x", *dp & 0xff); - fprintf (stderr, "\n"); - } - - return OK; -} - - -/* Make sure that buf contains at least one appearance of name, - followed by =. If not, insert both name and value, just after - first semicolon, if any. Note that name should not contain a - trailing =. And quotes will be added around the value. Typical - usage: make sure that a Content-Disposition header contains - filename="foo". If it doesn't and value does, use value from - that. */ -static char * -incl_name_value (unsigned char *buf, char *name, char *value) { - char *newbuf = buf; - - /* Assume that name is non-null. */ - if (buf && value) { - char *name_plus_equal = concat (name, "=", NULL); - - if (! strstr (buf, name_plus_equal)) { - char *insertion; - unsigned char *cp; - char *prefix, *suffix; - - /* Trim trailing space, esp. newline. */ - for (cp = &buf[strlen (buf) - 1]; - cp >= buf && isspace (*cp); - --cp) { - *cp = '\0'; - } - - insertion = concat ("; ", name, "=", "\"", value, "\"", NULL); - - /* Insert at first semicolon, if any. If none, append to - end. */ - prefix = add (buf, NULL); - if ((cp = strchr (prefix, ';'))) { - suffix = concat (cp, NULL); - *cp = '\0'; - newbuf = concat (prefix, insertion, suffix, "\n", NULL); - free (suffix); - } else { - /* Append to end. */ - newbuf = concat (buf, insertion, "\n", NULL); - } - - free (prefix); - free (insertion); - free (buf); - } - - free (name_plus_equal); - } - - return newbuf; -} - - -/* Extract just name_suffix="foo", if any, from value. If there isn't - one, return the entire value. Note that, for example, a name_suffix - of name will match filename="foo", and return foo. */ -static char * -extract_name_value (char *name_suffix, char *value) { - char *extracted_name_value = value; - char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL); - char *name_suffix_equals = strstr (value, name_suffix_plus_quote); - char *cp; - - free (name_suffix_plus_quote); - if (name_suffix_equals) { - char *name_suffix_begin; - - /* Find first \". */ - for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */; - name_suffix_begin = ++cp; - /* Find second \". */ - for (; *cp != '"'; ++cp) /* empty */; - - extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1); - memcpy (extracted_name_value, - name_suffix_begin, - cp - name_suffix_begin); - extracted_name_value[cp - name_suffix_begin] = '\0'; - } - - return extracted_name_value; -} diff --git a/uip/mhlist.c b/uip/mhlist.c index 4bb21bb..4cca8d2 100644 --- a/uip/mhlist.c +++ b/uip/mhlist.c @@ -68,7 +68,6 @@ static struct swit switches[] = { /* mhparse.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ diff --git a/uip/mhn.c b/uip/mhn.c index 376fc58..45358e7 100644 --- a/uip/mhn.c +++ b/uip/mhn.c @@ -126,7 +126,6 @@ static struct swit switches[] = { /* mhparse.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ diff --git a/uip/mhparse.c b/uip/mhparse.c index 6771789..f5ff85c 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -46,24 +46,16 @@ int checksw = 0; /* check Content-MD5 field */ char *tmp; /* - * Structure for mapping types to their internal flags - */ -struct k2v { - char *kv_key; - int kv_value; -}; - -/* * Structures for TEXT messages */ -static struct k2v SubText[] = { +struct k2v SubText[] = { { "plain", TEXT_PLAIN }, { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */ { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */ { NULL, TEXT_UNKNOWN } /* this one must be last! */ }; -static struct k2v Charset[] = { +struct k2v Charset[] = { { "us-ascii", CHARSET_USASCII }, { "iso-8859-1", CHARSET_LATIN }, { NULL, CHARSET_UNKNOWN } /* this one must be last! */ @@ -72,7 +64,7 @@ static struct k2v Charset[] = { /* * Structures for MULTIPART messages */ -static struct k2v SubMultiPart[] = { +struct k2v SubMultiPart[] = { { "mixed", MULTI_MIXED }, { "alternative", MULTI_ALTERNATE }, { "digest", MULTI_DIGEST }, @@ -83,7 +75,7 @@ static struct k2v SubMultiPart[] = { /* * Structures for MESSAGE messages */ -static struct k2v SubMessage[] = { +struct k2v SubMessage[] = { { "rfc822", MESSAGE_RFC822 }, { "partial", MESSAGE_PARTIAL }, { "external-body", MESSAGE_EXTERNAL }, @@ -93,7 +85,7 @@ static struct k2v SubMessage[] = { /* * Structure for APPLICATION messages */ -static struct k2v SubApplication[] = { +struct k2v SubApplication[] = { { "octet-stream", APPLICATION_OCTETS }, { "postscript", APPLICATION_POSTSCRIPT }, { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ @@ -117,34 +109,24 @@ void free_content (CT); void free_encoding (CT, int); /* - * prototypes - */ -int pidcheck (int); -CT parse_mime (char *); - -/* * static prototypes */ static CT get_content (FILE *, char *, int); -static int add_header (CT, char *, char *); -static int get_ctinfo (unsigned char *, CT); static int get_comment (CT, unsigned char **, int); + static int InitGeneric (CT); static int InitText (CT); static int InitMultiPart (CT); static void reverse_parts (CT); static int InitMessage (CT); -static int params_external (CT, int); static int InitApplication (CT); static int init_encoding (CT, OpenCEFunc); -static void close_encoding (CT); static unsigned long size_encoding (CT); static int InitBase64 (CT); static int openBase64 (CT, char **); static int InitQuoted (CT); static int openQuoted (CT, char **); static int Init7Bit (CT); -static int open7Bit (CT, char **); static int openExternal (CT, CT, CE, char **, int *); static int InitFile (CT); static int openFile (CT, char **); @@ -154,17 +136,7 @@ static int InitMail (CT); static int openMail (CT, char **); static int readDigest (CT, char *); -/* - * Structures for mapping (content) types to - * the functions to handle them. - */ -struct str2init { - char *si_key; - int si_val; - InitFunc si_init; -}; - -static struct str2init str2cts[] = { +struct str2init str2cts[] = { { "application", CT_APPLICATION, InitApplication }, { "audio", CT_AUDIO, InitGeneric }, { "image", CT_IMAGE, InitGeneric }, @@ -176,7 +148,7 @@ static struct str2init str2cts[] = { { NULL, CT_UNKNOWN, NULL }, }; -static struct str2init str2ces[] = { +struct str2init str2ces[] = { { "base64", CE_BASE64, InitBase64 }, { "quoted-printable", CE_QUOTED, InitQuoted }, { "8bit", CE_8BIT, Init7Bit }, @@ -191,7 +163,7 @@ static struct str2init str2ces[] = { * * si_key is 1 if access method is anonymous. */ -static struct str2init str2methods[] = { +struct str2init str2methods[] = { { "afs", 1, InitFile }, { "anon-ftp", 1, InitFTP }, { "ftp", 0, InitFTP }, @@ -429,7 +401,7 @@ get_content (FILE *in, char *file, int toplevel) } /* Parse the Content-Type field */ - if (get_ctinfo (hp->value, ct) == NOTOK) + if (get_ctinfo (hp->value, ct, 0) == NOTOK) goto out; /* @@ -534,6 +506,10 @@ get_content (FILE *in, char *file, int toplevel) /* Get Content-Description field */ ct->c_descr = add (hp->value, ct->c_descr); } + else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) { + /* Get Content-Disposition field */ + ct->c_dispo = add (hp->value, ct->c_dispo); + } next_header: hp = hp->next; /* next header field */ @@ -550,7 +526,7 @@ next_header: * so default type is message/rfc822 */ if (toplevel < 0) { - if (get_ctinfo ("message/rfc822", ct) == NOTOK) + if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) goto out; ct->c_type = CT_MESSAGE; ct->c_ctinitfnx = InitMessage; @@ -558,7 +534,7 @@ next_header: /* * Else default type is text/plain */ - if (get_ctinfo ("text/plain", ct) == NOTOK) + if (get_ctinfo ("text/plain", ct, 0) == NOTOK) goto out; ct->c_type = CT_TEXT; ct->c_ctinitfnx = InitText; @@ -583,7 +559,7 @@ out: * small routine to add header field to list */ -static int +int add_header (CT ct, char *name, char *value) { HF hp; @@ -609,13 +585,95 @@ add_header (CT ct, char *name, char *value) } +/* Make sure that buf contains at least one appearance of name, + followed by =. If not, insert both name and value, just after + first semicolon, if any. Note that name should not contain a + trailing =. And quotes will be added around the value. Typical + usage: make sure that a Content-Disposition header contains + filename="foo". If it doesn't and value does, use value from + that. */ +static char * +incl_name_value (unsigned char *buf, char *name, char *value) { + char *newbuf = buf; + + /* Assume that name is non-null. */ + if (buf && value) { + char *name_plus_equal = concat (name, "=", NULL); + + if (! strstr (buf, name_plus_equal)) { + char *insertion; + unsigned char *cp; + char *prefix, *suffix; + + /* Trim trailing space, esp. newline. */ + for (cp = &buf[strlen (buf) - 1]; + cp >= buf && isspace (*cp); + --cp) { + *cp = '\0'; + } + + insertion = concat ("; ", name, "=", "\"", value, "\"", NULL); + + /* Insert at first semicolon, if any. If none, append to + end. */ + prefix = add (buf, NULL); + if ((cp = strchr (prefix, ';'))) { + suffix = concat (cp, NULL); + *cp = '\0'; + newbuf = concat (prefix, insertion, suffix, "\n", NULL); + free (suffix); + } else { + /* Append to end. */ + newbuf = concat (buf, insertion, "\n", NULL); + } + + free (prefix); + free (insertion); + free (buf); + } + + free (name_plus_equal); + } + + return newbuf; +} + +/* Extract just name_suffix="foo", if any, from value. If there isn't + one, return the entire value. Note that, for example, a name_suffix + of name will match filename="foo", and return foo. */ +static char * +extract_name_value (char *name_suffix, char *value) { + char *extracted_name_value = value; + char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL); + char *name_suffix_equals = strstr (value, name_suffix_plus_quote); + char *cp; + + free (name_suffix_plus_quote); + if (name_suffix_equals) { + char *name_suffix_begin; + + /* Find first \". */ + for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */; + name_suffix_begin = ++cp; + /* Find second \". */ + for (; *cp != '"'; ++cp) /* empty */; + + extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1); + memcpy (extracted_name_value, + name_suffix_begin, + cp - name_suffix_begin); + extracted_name_value[cp - name_suffix_begin] = '\0'; + } + + return extracted_name_value; +} + /* - * Parse Content-Type line and fill in the - * information of the CTinfo structure. + * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition + * directives. Fills in the information of the CTinfo structure. */ - -static int -get_ctinfo (unsigned char *cp, CT ct) +int +get_ctinfo (unsigned char *cp, CT ct, int magic) { int i; unsigned char *dp; @@ -672,7 +730,8 @@ get_ctinfo (unsigned char *cp, CT ct) return NOTOK; if (*cp != '/') { - ci->ci_subtype = add ("", NULL); + if (!magic) + ci->ci_subtype = add ("", NULL); goto magic_skip; } @@ -806,11 +865,108 @@ bad_quote: } /* + * Get any given in buffer + */ + if (magic && *cp == '<') { + if (ct->c_id) { + free (ct->c_id); + ct->c_id = NULL; + } + if (!(dp = strchr(ct->c_id = ++cp, '>'))) { + advise (NULL, "invalid ID in message %s", ct->c_file); + return NOTOK; + } + c = *dp; + *dp = '\0'; + if (*ct->c_id) + ct->c_id = concat ("<", ct->c_id, ">\n", NULL); + else + ct->c_id = NULL; + *dp++ = c; + cp = dp; + + while (isspace (*cp)) + cp++; + } + + /* + * Get any [Content-Description] given in buffer. + */ + if (magic && *cp == '[') { + ct->c_descr = ++cp; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (*dp == ']') + break; + if (dp < cp) { + advise (NULL, "invalid description in message %s", ct->c_file); + ct->c_descr = NULL; + return NOTOK; + } + + c = *dp; + *dp = '\0'; + if (*ct->c_descr) + ct->c_descr = concat (ct->c_descr, "\n", NULL); + else + ct->c_descr = NULL; + *dp++ = c; + cp = dp; + + while (isspace (*cp)) + cp++; + } + + /* + * Get any {Content-Disposition} given in buffer. + */ + if (magic && *cp == '{') { + ct->c_dispo = ++cp; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (*dp == '}') + break; + if (dp < cp) { + advise (NULL, "invalid disposition in message %s", ct->c_file); + ct->c_dispo = NULL; + return NOTOK; + } + + c = *dp; + *dp = '\0'; + if (*ct->c_dispo) + ct->c_dispo = concat (ct->c_dispo, "\n", NULL); + else + ct->c_dispo = NULL; + *dp++ = c; + cp = dp; + + while (isspace (*cp)) + cp++; + } + + /* * Check if anything is left over */ if (*cp) { - advise (NULL, "extraneous information in message %s's %s: field\n%*.*s(%s)", - ct->c_file, TYPE_FIELD, i, i, "", cp); + if (magic) { + ci->ci_magic = add (cp, NULL); + + /* If there is a Content-Disposition header and it doesn't + have a *filename=, extract it from the magic contents. + The r1bindex call skips any leading directory + components. */ + if (ct->c_dispo) + ct->c_dispo = + incl_name_value (ct->c_dispo, + "filename", + r1bindex (extract_name_value ("name", + ci-> + ci_magic), + '/')); + } + else + advise (NULL, + "extraneous information in message %s's %s: field\n%*.*s(%s)", + ct->c_file, TYPE_FIELD, i, i, "", cp); } return OK; @@ -918,7 +1074,7 @@ InitText (CT ct) break; ct->c_subtype = kv->kv_value; - /* allocate text structure */ + /* allocate text character set structure */ if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) adios (NULL, "out of memory"); ct->c_ctparams = (void *) t; @@ -928,23 +1084,25 @@ InitText (CT ct) if (!mh_strcasecmp (*ap, "charset")) break; - if (*ap) - chset = *ep; - else - chset = "US-ASCII"; /* default for text */ - - /* match character set, or set to unknown */ - for (kv = Charset; kv->kv_key; kv++) - if (!mh_strcasecmp (chset, kv->kv_key)) - break; - t->tx_charset = kv->kv_value; + /* check if content specified a character set */ + if (*ap) { + /* match character set or set to CHARSET_UNKNOWN */ + for (kv = Charset; kv->kv_key; kv++) + if (!mh_strcasecmp (*ep, kv->kv_key)) + break; + t->tx_charset = kv->kv_value; + } else { + t->tx_charset = CHARSET_UNSPECIFIED; + } /* * If we can not handle character set natively, * then check profile for string to modify the * terminal or display method. + * + * termproc is for mhshow, though mhlist -debug prints it, too. */ - if (!check_charset (chset, strlen (chset))) { + if (chset != NULL && !check_charset (chset, strlen (chset))) { snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset); if ((cp = context_find (buffer))) ct->c_termproc = getcpy (cp); @@ -1364,7 +1522,7 @@ no_body: } -static int +int params_external (CT ct, int composing) { char **ap, **ep; @@ -1484,7 +1642,7 @@ init_encoding (CT ct, OpenCEFunc openfnx) } -static void +void close_encoding (CT ct) { CE ce; @@ -1578,7 +1736,7 @@ openBase64 (CT ct, char **file) unsigned char value, *b, *b1, *b2, *b3; unsigned char *cp, *ep; char buffer[BUFSIZ]; - /* sbeck -- handle prefixes */ + /* sbeck -- handle suffixes */ CI ci; CE ce; MD5_CTX mdContext; @@ -1792,7 +1950,7 @@ openQuoted (CT ct, char **file) char buffer[BUFSIZ]; unsigned char mask; CE ce; - /* sbeck -- handle prefixes */ + /* sbeck -- handle suffixes */ CI ci; MD5_CTX mdContext; @@ -1991,12 +2149,12 @@ Init7Bit (CT ct) } -static int +int open7Bit (CT ct, char **file) { int cc, fd, len; char buffer[BUFSIZ]; - /* sbeck -- handle prefixes */ + /* sbeck -- handle suffixes */ char *cp; CI ci; CE ce; @@ -2083,6 +2241,8 @@ open7Bit (CT ct, char **file) fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id); if (ct->c_descr) fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr); + if (ct->c_dispo) + fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo); fprintf (ce->ce_fp, "\n"); } @@ -2596,6 +2756,8 @@ openMail (CT ct, char **file) return NOTOK; } + /* showproc is for mhshow and mhstore, though mhlist -debug + * prints it, too. */ if (ct->c_showproc) free (ct->c_showproc); ct->c_showproc = add ("true", NULL); diff --git a/uip/mhshow.c b/uip/mhshow.c index 179173e..2b406d4 100644 --- a/uip/mhshow.c +++ b/uip/mhshow.c @@ -82,7 +82,6 @@ static struct swit switches[] = { /* mhparse.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ diff --git a/uip/mhstore.c b/uip/mhstore.c index 91c57a2..bef9122 100644 --- a/uip/mhstore.c +++ b/uip/mhstore.c @@ -64,7 +64,6 @@ static struct swit switches[] = { /* mhparse.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ diff --git a/uip/mhtest.c b/uip/mhtest.c index d8c025f..9590fd3 100644 --- a/uip/mhtest.c +++ b/uip/mhtest.c @@ -64,7 +64,6 @@ static struct swit switches[] = { int ebcdicsw = 0; /* hack for linking purposes */ /* mhparse.c */ -extern int checksw; extern char *tmp; /* directory to place temp files */ /* mhcachesbr.c */ -- 1.7.10.4