X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/1691e80890e5d8ba258c51c214a3e91880e1db2b..1316c8d619a6de50544da7c8435d2f6d6045da54:/uip/mhparse.c diff --git a/uip/mhparse.c b/uip/mhparse.c index 1839a15d..69d2f2c8 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -2,7 +2,9 @@ /* * mhparse.c -- routines to parse the contents of MIME messages * - * $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. */ #include @@ -10,23 +12,16 @@ #include #include #include -#include #include -#include -#include +#include +#include #include #include - -#ifdef HAVE_SYS_WAIT_H -# include -#endif +#include -extern int errno; extern int debugsw; -extern int endian; /* mhmisc.c */ - extern pid_t xpid; /* mhshowsbr.c */ /* cache policies */ @@ -42,33 +37,31 @@ int checksw = 0; /* check Content-MD5 field */ char *tmp; /* - * Structure for mapping types to their internal flags + * These are for mhfixmsg to: + * 1) Instruct parser not to detect invalid Content-Transfer-Encoding + * in a multipart. + * 2) Suppress the warning about bogus multipart content, and report it. */ -struct k2v { - char *kv_key; - int kv_value; -}; +int skip_mp_cte_check; +int suppress_bogus_mp_content_warning; +int bogus_mp_content; /* * 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[] = { - { "us-ascii", CHARSET_USASCII }, - { "iso-8859-1", CHARSET_LATIN }, - { NULL, CHARSET_UNKNOWN } /* this one must be last! */ -}; +/* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */ /* * Structures for MULTIPART messages */ -static struct k2v SubMultiPart[] = { +struct k2v SubMultiPart[] = { { "mixed", MULTI_MIXED }, { "alternative", MULTI_ALTERNATE }, { "digest", MULTI_DIGEST }, @@ -79,7 +72,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 }, @@ -89,58 +82,43 @@ 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! */ }; -/* ftpsbr.c */ -int ftp_get (char *, char *, char *, char *, char *, char *, int, int); - /* mhcachesbr.c */ int find_cache (CT, int, int *, char *, char *, int); /* mhmisc.c */ int part_ok (CT, int); int type_ok (CT, int); -int make_intermediates (char *); void content_error (char *, CT, char *, ...); /* mhfree.c */ -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 (char *, CT); static int get_comment (CT, 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 **); @@ -149,18 +127,9 @@ static int openFTP (CT, char **); static int InitMail (CT); static int openMail (CT, char **); static int readDigest (CT, char *); +static int get_leftover_mp_content (CT, int); -/* - * 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 }, @@ -172,12 +141,12 @@ 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 }, { "7bit", CE_7BIT, Init7Bit }, - { "binary", CE_BINARY, NULL }, + { "binary", CE_BINARY, Init7Bit }, { NULL, CE_EXTENSION, NULL }, /* these two must be last! */ { NULL, CE_UNKNOWN, NULL }, }; @@ -187,7 +156,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 }, @@ -206,7 +175,7 @@ pidcheck (int status) fflush (stdout); fflush (stderr); done (1); - /* NOTREACHED */ + return 1; } @@ -228,12 +197,14 @@ parse_mime (char *file) * Check if file is actually standard input */ if ((is_stdin = !(strcmp (file, "-")))) { - file = add (m_tmpfil (invo_name), NULL); - if ((fp = fopen (file, "w+")) == NULL) { - advise (file, "unable to fopen for writing and reading"); - return NULL; - } + char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp); + if (tfile == NULL) { + advise("mhparse", "unable to create temporary file"); + return NULL; + } + file = add (tfile, NULL); chmod (file, 0600); + while (fgets (buffer, sizeof(buffer), stdin)) fputs (buffer, fp); fflush (fp); @@ -257,7 +228,6 @@ parse_mime (char *file) if (!(ct = get_content (fp, file, 1))) { if (is_stdin) unlink (file); - fclose (fp); advise (NULL, "unable to decode %s", file); return NULL; } @@ -291,6 +261,7 @@ parse_mime (char *file) * toplevel = 0 # we are inside message type or multipart type * # other than multipart/digest * toplevel = -1 # we are inside multipart/digest + * NB: on failure we will fclose(in)! */ static CT @@ -301,6 +272,7 @@ get_content (FILE *in, char *file, int toplevel) char *np, *vp; CT ct; HF hp; + m_getfld_state_t gstate = 0; /* allocate the content structure */ if (!(ct = (CT) calloc (1, sizeof(*ct)))) @@ -314,11 +286,12 @@ get_content (FILE *in, char *file, int toplevel) * Parse the header fields for this * content into a linked list. */ - for (compnum = 1, state = FLD;;) { - switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + m_getfld_track_filepos (&gstate, in); + for (compnum = 1;;) { + int bufsz = sizeof buf; + switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) { case FLD: case FLDPLUS: - case FLDEOF: compnum++; /* get copies of the buffers */ @@ -327,22 +300,19 @@ get_content (FILE *in, char *file, int toplevel) /* if necessary, get rest of field */ while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); + bufsz = sizeof buf; + state = m_getfld (&gstate, name, buf, &bufsz, in); vp = add (buf, vp); /* add to previous value */ } /* Now add the header data to the list */ add_header (ct, np, vp); - /* continue, if this isn't the last header field */ - if (state != FLDEOF) { - ct->c_begin = ftell (in) + 1; - continue; - } - /* else fall... */ + /* continue, to see if this isn't the last header field */ + ct->c_begin = ftell (in) + 1; + continue; case BODY: - case BODYEOF: ct->c_begin = ftell (in) - strlen (buf); break; @@ -361,6 +331,7 @@ get_content (FILE *in, char *file, int toplevel) /* break out of the loop */ break; } + m_getfld_state_destroy (&gstate); /* * Read the content headers. We will parse the @@ -372,7 +343,7 @@ get_content (FILE *in, char *file, int toplevel) hp = ct->c_first_hf; /* start at first header field */ while (hp) { /* Get MIME-Version field */ - if (!strcasecmp (hp->name, VRSN_FIELD)) { + if (!mh_strcasecmp (hp->name, VRSN_FIELD)) { int ucmp; char c, *cp, *dp; @@ -386,12 +357,12 @@ get_content (FILE *in, char *file, int toplevel) /* Now, cleanup this field */ cp = ct->c_vrsn; - while (isspace (*cp)) + while (isspace ((unsigned char) *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)) + if (!isspace ((unsigned char) *dp)) break; *++dp = '\0'; if (debugsw) @@ -404,14 +375,14 @@ get_content (FILE *in, char *file, int toplevel) continue; c = *dp; *dp = '\0'; - ucmp = !strcasecmp (cp, VRSN_VALUE); + 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); } } - else if (!strcasecmp (hp->name, TYPE_FIELD)) { + else if (!mh_strcasecmp (hp->name, TYPE_FIELD)) { /* Get Content-Type field */ struct str2init *s2i; CI ci = &ct->c_ctinfo; @@ -424,7 +395,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; /* @@ -432,14 +403,14 @@ get_content (FILE *in, char *file, int toplevel) * flag for this content type. */ for (s2i = str2cts; s2i->si_key; s2i++) - if (!strcasecmp (ci->ci_type, s2i->si_key)) + 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; } - else if (!strcasecmp (hp->name, ENCODING_FIELD)) { + else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) { /* Get Content-Transfer-Encoding field */ char c, *cp, *dp; struct str2init *s2i; @@ -457,7 +428,7 @@ get_content (FILE *in, char *file, int toplevel) /* get copy of this field */ ct->c_celine = cp = add (hp->value, NULL); - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; for (dp = cp; istoken (*dp); dp++) continue; @@ -469,7 +440,7 @@ get_content (FILE *in, char *file, int toplevel) * for this transfer encoding. */ for (s2i = str2ces; s2i->si_key; s2i++) - if (!strcasecmp (cp, s2i->si_key)) + if (!mh_strcasecmp (cp, s2i->si_key)) break; if (!s2i->si_key && !uprf (cp, "X-")) s2i++; @@ -480,7 +451,7 @@ get_content (FILE *in, char *file, int toplevel) if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) goto out; } - else if (!strcasecmp (hp->name, MD5_FIELD)) { + else if (!mh_strcasecmp (hp->name, MD5_FIELD)) { /* Get Content-MD5 field */ char *cp, *dp, *ep; @@ -495,12 +466,12 @@ get_content (FILE *in, char *file, int toplevel) ep = cp = add (hp->value, NULL); /* get a copy */ - while (isspace (*cp)) + while (isspace ((unsigned char) *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)) + if (!isspace ((unsigned char) *dp)) break; *++dp = '\0'; if (debugsw) @@ -511,7 +482,7 @@ get_content (FILE *in, char *file, int toplevel) goto out; } - for (dp = cp; *dp && !isspace (*dp); dp++) + for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++) continue; *dp = '\0'; @@ -519,14 +490,18 @@ get_content (FILE *in, char *file, int toplevel) free (ep); ct->c_digested++; } - else if (!strcasecmp (hp->name, ID_FIELD)) { + else if (!mh_strcasecmp (hp->name, ID_FIELD)) { /* Get Content-ID field */ ct->c_id = add (hp->value, ct->c_id); } - else if (!strcasecmp (hp->name, DESCR_FIELD)) { + else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) { /* 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 */ @@ -543,7 +518,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; @@ -551,7 +526,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; @@ -576,14 +551,13 @@ out: * small routine to add header field to list */ -static int +int add_header (CT ct, char *name, char *value) { HF hp; /* allocate header field structure */ - if (!(hp = malloc (sizeof(*hp)))) - adios (NULL, "out of memory"); + hp = mh_xmalloc (sizeof(*hp)); /* link data into header structure */ hp->name = name; @@ -603,13 +577,94 @@ 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 (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; + char *cp, *prefix, *suffix; + + /* Trim trailing space, esp. newline. */ + for (cp = &buf[strlen (buf) - 1]; + cp >= buf && isspace ((unsigned char) *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 (char *cp, CT ct) +int +get_ctinfo (char *cp, CT ct, int magic) { int i; char *dp, **ap, **ep; @@ -622,7 +677,7 @@ get_ctinfo (char *cp, CT ct) /* store copy of Content-Type line */ cp = ct->c_ctline = add (cp, NULL); - while (isspace (*cp)) /* trim leading spaces */ + while (isspace ((unsigned char) *cp)) /* trim leading spaces */ cp++; /* change newlines to spaces */ @@ -631,7 +686,7 @@ get_ctinfo (char *cp, CT ct) /* trim trailing spaces */ for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) + if (!isspace ((unsigned char) *dp)) break; *++dp = '\0'; @@ -655,22 +710,23 @@ get_ctinfo (char *cp, CT ct) /* down case the content type string */ for (dp = ci->ci_type; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); + if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) + *dp = tolower ((unsigned char) *dp); - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) return NOTOK; if (*cp != '/') { - ci->ci_subtype = add ("", NULL); + if (!magic) + ci->ci_subtype = add ("", NULL); goto magic_skip; } cp++; - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) @@ -691,11 +747,11 @@ get_ctinfo (char *cp, CT ct) /* down case the content subtype string */ for (dp = ci->ci_subtype; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); + if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) + *dp = tolower ((unsigned char) *dp); magic_skip: - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) @@ -716,7 +772,7 @@ magic_skip: } cp++; - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) @@ -730,11 +786,11 @@ magic_skip: } /* down case the attribute name */ - for (dp = cp; istoken (*dp); dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); + for (dp = cp; istoken ((unsigned char) *dp); dp++) + if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) + *dp = tolower ((unsigned char) *dp); - for (up = dp; isspace (*dp);) + for (up = dp; isspace ((unsigned char) *dp);) dp++; if (dp == cp || *dp != '=') { advise (NULL, @@ -745,7 +801,7 @@ magic_skip: vp = (*ap = add (cp, NULL)) + (up - cp); *vp = '\0'; - for (dp++; isspace (*dp);) + for (dp++; isspace ((unsigned char) *dp);) dp++; /* now add the attribute value */ @@ -790,19 +846,116 @@ bad_quote: } ap++; - while (isspace (*cp)) + while (isspace ((unsigned char) *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 ((unsigned char) *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 ((unsigned char) *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 ((unsigned char) *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; @@ -863,7 +1016,7 @@ invalid: } } - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; *ap = cp; @@ -881,6 +1034,8 @@ invalid: static int InitGeneric (CT ct) { + NMH_UNUSED (ct); + return OK; /* not much to do here */ } @@ -893,7 +1048,7 @@ static int InitText (CT ct) { char buffer[BUFSIZ]; - char *chset; + char *chset = NULL; char **ap, **ep, *cp; struct k2v *kv; struct text *t; @@ -905,37 +1060,36 @@ InitText (CT ct) /* match subtype */ for (kv = SubText; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) 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; /* scan for charset parameter */ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) - if (!strcasecmp (*ap, "charset")) + if (!mh_strcasecmp (*ap, "charset")) break; - if (*ap) + /* check if content specified a character set */ + 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 (!strcasecmp (chset, kv->kv_key)) - break; - t->tx_charset = kv->kv_value; + t->tx_charset = CHARSET_SPECIFIED; + } 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); @@ -967,17 +1121,30 @@ InitMultiPart (CT ct) * 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) { + if (! skip_mp_cte_check && ct->c_encoding != CE_7BIT && + ct->c_encoding != CE_8BIT && ct->c_encoding != CE_BINARY) { + /* Copy the Content-Transfer-Encoding header field body so we can + remove any trailing whitespace and leading blanks from it. */ + char *cte = add (ct->c_celine ? ct->c_celine : "(null)", NULL); + + bp = cte + strlen (cte) - 1; + while (bp >= cte && isspace ((unsigned char) *bp)) *bp-- = '\0'; + for (bp = cte; *bp && isblank ((unsigned char) *bp); ++bp) continue; + 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); + "\"%s/%s\" type in message %s must be encoded in\n" + "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround " + "is to\nmanually edit the file and change the \"%s\"\n" + "Content-Transfer-Encoding to one of those. For now", + ci->ci_type, ci->ci_subtype, ct->c_file, bp); + free (cte); + return NOTOK; } /* match subtype */ for (kv = SubMultiPart; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -985,8 +1152,9 @@ InitMultiPart (CT ct) * Check for "boundary" parameter, which is * required for multipart messages. */ + bp = 0; for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "boundary")) { + if (!mh_strcasecmp (*ap, "boundary")) { bp = *ep; break; } @@ -1006,7 +1174,7 @@ InitMultiPart (CT ct) ct->c_ctparams = (void *) m; /* check if boundary parameter contains only whitespace characters */ - for (cp = bp; isspace (*cp); cp++) + for (cp = bp; isspace ((unsigned char) *cp); cp++) continue; if (!*cp) { advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field", @@ -1016,7 +1184,7 @@ InitMultiPart (CT ct) /* remove trailing whitespace from boundary parameter */ for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--) - if (!isspace (*dp)) + if (!isspace ((unsigned char) *dp)) break; *++dp = '\0'; @@ -1053,7 +1221,6 @@ next_part: if (!(p = get_content (fp, ct->c_file, ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { - fclose (ct->c_fp); ct->c_fp = NULL; return NOTOK; } @@ -1080,7 +1247,11 @@ end_part: } } - advise (NULL, "bogus multipart content in message %s", ct->c_file); + if (! suppress_bogus_mp_content_warning) { + advise (NULL, "bogus multipart content in message %s", ct->c_file); + } + bogus_mp_content = 1; + if (!inout && part) { p = part->mp_part; p->c_end = ct->c_end; @@ -1110,7 +1281,7 @@ last_part: char partnam[BUFSIZ]; if (ct->c_partno) { - snprintf (partnam, sizeof(partnum), "%s.", ct->c_partno); + snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno); pp = partnam + strlen (partnam); } else { pp = partnam; @@ -1132,6 +1303,9 @@ last_part: } } + get_leftover_mp_content (ct, 1); + get_leftover_mp_content (ct, 0); + fclose (ct->c_fp); ct->c_fp = NULL; return OK; @@ -1194,9 +1368,9 @@ InitMessage (CT ct) struct k2v *kv; CI ci = &ct->c_ctinfo; - if (ct->c_encoding != CE_7BIT) { + if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) { admonish (NULL, - "\"%s/%s\" type in message %s should be encoded in 7bit", + "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit", ci->ci_type, ci->ci_subtype, ct->c_file); return NOTOK; } @@ -1207,7 +1381,7 @@ InitMessage (CT ct) /* match subtype */ for (kv = SubMessage; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1226,11 +1400,11 @@ InitMessage (CT ct) /* scan for parameters "id", "number", and "total" */ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "id")) { + if (!mh_strcasecmp (*ap, "id")) { p->pm_partid = add (*ep, NULL); continue; } - if (!strcasecmp (*ap, "number")) { + if (!mh_strcasecmp (*ap, "number")) { if (sscanf (*ep, "%d", &p->pm_partno) != 1 || p->pm_partno < 1) { invalid_param: @@ -1242,7 +1416,7 @@ invalid_param: } continue; } - if (!strcasecmp (*ap, "total")) { + if (!mh_strcasecmp (*ap, "total")) { if (sscanf (*ep, "%d", &p->pm_maxno) != 1 || p->pm_maxno < 1) goto invalid_param; @@ -1282,7 +1456,6 @@ invalid_param: 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; } @@ -1302,8 +1475,7 @@ invalid_param: goto no_body; } - if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL) - adios (NULL, "out of memory"); + 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)) { @@ -1356,7 +1528,7 @@ no_body: } -static int +int params_external (CT ct, int composing) { char **ap, **ep; @@ -1364,12 +1536,12 @@ params_external (CT ct, int composing) CI ci = &ct->c_ctinfo; for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "access-type")) { + if (!mh_strcasecmp (*ap, "access-type")) { struct str2init *s2i; CT p = e->eb_content; for (s2i = str2methods; s2i->si_key; s2i++) - if (!strcasecmp (*ep, s2i->si_key)) + if (!mh_strcasecmp (*ep, s2i->si_key)) break; if (!s2i->si_key) { e->eb_access = *ep; @@ -1386,39 +1558,39 @@ params_external (CT ct, int composing) return NOTOK; continue; } - if (!strcasecmp (*ap, "name")) { + if (!mh_strcasecmp (*ap, "name")) { e->eb_name = *ep; continue; } - if (!strcasecmp (*ap, "permission")) { + if (!mh_strcasecmp (*ap, "permission")) { e->eb_permission = *ep; continue; } - if (!strcasecmp (*ap, "site")) { + if (!mh_strcasecmp (*ap, "site")) { e->eb_site = *ep; continue; } - if (!strcasecmp (*ap, "directory")) { + if (!mh_strcasecmp (*ap, "directory")) { e->eb_dir = *ep; continue; } - if (!strcasecmp (*ap, "mode")) { + if (!mh_strcasecmp (*ap, "mode")) { e->eb_mode = *ep; continue; } - if (!strcasecmp (*ap, "size")) { + if (!mh_strcasecmp (*ap, "size")) { sscanf (*ep, "%lu", &e->eb_size); continue; } - if (!strcasecmp (*ap, "server")) { + if (!mh_strcasecmp (*ap, "server")) { e->eb_server = *ep; continue; } - if (!strcasecmp (*ap, "subject")) { + if (!mh_strcasecmp (*ap, "subject")) { e->eb_subject = *ep; continue; } - if (composing && !strcasecmp (*ap, "body")) { + if (composing && !mh_strcasecmp (*ap, "body")) { e->eb_body = getcpy (*ep); continue; } @@ -1447,7 +1619,7 @@ InitApplication (CT ct) /* match subtype */ for (kv = SubApplication; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1476,7 +1648,7 @@ init_encoding (CT ct, OpenCEFunc openfnx) } -static void +void close_encoding (CT ct) { CE ce; @@ -1565,18 +1737,15 @@ 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; + int fd, len, skip, own_ct_fp = 0; + uint32_t bits; + unsigned char value, b; char *cp, *ep, buffer[BUFSIZ]; + /* sbeck -- handle suffixes */ + CI ci; 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); @@ -1592,13 +1761,39 @@ openBase64 (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); ce->ce_unlink = 0; } + /* sbeck@cise.ufl.edu -- handle suffixes */ + ci = &ct->c_ctinfo; + snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + cp = context_find (buffer); + if (cp == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name, + ci->ci_type); + cp = context_find (buffer); + } + if (cp != NULL && *cp != '\0') { + if (ce->ce_unlink) { + /* Temporary file already exists, so we rename to + version with extension. */ + char *file_org = strdup(ce->ce_file); + ce->ce_file = add (cp, ce->ce_file); + if (rename(file_org, ce->ce_file)) { + adios (ce->ce_file, "unable to rename %s to ", file_org); + } + free(file_org); + + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); return NOTOK; @@ -1607,9 +1802,12 @@ openBase64 (CT ct, char **file) 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 (! ct->c_fp) { + if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + own_ct_fp = 1; } if ((digested = ct->c_digested)) @@ -1638,13 +1836,13 @@ openBase64 (CT ct, char **file) for (ep = (cp = buffer) + cc; cp < ep; cp++) { switch (*cp) { default: - if (isspace (*cp)) + if (isspace ((unsigned char) *cp)) break; - if (skip || (*cp & 0x80) - || (value = b642nib[*cp & 0x7f]) > 0x3f) { + if (skip || (((unsigned char) *cp) & 0x80) + || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) { if (debugsw) { fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", - *cp, + (unsigned char) *cp, (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)), skip); } @@ -1656,17 +1854,20 @@ openBase64 (CT ct, char **file) bits |= value << bitno; test_end: if ((bitno -= 6) < 0) { - putc ((char) *b1, ce->ce_fp); + b = (bits >> 16) & 0xff; + putc ((char) b, ce->ce_fp); if (digested) - MD5Update (&mdContext, b1, 1); + MD5Update (&mdContext, &b, 1); if (skip < 2) { - putc ((char) *b2, ce->ce_fp); + b = (bits >> 8) & 0xff; + putc ((char) b, ce->ce_fp); if (digested) - MD5Update (&mdContext, b2, 1); + MD5Update (&mdContext, &b, 1); if (skip < 1) { - putc ((char) *b3, ce->ce_fp); + b = bits & 0xff; + putc ((char) b, ce->ce_fp); if (digested) - MD5Update (&mdContext, b3, 1); + MD5Update (&mdContext, &b, 1); } } @@ -1721,9 +1922,17 @@ self_delimiting: ready_to_go: *file = ce->ce_file; + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } return fileno (ce->ce_fp); clean_up: + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } free_encoding (ct, 0); return NOTOK; } @@ -1763,11 +1972,13 @@ InitQuoted (CT ct) static int openQuoted (CT ct, char **file) { - int cc, digested, len, quoted; + int cc, digested, len, quoted, own_ct_fp = 0; char *cp, *ep; char buffer[BUFSIZ]; unsigned char mask; CE ce; + /* sbeck -- handle suffixes */ + CI ci; MD5_CTX mdContext; ce = ct->c_cefile; @@ -1785,13 +1996,39 @@ openQuoted (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); ce->ce_unlink = 0; } + /* sbeck@cise.ufl.edu -- handle suffixes */ + ci = &ct->c_ctinfo; + snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + cp = context_find (buffer); + if (cp == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name, + ci->ci_type); + cp = context_find (buffer); + } + if (cp != NULL && *cp != '\0') { + if (ce->ce_unlink) { + /* Temporary file already exists, so we rename to + version with extension. */ + char *file_org = strdup(ce->ce_file); + ce->ce_file = add (cp, ce->ce_file); + if (rename(file_org, ce->ce_file)) { + adios (ce->ce_file, "unable to rename %s to ", file_org); + } + free(file_org); + + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); return NOTOK; @@ -1800,9 +2037,12 @@ openQuoted (CT ct, char **file) 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 (! ct->c_fp) { + if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + own_ct_fp = 1; } if ((digested = ct->c_digested)) @@ -1815,8 +2055,6 @@ openQuoted (CT ct, char **file) 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; @@ -1827,84 +2065,73 @@ openQuoted (CT ct, char **file) len -= cc; for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) - if (!isspace (*ep)) + if (!isspace ((unsigned char) *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; - } + if (quoted > 0) { + /* in an escape sequence */ + if (quoted == 1) { + /* at byte 1 of an escape sequence */ + mask = hex2nib[((unsigned char) *cp) & 0x7f]; + /* next is byte 2 */ + quoted = 2; + } else { + /* at byte 2 of an escape sequence */ mask <<= 4; - mask |= hex2nib[*cp & 0x7f]; + mask |= hex2nib[((unsigned char) *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; } + /* finished escape sequence; next may be literal or a new + * escape sequence */ + quoted = 0; } - - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - quoted = 0; + /* on to next byte */ 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); + /* not in an escape sequence */ + if (*cp == '=') { + /* starting an escape sequence, or invalid '='? */ + if (cp + 1 < ep && cp[1] == '\n') { + /* "=\n" soft line break, eat the \n */ + cp++; + continue; } - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; + if (cp + 1 >= ep || cp + 2 >= ep) { + /* We don't have 2 bytes left, so this is an invalid + * escape sequence; just show the raw bytes (below). */ + } else if (isxdigit ((unsigned char) cp[1]) && + isxdigit ((unsigned char) cp[2])) { + /* Next 2 bytes are hex digits, making this a valid escape + * sequence; let's decode it (above). */ + quoted = 1; + continue; + } else { + /* One or both of the next 2 is out of range, making this + * an invalid escape sequence; just show the raw bytes + * (below). */ } - break; + } - case '=': - if (*++cp != '\n') { - quoted = 1; - cp--; + /* Just show the raw byte. */ + putc (*cp, ce->ce_fp); + if (digested) { + if (*cp == '\n') { + MD5Update (&mdContext, (unsigned char *) "\r\n",2); + } else { + MD5Update (&mdContext, (unsigned char *) cp, 1); } - break; + } + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; } } } @@ -1938,10 +2165,18 @@ invalid_encoding: ready_to_go: *file = ce->ce_file; + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } return fileno (ce->ce_fp); clean_up: free_encoding (ct, 0); + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } return NOTOK; } @@ -1961,11 +2196,14 @@ Init7Bit (CT ct) } -static int +int open7Bit (CT ct, char **file) { - int cc, fd, len; + int cc, fd, len, own_ct_fp = 0; char buffer[BUFSIZ]; + /* sbeck -- handle suffixes */ + char *cp; + CI ci; CE ce; ce = ct->c_cefile; @@ -1983,13 +2221,39 @@ open7Bit (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); ce->ce_unlink = 0; } + /* sbeck@cise.ufl.edu -- handle suffixes */ + ci = &ct->c_ctinfo; + snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + cp = context_find (buffer); + if (cp == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name, + ci->ci_type); + cp = context_find (buffer); + } + if (cp != NULL && *cp != '\0') { + if (ce->ce_unlink) { + /* Temporary file already exists, so we rename to + version with extension. */ + char *file_org = strdup(ce->ce_file); + ce->ce_file = add (cp, ce->ce_file); + if (rename(file_org, ce->ce_file)) { + adios (ce->ce_file, "unable to rename %s to ", file_org); + } + free(file_org); + + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); return NOTOK; @@ -2037,15 +2301,20 @@ 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"); } 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; + if (! ct->c_fp) { + if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + own_ct_fp = 1; } lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); @@ -2082,10 +2351,18 @@ open7Bit (CT ct, char **file) ready_to_go: *file = ce->ce_file; + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } return fileno (ce->ce_fp); clean_up: free_encoding (ct, 0); + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } return NOTOK; } @@ -2174,7 +2451,7 @@ openFile (CT ct, char **file) return NOTOK; } - if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + 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; @@ -2241,10 +2518,8 @@ openFTP (CT ct, char **file) if ((ftp = context_find (nmhaccessftp)) && !*ftp) ftp = NULL; -#ifndef BUILTIN_FTP if (!ftp) return NOTOK; -#endif switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { case NOTOK: @@ -2311,7 +2586,8 @@ openFTP (CT ct, char **file) if (e->eb_flags) { user = "anonymous"; - snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ()); + snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), + LocalName (1)); pass = buffer; } else { ruserpass (e->eb_site, &username, &password); @@ -2322,7 +2598,7 @@ openFTP (CT ct, char **file) ce->ce_unlink = (*file == NULL); caching = 0; cachefile[0] = '\0'; - if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + 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) { @@ -2336,16 +2612,13 @@ openFTP (CT ct, char **file) else if (caching) ce->ce_file = add (cachefile, NULL); else - ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); return NOTOK; } -#ifdef BUILTIN_FTP - if (ftp) -#endif { int child_id, i, vecp; char *vec[9]; @@ -2358,13 +2631,13 @@ openFTP (CT ct, char **file) vec[vecp++] = e->eb_dir; vec[vecp++] = e->eb_name; vec[vecp++] = ce->ce_file, - vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii") + 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++) + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) sleep (5); switch (child_id) { case NOTOK: @@ -2381,9 +2654,6 @@ openFTP (CT ct, char **file) default: if (pidXwait (child_id, NULL)) { -#ifdef BUILTIN_FTP -losing_ftp: -#endif username = password = NULL; ce->ce_unlink = 1; return NOTOK; @@ -2391,16 +2661,8 @@ losing_ftp: 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 && !strcasecmp (e->eb_mode, "ascii"), 0) - == NOTOK) - goto losing_ftp; -#endif - if (cachefile[0]) + if (cachefile[0]) { if (caching) chmod (cachefile, cachetype ? m_gmprot () : 0444); else { @@ -2414,7 +2676,7 @@ losing_ftp: fseek (gp, 0L, SEEK_SET); - while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) > 0) fwrite (buffer, sizeof(*buffer), cc, fp); fflush (fp); @@ -2432,6 +2694,7 @@ losing_ftp: } umask (mask); } + } fseek (ce->ce_fp, 0L, SEEK_SET); *file = ce->ce_file; @@ -2516,7 +2779,7 @@ openMail (CT ct, char **file) vec[vecp++] = e->eb_body; vec[vecp] = NULL; - for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) sleep (5); switch (child_id) { case NOTOK: @@ -2537,7 +2800,7 @@ openMail (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -2549,6 +2812,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); @@ -2563,15 +2828,10 @@ static int readDigest (CT ct, char *cp) { int bitno, skip; - unsigned long bits; + uint32_t 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; @@ -2593,11 +2853,11 @@ test_end: if ((bitno -= 6) < 0) { if (dp + (3 - skip) > ep) goto invalid_digest; - *dp++ = *b1; + *dp++ = (bits >> 16) & 0xff; if (skip < 2) { - *dp++ = *b2; + *dp++ = (bits >> 8) & 0xff; if (skip < 1) - *dp++ = *b3; + *dp++ = bits & 0xff; } bitno = 18; bits = 0L; @@ -2623,7 +2883,7 @@ invalid_digest: while (*cp) cp++; fprintf (stderr, "invalid MD5 digest (got %d octets)\n", - cp - bp); + (int)(cp - bp)); } return NOTOK; @@ -2638,3 +2898,109 @@ invalid_digest: return OK; } + + +/* Multipart parts might have content before the first subpart and/or + after the last subpart that hasn't been stored anywhere else, so do + that. */ +int +get_leftover_mp_content (CT ct, int before /* or after */) { + struct multipart *m = (struct multipart *) ct->c_ctparams; + char *boundary; + int found_boundary = 0; + char buffer[BUFSIZ]; + int max = BUFSIZ; + int read = 0; + char *content = NULL; + + if (! m) return NOTOK; + + if (before) { + if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK; + + /* Isolate the beginning of this part to the beginning of the + first subpart and save any content between them. */ + fseeko (ct->c_fp, ct->c_begin, SEEK_SET); + max = m->mp_parts->mp_part->c_begin - ct->c_begin; + boundary = concat ("--", m->mp_start, NULL); + } else { + struct part *last_subpart = NULL; + struct part *subpart; + + /* Go to the last subpart to get its end position. */ + for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) { + last_subpart = subpart; + } + + if (last_subpart == NULL) return NOTOK; + + /* Isolate the end of the last subpart to the end of this part + and save any content between them. */ + fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET); + max = ct->c_end - last_subpart->mp_part->c_end; + boundary = concat ("--", m->mp_stop, NULL); + } + + /* Back up by 1 to pick up the newline. */ + while (fgets (buffer, sizeof(buffer) - 1, ct->c_fp)) { + read += strlen (buffer); + /* Don't look beyond beginning of first subpart (before) or + next part (after). */ + if (read > max) buffer[read-max] = '\0'; + + if (before) { + if (! strcmp (buffer, boundary)) { + found_boundary = 1; + } + } else { + if (! found_boundary && ! strcmp (buffer, boundary)) { + found_boundary = 1; + continue; + } + } + + if ((before && ! found_boundary) || (! before && found_boundary)) { + if (content) { + char *old_content = content; + content = concat (content, buffer, NULL); + free (old_content); + } else { + content = before + ? concat ("\n", buffer, NULL) + : concat (buffer, NULL); + } + } + + if (before) { + if (found_boundary || read > max) break; + } else { + if (read > max) break; + } + } + + /* Skip the newline if that's all there is. */ + if (content) { + char *cp; + + /* Remove trailing newline, except at EOF. */ + if ((before || ! feof (ct->c_fp)) && + (cp = content + strlen (content)) > content && + *--cp == '\n') { + *cp = '\0'; + } + + if (strlen (content) > 1) { + if (before) { + m->mp_content_before = content; + } else { + m->mp_content_after = content; + } + } else { + free (content); + } + } + + free (boundary); + + return OK; +}