X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/fc8a48c33835906623cb10ee129fc70a72271e02..c72aa476d3eec3bede50fdee7e0bd30ef69f1f4b:/uip/mhparse.c?ds=sidebyside diff --git a/uip/mhparse.c b/uip/mhparse.c index c9574efd..9873e0b1 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -11,8 +11,6 @@ #include #include #include -#include -#include #include #include #include @@ -31,16 +29,14 @@ extern int wcachesw; /* mhcachesbr.c */ int checksw = 0; /* check Content-MD5 field */ /* - * Directory to place temp files. This must - * be set before these routines are called. - */ -char *tmp; - -/* - * Instruct parser not to detect invalid Content-Transfer-Encoding - * in a multipart. + * 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. */ int skip_mp_cte_check; +int suppress_bogus_mp_content_warning; +int bogus_mp_content; /* * Structures for TEXT messages @@ -84,6 +80,19 @@ struct k2v SubApplication[] = { { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ }; +/* + * Mapping of names of CTE types in mhbuild directives + */ +static struct k2v EncodingType[] = { + { "8bit", CE_8BIT }, + { "qp", CE_QUOTED }, + { "q-p", CE_QUOTED }, + { "quoted-printable", CE_QUOTED }, + { "b64", CE_BASE64 }, + { "base64", CE_BASE64 }, + { NULL, 0 }, +}; + /* mhcachesbr.c */ int find_cache (CT, int, int *, char *, char *, int); @@ -94,19 +103,18 @@ int type_ok (CT, int); void content_error (char *, CT, char *, ...); /* mhfree.c */ -void free_content (CT); void free_encoding (CT, int); /* * static prototypes */ static CT get_content (FILE *, char *, int); -static int get_comment (CT, char **, int); +static int get_comment (const char *, CI, char **, int); static int InitGeneric (CT); static int InitText (CT); static int InitMultiPart (CT); -static void reverse_parts (CT); +void reverse_parts (CT); static int InitMessage (CT); static int InitApplication (CT); static int init_encoding (CT, OpenCEFunc); @@ -124,6 +132,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); +static int InitURL (CT); +static int openURL (CT, char **); struct str2init str2cts[] = { { "application", CT_APPLICATION, InitApplication }, @@ -158,6 +169,7 @@ struct str2init str2methods[] = { { "ftp", 0, InitFTP }, { "local-file", 0, InitFile }, { "mail-server", 0, InitMail }, + { "url", 0, InitURL }, { NULL, 0, NULL } }; @@ -195,23 +207,23 @@ parse_mime (char *file) if ((is_stdin = !(strcmp (file, "-")))) { char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp); if (tfile == NULL) { - advise("mhparse", "unable to create temporary file"); + advise("mhparse", "unable to create temporary file in %s", + get_temp_dir()); return NULL; } file = add (tfile, NULL); - chmod (file, 0600); while (fgets (buffer, sizeof(buffer), stdin)) fputs (buffer, fp); fflush (fp); if (ferror (stdin)) { - unlink (file); + (void) m_unlink (file); advise ("stdin", "error reading"); return NULL; } if (ferror (fp)) { - unlink (file); + (void) m_unlink (file); advise (file, "error writing"); return NULL; } @@ -223,7 +235,7 @@ parse_mime (char *file) if (!(ct = get_content (fp, file, 1))) { if (is_stdin) - unlink (file); + (void) m_unlink (file); advise (NULL, "unable to decode %s", file); return NULL; } @@ -339,7 +351,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 (!mh_strcasecmp (hp->name, VRSN_FIELD)) { + if (!strcasecmp (hp->name, VRSN_FIELD)) { int ucmp; char c, *cp, *dp; @@ -364,21 +376,22 @@ get_content (FILE *in, char *file, int toplevel) if (debugsw) fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) + if (*cp == '(' && + get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) goto out; for (dp = cp; istoken (*dp); dp++) continue; c = *dp; *dp = '\0'; - ucmp = !mh_strcasecmp (cp, VRSN_VALUE); + ucmp = !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 (!mh_strcasecmp (hp->name, TYPE_FIELD)) { + else if (!strcasecmp (hp->name, TYPE_FIELD)) { /* Get Content-Type field */ struct str2init *s2i; CI ci = &ct->c_ctinfo; @@ -399,14 +412,14 @@ get_content (FILE *in, char *file, int toplevel) * flag for this content type. */ for (s2i = str2cts; s2i->si_key; s2i++) - if (!mh_strcasecmp (ci->ci_type, s2i->si_key)) + if (!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 (!mh_strcasecmp (hp->name, ENCODING_FIELD)) { + else if (!strcasecmp (hp->name, ENCODING_FIELD)) { /* Get Content-Transfer-Encoding field */ char c, *cp, *dp; struct str2init *s2i; @@ -436,7 +449,7 @@ get_content (FILE *in, char *file, int toplevel) * for this transfer encoding. */ for (s2i = str2ces; s2i->si_key; s2i++) - if (!mh_strcasecmp (cp, s2i->si_key)) + if (!strcasecmp (cp, s2i->si_key)) break; if (!s2i->si_key && !uprf (cp, "X-")) s2i++; @@ -447,7 +460,7 @@ get_content (FILE *in, char *file, int toplevel) if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) goto out; } - else if (!mh_strcasecmp (hp->name, MD5_FIELD)) { + else if (!strcasecmp (hp->name, MD5_FIELD)) { /* Get Content-MD5 field */ char *cp, *dp, *ep; @@ -473,7 +486,8 @@ get_content (FILE *in, char *file, int toplevel) if (debugsw) fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { + if (*cp == '(' && + get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) { free (ep); goto out; } @@ -486,15 +500,15 @@ get_content (FILE *in, char *file, int toplevel) free (ep); ct->c_digested++; } - else if (!mh_strcasecmp (hp->name, ID_FIELD)) { + else if (!strcasecmp (hp->name, ID_FIELD)) { /* Get Content-ID field */ ct->c_id = add (hp->value, ct->c_id); } - else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) { + else if (!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)) { + else if (!strcasecmp (hp->name, DISPO_FIELD)) { /* Get Content-Disposition field */ ct->c_dispo = add (hp->value, ct->c_dispo); } @@ -663,9 +677,10 @@ int get_ctinfo (char *cp, CT ct, int magic) { int i; - char *dp, **ap, **ep; + char *dp; char c; CI ci; + int status; ci = &ct->c_ctinfo; i = strlen (invo_name) + 2; @@ -689,7 +704,7 @@ get_ctinfo (char *cp, CT ct, int magic) if (debugsw) fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -712,7 +727,7 @@ get_ctinfo (char *cp, CT ct, int magic) while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; if (*cp != '/') { @@ -725,7 +740,7 @@ get_ctinfo (char *cp, CT ct, int magic) while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -750,103 +765,11 @@ magic_skip: while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; - /* - * Parse attribute/value pairs given with Content-Type - */ - ep = (ap = ci->ci_attrs) + NPARMS; - while (*cp == ';') { - char *vp, *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 ((unsigned char) *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 ((unsigned char) *dp); dp++) - if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) - *dp = tolower ((unsigned char) *dp); - - for (up = dp; isspace ((unsigned char) *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 ((unsigned char) *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 ((unsigned char) *cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; + if (parse_header_attrs (ct->c_file, i, &cp, ci, &status) == NOTOK) { + return status; } /* @@ -928,6 +851,47 @@ bad_quote: cp++; } + /* + * Get any extension directives (right now just the content transfer + * encoding, but maybe others) that we care about. + */ + + if (magic && *cp == '*') { + /* + * See if it's a CTE we match on + */ + struct k2v *kv; + + dp = ++cp; + while (*cp != '\0' && ! isspace((unsigned char) *cp)) + cp++; + + if (dp == cp) { + advise (NULL, "invalid null transfer encoding specification"); + return NOTOK; + } + + if (*cp != '\0') + *cp++ = '\0'; + + ct->c_reqencoding = CE_UNKNOWN; + + for (kv = EncodingType; kv->kv_key; kv++) { + if (strcasecmp(kv->kv_key, dp) == 0) { + ct->c_reqencoding = kv->kv_value; + break; + } + } + + if (ct->c_reqencoding == CE_UNKNOWN) { + advise (NULL, "invalid CTE specification: \"%s\"", dp); + return NOTOK; + } + + while (isspace ((unsigned char) *cp)) + cp++; + } + /* * Check if anything is left over */ @@ -959,14 +923,12 @@ bad_quote: static int -get_comment (CT ct, char **ap, int istype) +get_comment (const char *filename, CI ci, char **ap, int istype) { int i; char *bp, *cp; char c, buffer[BUFSIZ], *dp; - CI ci; - ci = &ct->c_ctinfo; cp = *ap; bp = buffer; cp++; @@ -976,7 +938,7 @@ get_comment (CT ct, char **ap, int istype) case '\0': invalid: advise (NULL, "invalid comment in message %s's %s: field", - ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); + filename, istype ? TYPE_FIELD : VRSN_FIELD); return NOTOK; case '\\': @@ -1056,7 +1018,7 @@ InitText (CT ct) /* match subtype */ for (kv = SubText; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1067,7 +1029,7 @@ InitText (CT ct) /* scan for charset parameter */ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) - if (!mh_strcasecmp (*ap, "charset")) + if (!strcasecmp (*ap, "charset")) break; /* check if content specified a character set */ @@ -1140,7 +1102,7 @@ InitMultiPart (CT ct) /* match subtype */ for (kv = SubMultiPart; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1150,7 +1112,7 @@ InitMultiPart (CT ct) */ bp = 0; for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "boundary")) { + if (!strcasecmp (*ap, "boundary")) { bp = *ep; break; } @@ -1243,7 +1205,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; @@ -1295,6 +1261,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; @@ -1302,48 +1271,23 @@ last_part: /* - * reverse the order of the parts of a multipart + * reverse the order of the parts of a multipart/alternative */ -static void +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; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + struct part *next; - /* 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; + /* Reverse the order of its parts by walking the mp_parts list + and pushing each node to the front. */ + for (part = m->mp_parts, m->mp_parts = NULL; part; part = next) { + next = part->mp_next; + part->mp_next = m->mp_parts; + m->mp_parts = part; } - *next = NULL; - - /* free array of pointers */ - free ((char *) base); } @@ -1370,7 +1314,7 @@ InitMessage (CT ct) /* match subtype */ for (kv = SubMessage; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1389,11 +1333,11 @@ InitMessage (CT ct) /* scan for parameters "id", "number", and "total" */ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "id")) { + if (!strcasecmp (*ap, "id")) { p->pm_partid = add (*ep, NULL); continue; } - if (!mh_strcasecmp (*ap, "number")) { + if (!strcasecmp (*ap, "number")) { if (sscanf (*ep, "%d", &p->pm_partno) != 1 || p->pm_partno < 1) { invalid_param: @@ -1405,7 +1349,7 @@ invalid_param: } continue; } - if (!mh_strcasecmp (*ap, "total")) { + if (!strcasecmp (*ap, "total")) { if (sscanf (*ep, "%d", &p->pm_maxno) != 1 || p->pm_maxno < 1) goto invalid_param; @@ -1452,6 +1396,7 @@ invalid_param: e->eb_parent = ct; e->eb_content = p; p->c_ctexbody = e; + p->c_ceopenfnx = NULL; if ((exresult = params_external (ct, 0)) != NOTOK && p->c_ceopenfnx == openMail) { int cc, size; @@ -1524,13 +1469,14 @@ params_external (CT ct, int composing) struct exbody *e = (struct exbody *) ct->c_ctparams; CI ci = &ct->c_ctinfo; + ct->c_ceopenfnx = NULL; for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "access-type")) { + if (!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)) + if (!strcasecmp (*ep, s2i->si_key)) break; if (!s2i->si_key) { e->eb_access = *ep; @@ -1547,39 +1493,56 @@ params_external (CT ct, int composing) return NOTOK; continue; } - if (!mh_strcasecmp (*ap, "name")) { + if (!strcasecmp (*ap, "name")) { e->eb_name = *ep; continue; } - if (!mh_strcasecmp (*ap, "permission")) { + if (!strcasecmp (*ap, "permission")) { e->eb_permission = *ep; continue; } - if (!mh_strcasecmp (*ap, "site")) { + if (!strcasecmp (*ap, "site")) { e->eb_site = *ep; continue; } - if (!mh_strcasecmp (*ap, "directory")) { + if (!strcasecmp (*ap, "directory")) { e->eb_dir = *ep; continue; } - if (!mh_strcasecmp (*ap, "mode")) { + if (!strcasecmp (*ap, "mode")) { e->eb_mode = *ep; continue; } - if (!mh_strcasecmp (*ap, "size")) { + if (!strcasecmp (*ap, "size")) { sscanf (*ep, "%lu", &e->eb_size); continue; } - if (!mh_strcasecmp (*ap, "server")) { + if (!strcasecmp (*ap, "server")) { e->eb_server = *ep; continue; } - if (!mh_strcasecmp (*ap, "subject")) { + if (!strcasecmp (*ap, "subject")) { e->eb_subject = *ep; continue; } - if (composing && !mh_strcasecmp (*ap, "body")) { + if (!strcasecmp (*ap, "url")) { + /* + * According to RFC 2017, we have to remove all whitespace from + * the URL + */ + + char *u, *p = *ep; + e->eb_url = u = mh_xmalloc(strlen(*ep) + 1); + + for (; *p != '\0'; p++) { + if (! isspace((unsigned char) *p)) + *u++ = *p; + } + + *u = '\0'; + continue; + } + if (composing && !strcasecmp (*ap, "body")) { e->eb_body = getcpy (*ep); continue; } @@ -1608,7 +1571,7 @@ InitApplication (CT ct) /* match subtype */ for (kv = SubApplication; kv->kv_key; kv++) - if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key)) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) break; ct->c_subtype = kv->kv_value; @@ -1623,12 +1586,6 @@ InitApplication (CT ct) 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; @@ -1640,10 +1597,7 @@ init_encoding (CT ct, OpenCEFunc openfnx) void close_encoding (CT ct) { - CE ce; - - if (!(ce = ct->c_cefile)) - return; + CE ce = &ct->c_cefile; if (ce->ce_fp) { fclose (ce->ce_fp); @@ -1658,12 +1612,9 @@ size_encoding (CT ct) int fd; unsigned long size; char *file; - CE ce; + CE ce = &ct->c_cefile; 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; @@ -1726,16 +1677,15 @@ static int openBase64 (CT ct, char **file) { int bitno, cc, digested; - int fd, len, skip, own_ct_fp = 0; + int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT; uint32_t bits; unsigned char value, b; char *cp, *ep, buffer[BUFSIZ]; /* sbeck -- handle suffixes */ CI ci; - CE ce; + CE ce = &ct->c_cefile; MD5_CTX mdContext; - ce = ct->c_cefile; if (ce->ce_fp) { fseek (ce->ce_fp, 0L, SEEK_SET); goto ready_to_go; @@ -1750,7 +1700,6 @@ openBase64 (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -1768,19 +1717,22 @@ openBase64 (CT ct, char **file) 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_unlink) { + /* Create temporary file with filename extension. */ + if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } else if (*file == NULL) { + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); } if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { @@ -1844,17 +1796,20 @@ openBase64 (CT ct, char **file) test_end: if ((bitno -= 6) < 0) { b = (bits >> 16) & 0xff; - putc ((char) b, ce->ce_fp); + if (!text || b != '\r') + putc ((char) b, ce->ce_fp); if (digested) MD5Update (&mdContext, &b, 1); if (skip < 2) { b = (bits >> 8) & 0xff; - putc ((char) b, ce->ce_fp); + if (! text || b != '\r') + putc ((char) b, ce->ce_fp); if (digested) MD5Update (&mdContext, &b, 1); if (skip < 1) { b = bits & 0xff; - putc ((char) b, ce->ce_fp); + if (! text || b != '\r') + putc ((char) b, ce->ce_fp); if (digested) MD5Update (&mdContext, &b, 1); } @@ -1965,12 +1920,11 @@ openQuoted (CT ct, char **file) char *cp, *ep; char buffer[BUFSIZ]; unsigned char mask; - CE ce; + CE ce = &ct->c_cefile; /* sbeck -- handle suffixes */ CI ci; MD5_CTX mdContext; - ce = ct->c_cefile; if (ce->ce_fp) { fseek (ce->ce_fp, 0L, SEEK_SET); goto ready_to_go; @@ -1985,7 +1939,6 @@ openQuoted (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -2003,19 +1956,22 @@ openQuoted (CT ct, char **file) 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_unlink) { + /* Create temporary file with filename extension. */ + if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } else if (*file == NULL) { + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); } if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { @@ -2193,9 +2149,8 @@ open7Bit (CT ct, char **file) /* sbeck -- handle suffixes */ char *cp; CI ci; - CE ce; + CE ce = &ct->c_cefile; - ce = ct->c_cefile; if (ce->ce_fp) { fseek (ce->ce_fp, 0L, SEEK_SET); goto ready_to_go; @@ -2210,7 +2165,6 @@ open7Bit (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -2228,19 +2182,22 @@ open7Bit (CT ct, char **file) 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_unlink) { + /* Create temporary file with filename extension. */ + if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + } else { + ce->ce_file = add (cp, ce->ce_file); + } + } else if (*file == NULL) { + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); } if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { @@ -2414,7 +2371,7 @@ openFile (CT ct, char **file) int fd, cachetype; char cachefile[BUFSIZ]; struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; + CE ce = &ct->c_cefile; switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { case NOTOK: @@ -2440,7 +2397,7 @@ openFile (CT ct, char **file) return NOTOK; } - if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write")) + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, cachefile, sizeof(cachefile)) != NOTOK) { int mask; @@ -2461,12 +2418,12 @@ openFile (CT ct, char **file) if (ferror (gp)) { admonish (ce->ce_file, "error reading"); - unlink (cachefile); + (void) m_unlink (cachefile); } else if (ferror (fp)) { admonish (cachefile, "error writing"); - unlink (cachefile); + (void) m_unlink (cachefile); } fclose (fp); } @@ -2497,12 +2454,11 @@ openFTP (CT ct, char **file) char *bp, *ftp, *user, *pass; char buffer[BUFSIZ], cachefile[BUFSIZ]; struct exbody *e; - CE ce; + CE ce = &ct->c_cefile; static char *username = NULL; static char *password = NULL; e = ct->c_ctexbody; - ce = ct->c_cefile; if ((ftp = context_find (nmhaccessftp)) && !*ftp) ftp = NULL; @@ -2587,7 +2543,7 @@ openFTP (CT ct, char **file) ce->ce_unlink = (*file == NULL); caching = 0; cachefile[0] = '\0'; - if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write")) + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, cachefile, sizeof(cachefile)) != NOTOK) { if (*file == NULL) { @@ -2600,8 +2556,14 @@ openFTP (CT ct, char **file) ce->ce_file = add (*file, NULL); else if (caching) ce->ce_file = add (cachefile, NULL); - else - ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); + else { + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); + } if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); @@ -2620,7 +2582,7 @@ 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 && !mh_strcasecmp (e->eb_mode, "ascii") + vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii") ? "ascii" : "binary"; vec[vecp] = NULL; @@ -2672,12 +2634,12 @@ openFTP (CT ct, char **file) if (ferror (gp)) { admonish (ce->ce_file, "error reading"); - unlink (cachefile); + (void) m_unlink (cachefile); } else if (ferror (fp)) { admonish (cachefile, "error writing"); - unlink (cachefile); + (void) m_unlink (cachefile); } fclose (fp); } @@ -2709,7 +2671,7 @@ openMail (CT ct, char **file) int len, buflen; char *bp, buffer[BUFSIZ], *vec[7]; struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; + CE ce = &ct->c_cefile; switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { case NOTOK: @@ -2789,7 +2751,12 @@ openMail (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL); + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -2813,6 +2780,147 @@ openMail (CT ct, char **file) } +/* + * URL + */ + +static int +InitURL (CT ct) +{ + return init_encoding (ct, openURL); +} + + +static int +openURL (CT ct, char **file) +{ + struct exbody *e = ct->c_ctexbody; + CE ce = &ct->c_cefile; + char *urlprog, *program; + char buffer[BUFSIZ], cachefile[BUFSIZ]; + int fd, caching, cachetype; + struct msgs_array args = { 0, 0, NULL}; + pid_t child_id; + + if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0') + urlprog = NULL; + + if (! urlprog) { + content_error(NULL, ct, "No entry for nmh-access-url in profile"); + return NOTOK; + } + + 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_url) { + content_error(NULL, ct, "missing url parameter"); + return NOTOK; + } + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + ce->ce_unlink = (*file == NULL); + caching = 0; + cachefile[0] = '\0'; + + if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + if (*file == NULL) { + ce->ce_unlink = 0; + caching = 1; + } + } + + if (*file) + ce->ce_file = add(*file, NULL); + else if (caching) + ce->ce_file = add(cachefile, NULL); + else { + char *tempfile; + if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ce->ce_file = add (tempfile, NULL); + } + + if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) { + content_error(ce->ce_file, ct, "unable to fopen for read/writing"); + return NOTOK; + } + + switch (child_id = fork()) { + case NOTOK: + adios ("fork", "unable to"); + /* NOTREACHED */ + + case OK: + argsplit_msgarg(&args, urlprog, &program); + app_msgarg(&args, e->eb_url); + app_msgarg(&args, NULL); + dup2(fileno(ce->ce_fp), 1); + close(fileno(ce->ce_fp)); + execvp(program, args.msgs); + fprintf(stderr, "Unable to exec "); + perror(program); + _exit(-1); + /* NOTREACHED */ + + default: + if (pidXwait(child_id, NULL)) { + ce->ce_unlink = 1; + return NOTOK; + } + } + + if (cachefile[0]) { + if (caching) + chmod(cachefile, cachetype ? m_gmprot() : 0444); + else { + int mask; + FILE *fp; + + mask = umask (cachetype ? ~m_gmprot() : 0222); + if ((fp = fopen(cachefile, "w"))) { + int cc; + FILE *gp = ce->ce_fp; + + fseeko(gp, 0, 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"); + (void) m_unlink (cachefile); + } + } + umask(mask); + } + } + + fseeko(ce->ce_fp, 0, SEEK_SET); + *file = ce->ce_file; + return fd; +} + static int readDigest (CT ct, char *cp) { @@ -2887,3 +2995,420 @@ 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; +} + + +char * +ct_type_str (int type) { + switch (type) { + case CT_APPLICATION: + return "application"; + case CT_AUDIO: + return "audio"; + case CT_IMAGE: + return "image"; + case CT_MESSAGE: + return "message"; + case CT_MULTIPART: + return "multipart"; + case CT_TEXT: + return "text"; + case CT_VIDEO: + return "video"; + case CT_EXTENSION: + return "extension"; + default: + return "unknown_type"; + } +} + + +char * +ct_subtype_str (int type, int subtype) { + switch (type) { + case CT_APPLICATION: + switch (subtype) { + case APPLICATION_OCTETS: + return "octets"; + case APPLICATION_POSTSCRIPT: + return "postscript"; + default: + return "unknown_app_subtype"; + } + case CT_MESSAGE: + switch (subtype) { + case MESSAGE_RFC822: + return "rfc822"; + case MESSAGE_PARTIAL: + return "partial"; + case MESSAGE_EXTERNAL: + return "external"; + default: + return "unknown_msg_subtype"; + } + case CT_MULTIPART: + switch (subtype) { + case MULTI_MIXED: + return "mixed"; + case MULTI_ALTERNATE: + return "alternative"; + case MULTI_DIGEST: + return "digest"; + case MULTI_PARALLEL: + return "parallel"; + default: + return "unknown_multipart_subtype"; + } + case CT_TEXT: + switch (subtype) { + case TEXT_PLAIN: + return "plain"; + case TEXT_RICHTEXT: + return "richtext"; + case TEXT_ENRICHED: + return "enriched"; + default: + return "unknown_text_subtype"; + } + default: + return "unknown_type"; + } +} + + +/* Find the content type and InitFunc for the CT. */ +const struct str2init * +get_ct_init (int type) { + const struct str2init *sp; + + for (sp = str2cts; sp->si_key; ++sp) { + if (type == sp->si_val) { + return sp; + } + } + + return NULL; +} + +const char * +ce_str (int encoding) { + switch (encoding) { + case CE_BASE64: + return "base64"; + case CE_QUOTED: + return "quoted-printable"; + case CE_8BIT: + return "8bit"; + case CE_7BIT: + return "7bit"; + case CE_BINARY: + return "binary"; + case CE_EXTENSION: + return "extension"; + case CE_EXTERNAL: + return "external"; + default: + return "unknown"; + } +} + +/* Find the content type and InitFunc for the content encoding method. */ +const struct str2init * +get_ce_method (const char *method) { + struct str2init *sp; + + for (sp = str2ces; sp->si_key; ++sp) { + if (! strcasecmp (method, sp->si_key)) { + return sp; + } + } + + return NULL; +} + +int +parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci, + int *status) { + char **attr = ci->ci_attrs; + char *cp = *header_attrp; + + while (*cp == ';') { + char *dp, *vp, *up, c; + + /* Relies on knowledge of this declaration: + * char *ci_attrs[NPARMS + 2]; + */ + if (attr >= ci->ci_attrs + sizeof ci->ci_attrs/sizeof (char *) - 2) { + advise (NULL, + "too many parameters in message %s's %s: field (%d max)", + filename, TYPE_FIELD, NPARMS); + *status = NOTOK; + return NOTOK; + } + + cp++; + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, ci, &cp, 1) == NOTOK) { + *status = NOTOK; + return NOTOK; + } + + if (*cp == 0) { + advise (NULL, + "extraneous trailing ';' in message %s's %s: " + "parameter list", + filename, TYPE_FIELD); + *status = OK; + return NOTOK; + } + + /* down case the attribute name */ + 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 ((unsigned char) *dp);) + dp++; + if (dp == cp || *dp != '=') { + advise (NULL, + "invalid parameter in message %s's %s: " + "field\n%*.*sparameter %s (error detected at offset %d)", + filename, TYPE_FIELD, len, len, "", cp, dp - cp); + *status = NOTOK; + return NOTOK; + } + + vp = (*attr = add (cp, NULL)) + (up - cp); + *vp = '\0'; + for (dp++; isspace ((unsigned char) *dp);) + dp++; + + /* Now store the attribute value. */ + ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (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)", + filename, TYPE_FIELD, len, len, "", *attr); + *status = NOTOK; + 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)", + filename, TYPE_FIELD, len, len, "", *attr); + *status = NOTOK; + return NOTOK; + } + + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, ci, &cp, 1) == NOTOK) { + *status = NOTOK; + return NOTOK; + } + + ++attr; + } + + *header_attrp = cp; + return OK; +} + + +char * +content_charset (CT ct) { + const char *const charset = "charset"; + char *default_charset = NULL; + CI ctinfo = &ct->c_ctinfo; + char **ap, **vp; + char **src_charset = NULL; + + for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) { + if (! strcasecmp (*ap, charset)) { + src_charset = vp; + break; + } + } + + /* RFC 2045, Sec. 5.2: default to us-ascii. */ + if (src_charset == NULL) src_charset = &default_charset; + if (*src_charset == NULL) *src_charset = "US-ASCII"; + + return *src_charset; +} + + +/* Change the value of a name=value pair in a header field body. + If the name isn't there, append them. In any case, a new + string will be allocated and must be free'd by the caller. + Trims any trailing newlines. */ +char * +update_attr (char *body, const char *name, const char *value) { + char *bp = nmh_strcasestr (body, name); + char *new_body; + + if (bp) { + char *other_attrs = strchr (bp, ';'); + + *(bp + strlen (name)) = '\0'; + new_body = concat (body, "\"", value, "\"", NULL); + + if (other_attrs) { + char *cp; + + /* Trim any trailing newlines. */ + for (cp = &other_attrs[strlen (other_attrs) - 1]; + cp > other_attrs && *cp == '\n'; + *cp-- = '\0') continue; + new_body = add (other_attrs, new_body); + } + } else { + char *cp; + + /* Append name/value pair, after first removing a final newline + and (extraneous) semicolon. */ + if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0'; + if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0'; + new_body = concat (body, "; ", name, "\"", value, "\"", NULL); + } + + return new_body; +}