X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/81a21a9a97d8633f6d6231e31fdb6e328d0d3ff2..7711f3fc00259e55f630cfe6104eff3083dc9d77:/uip/mhparse.c?ds=sidebyside diff --git a/uip/mhparse.c b/uip/mhparse.c index fc24f5e1..00fa61bd 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -2,8 +2,6 @@ /* * 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. @@ -13,26 +11,18 @@ #include #include #include -#include -#include -#include #include #include #include #include #include - -#ifdef HAVE_SYS_WAIT_H -# include -#endif +#ifdef HAVE_ICONV +# include +#endif /* HAVE_ICONV */ extern int debugsw; -extern int endian; /* mhmisc.c */ - -extern pid_t xpid; /* mhshowsbr.c */ - /* cache policies */ extern int rcachesw; /* mhcachesbr.c */ extern int wcachesw; /* mhcachesbr.c */ @@ -40,50 +30,53 @@ 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. + * 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. + * 3) Suppress the warning about extraneous trailing ';' in header parameter + * lists, and report it. */ -char *tmp; +int skip_mp_cte_check; +int suppress_bogus_mp_content_warning; +int bogus_mp_content; +int suppress_extraneous_trailing_semicolon_warning; +int extraneous_trailing_semicolon; + +/* list of preferred type/subtype pairs, for -prefer */ +char *preferred_types[NPREFS], + *preferred_subtypes[NPREFS]; +int npreferred; -/* - * 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[] = { - { "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 }, { "parallel", MULTI_PARALLEL }, + { "related", MULTI_RELATED }, { NULL, MULTI_UNKNOWN } /* this one must be last! */ }; /* * Structures for MESSAGE messages */ -static struct k2v SubMessage[] = { +struct k2v SubMessage[] = { { "rfc822", MESSAGE_RFC822 }, { "partial", MESSAGE_PARTIAL }, { "external-body", MESSAGE_EXTERNAL }, @@ -93,58 +86,57 @@ 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! */ }; +/* + * 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 }, +}; -/* 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 part_ok (CT); 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 get_comment (const char *, const char *, char **, char **); + static int InitGeneric (CT); static int InitText (CT); static int InitMultiPart (CT); static void reverse_parts (CT); +static void prefer_parts(CT 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 **); @@ -153,18 +145,17 @@ static int openFTP (CT, char **); 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[] = { +static int get_leftover_mp_content (CT, int); +static int InitURL (CT); +static int openURL (CT, char **); +static int parse_header_attrs (const char *, const char *, char **, PM *, + PM *, char **); +static size_t param_len(PM, int, size_t, int *, int *, size_t *); +static size_t encode_param(PM, char *, size_t, size_t, size_t, int); +static size_t normal_param(PM, char *, size_t, size_t, size_t); +static int get_dispo (char *, CT, int); + +struct str2init str2cts[] = { { "application", CT_APPLICATION, InitApplication }, { "audio", CT_AUDIO, InitGeneric }, { "image", CT_IMAGE, InitGeneric }, @@ -176,7 +167,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,28 +182,17 @@ 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 }, { "local-file", 0, InitFile }, { "mail-server", 0, InitMail }, + { "url", 0, InitURL }, { NULL, 0, NULL } }; -int -pidcheck (int status) -{ - if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT) - return status; - - fflush (stdout); - fflush (stderr); - return done (1); -} - - /* * Main entry point for parsing a MIME message or file. * It returns the Content structure for the top level @@ -226,28 +206,36 @@ parse_mime (char *file) char buffer[BUFSIZ]; FILE *fp; CT ct; + size_t n; /* * 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 in %s", + get_temp_dir()); + return NULL; + } + file = add (tfile, NULL); + + while ((n = fread(buffer, 1, sizeof(buffer), stdin)) > 0) { + if (fwrite(buffer, 1, n, fp) != n) { + (void) m_unlink (file); + advise (file, "error copying to temporary file"); + return 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; } @@ -259,7 +247,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; } @@ -304,9 +292,10 @@ 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)))) + if (!(ct = (CT) mh_xcalloc (1, sizeof(*ct)))) adios (NULL, "out of memory"); ct->c_fp = in; @@ -317,11 +306,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 */ @@ -330,22 +320,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; @@ -364,6 +351,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 @@ -389,18 +377,19 @@ 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) fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) + if (*cp == '(' && + get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK) goto out; for (dp = cp; istoken (*dp); dp++) @@ -427,7 +416,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; /* @@ -460,7 +449,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; @@ -498,23 +487,24 @@ 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) fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { + if (*cp == '(' && + get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) { free (ep); goto out; } - for (dp = cp; *dp && !isspace (*dp); dp++) + for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++) continue; *dp = '\0'; @@ -530,6 +520,11 @@ get_content (FILE *in, char *file, int toplevel) /* Get Content-Description field */ ct->c_descr = add (hp->value, ct->c_descr); } + else if (!strcasecmp (hp->name, DISPO_FIELD)) { + /* Get Content-Disposition field */ + if (get_dispo(hp->value, ct, 0) == NOTOK) + goto out; + } next_header: hp = hp->next; /* next header field */ @@ -546,7 +541,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; @@ -554,7 +549,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; @@ -579,7 +574,7 @@ out: * small routine to add header field to list */ -static int +int add_header (CT ct, char *name, char *value) { HF hp; @@ -606,25 +601,23 @@ add_header (CT ct, char *name, char *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; + char *dp; char c; CI ci; + int status; 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 */ + while (isspace ((unsigned char) *cp)) /* trim leading spaces */ cp++; /* change newlines to spaces */ @@ -633,14 +626,15 @@ 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'; 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, TYPE_FIELD, &cp, + &ci->ci_comment) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -657,25 +651,28 @@ 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) + if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_comment) == 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) + if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_comment) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -693,133 +690,251 @@ 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) + if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_comment) == NOTOK) return NOTOK; + if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_first_pm, &ci->ci_last_pm, + &ci->ci_comment)) != OK) { + return status == NOTOK ? NOTOK : OK; + } + /* - * Parse attribute/value pairs given with Content-Type + * Get any given in buffer */ - 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); + 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; - cp++; - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; + } - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + /* + * 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; - - if (*cp == 0) { - advise (NULL, - "extraneous trailing ';' in message %s's %s: parameter list", - ct->c_file, TYPE_FIELD); - return OK; } + + 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; - /* down case the attribute name */ - for (dp = cp; istoken (*dp); dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); + while (isspace ((unsigned char) *cp)) + cp++; + } - 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); + /* + * Get any {Content-Disposition} given in buffer. + */ + if (magic && *cp == '{') { + ++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'; - vp = (*ap = add (cp, NULL)) + (up - cp); - *vp = '\0'; - for (dp++; isspace (*dp);) - dp++; + if (get_dispo(cp, ct, 1) != OK) + return NOTOK; - /* now add the attribute value */ - ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); + *dp++ = c; + cp = dp; - 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; + while (isspace ((unsigned char) *cp)) + cp++; + } - case '\\': - *dp++ = c; - if ((c = *cp++) == '\0') - goto bad_quote; - /* else fall... */ + /* + * Get any extension directives (right now just the content transfer + * encoding, but maybe others) that we care about. + */ - default: - *dp++ = c; - continue; + if (magic && *cp == '*') { + /* + * See if it's a CTE we match on + */ + struct k2v *kv; - case '"': - *dp = '\0'; - break; - } + 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; } - } 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); + + if (ct->c_reqencoding == CE_UNKNOWN) { + advise (NULL, "invalid CTE specification: \"%s\"", dp); return NOTOK; } - ap++; - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; } /* * 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_type && + !get_param(ct->c_dispo_first, "filename", '_', 1)) { + add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", + r1bindex(ci->ci_magic, '/'), 0); + } + } + else + advise (NULL, + "extraneous information in message %s's %s: field\n%*s(%s)", + ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp); + } + + return OK; +} + + +/* + * Parse out a Content-Disposition header. A lot of this is cribbed from + * get_ctinfo(). + */ +static int +get_dispo (char *cp, CT ct, int buildflag) +{ + char *dp, *dispoheader; + char c; + int status; + + /* + * Save the whole copy of the Content-Disposition header, unless we're + * processing a mhbuild directive. A NULL c_dispo will be a flag to + * mhbuild that the disposition header needs to be generated at that + * time. + */ + + dispoheader = cp = add(cp, NULL); + + while (isspace ((unsigned char) *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 ((unsigned char) *dp)) + break; + *++dp = '\0'; + + if (debugsw) + fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp); + + if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == + NOTOK) { + free(dispoheader); + return NOTOK; + } + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ct->c_dispo_type = add (cp, NULL); /* store disposition type */ + *dp = c, cp = dp; + + if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK) + return NOTOK; + + if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp, + &ct->c_dispo_first, &ct->c_dispo_last, + NULL)) != OK) { + if (status == NOTOK) { + free(dispoheader); + return NOTOK; + } + } else if (*cp) { + advise (NULL, + "extraneous information in message %s's %s: field\n%*s(%s)", + ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp); } + if (buildflag) + free(dispoheader); + else + ct->c_dispo = dispoheader; + return OK; } static int -get_comment (CT ct, char **ap, int istype) +get_comment (const char *filename, const char *fieldname, char **ap, + char **commentp) { int i; char *bp, *cp; char c, buffer[BUFSIZ], *dp; - CI ci; - ci = &ct->c_ctinfo; cp = *ap; bp = buffer; cp++; @@ -829,7 +944,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, fieldname); return NOTOK; case '\\': @@ -856,16 +971,16 @@ invalid: } *bp = '\0'; - if (istype) { - if ((dp = ci->ci_comment)) { - ci->ci_comment = concat (dp, " ", buffer, NULL); + if (commentp) { + if ((dp = *commentp)) { + *commentp = concat (dp, " ", buffer, NULL); free (dp); } else { - ci->ci_comment = add (buffer, NULL); + *commentp = add (buffer, NULL); } } - while (isspace (*cp)) + while (isspace ((unsigned char) *cp)) cp++; *ap = cp; @@ -883,6 +998,8 @@ invalid: static int InitGeneric (CT ct) { + NMH_UNUSED (ct); + return OK; /* not much to do here */ } @@ -895,9 +1012,9 @@ static int InitText (CT ct) { char buffer[BUFSIZ]; - char *chset; - char **ap, **ep, *cp; - struct k2v *kv; + char *chset = NULL; + char *cp; + PM pm; struct text *t; CI ci = &ct->c_ctinfo; @@ -906,38 +1023,34 @@ InitText (CT ct) ci->ci_subtype = add ("plain", ci->ci_subtype); /* match subtype */ - for (kv = SubText; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; + ct->c_subtype = ct_str_subtype (CT_TEXT, ci->ci_subtype); - /* allocate text structure */ - if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) + /* allocate text character set structure */ + if ((t = (struct text *) mh_xcalloc (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")) + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) + if (!strcasecmp (pm->pm_name, "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 (!strcasecmp (chset, kv->kv_key)) - break; - t->tx_charset = kv->kv_value; + /* check if content specified a character set */ + if (pm) { + chset = pm->pm_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); @@ -956,10 +1069,13 @@ InitMultiPart (CT ct) { int inout; long last, pos; - char *cp, *dp, **ap, **ep; - char *bp, buffer[BUFSIZ]; + char *cp, *dp; + PM pm; + char *bp; + char *bufp = NULL; + size_t buflen; + ssize_t gotlen; struct multipart *m; - struct k2v *kv; struct part *part, **next; CI ci = &ct->c_ctinfo; CT p; @@ -969,34 +1085,44 @@ 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)) - break; - ct->c_subtype = kv->kv_value; + ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype); /* * 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")) { - bp = *ep; + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + if (!strcasecmp (pm->pm_name, "boundary")) { + bp = pm->pm_value; break; } } /* complain if boundary parameter is missing */ - if (!*ap) { + if (!pm) { 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); @@ -1004,12 +1130,12 @@ InitMultiPart (CT ct) } /* allocate primary structure for multipart info */ - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + if ((m = (struct multipart *) mh_xcalloc (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++) + 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", @@ -1019,7 +1145,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'; @@ -1038,24 +1164,25 @@ InitMultiPart (CT ct) part = NULL; inout = 1; - while (fgets (buffer, sizeof(buffer) - 1, fp)) { + while ((gotlen = getline(&bufp, &buflen, fp)) != -1) { if (pos > last) break; - pos += strlen (buffer); - if (buffer[0] != '-' || buffer[1] != '-') + pos += gotlen; + if (bufp[0] != '-' || bufp[1] != '-') continue; if (inout) { - if (strcmp (buffer + 2, m->mp_start)) + if (strcmp (bufp + 2, m->mp_start)) continue; next_part: - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL) adios (NULL, "out of memory"); *next = part; next = &part->mp_next; if (!(p = get_content (fp, ct->c_file, ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { + free(bufp); ct->c_fp = NULL; return NOTOK; } @@ -1065,24 +1192,28 @@ next_part: fseek (fp, pos, SEEK_SET); inout = 0; } else { - if (strcmp (buffer + 2, m->mp_start) == 0) { + if (strcmp (bufp + 2, m->mp_start) == 0) { inout = 1; end_part: p = part->mp_part; - p->c_end = ftell(fp) - (strlen(buffer) + 1); + p->c_end = ftell(fp) - (gotlen + 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) + if (strcmp (bufp + 2, m->mp_stop) == 0) goto 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; @@ -1099,8 +1230,10 @@ end_part: last_part: /* reverse the order of the parts for multipart/alternative */ - if (ct->c_subtype == MULTI_ALTERNATE) + if (ct->c_subtype == MULTI_ALTERNATE) { reverse_parts (ct); + prefer_parts (ct); + } /* * label all subparts with part number, and @@ -1112,7 +1245,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; @@ -1127,6 +1260,7 @@ last_part: /* initialize the content of the subparts */ if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) { + free(bufp); fclose (ct->c_fp); ct->c_fp = NULL; return NOTOK; @@ -1134,6 +1268,10 @@ last_part: } } + get_leftover_mp_content (ct, 1); + get_leftover_mp_content (ct, 0); + + free(bufp); fclose (ct->c_fp); ct->c_fp = NULL; return OK; @@ -1141,48 +1279,107 @@ last_part: /* - * reverse the order of the parts of a multipart + * reverse the order of the parts of a multipart/alternative, + * presumably to put the "most favored" alternative first, for + * ease of choosing/displaying it later on. from a mail message on + * nmh-workers, from kenh: + * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I + * see code in mhn that did the same thing... Acccording to the RCS + * logs, that code was around from the initial checkin of mhn.c by + * John Romine in 1992, which is as far back as we have." */ - static void reverse_parts (CT ct) { - int i; - struct multipart *m; - struct part **base, **bmp, **next, *part; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + struct part *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; + } +} + +static void +move_preferred_part (CT ct, char *type, char *subtype) +{ + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part, *next, *prev, *head, *nhead, *ntail; + struct part h, n; + CI ci; + + /* move the matching part(s) to the head of the list: walk the + * list of parts, move matching parts to a new list (maintaining + * their order), and finally, concatenate the old list onto the + * new. + */ - m = (struct multipart *) ct->c_ctparams; + head = &h; + nhead = &n; + + head->mp_next = m->mp_parts; + nhead->mp_next = NULL; + ntail = nhead; + + prev = head; + part = head->mp_next; + while (part != NULL) { + ci = &part->mp_part->c_ctinfo; + if (!strcasecmp(ci->ci_type, type) && + (!subtype || !strcasecmp(ci->ci_subtype, subtype))) { + prev->mp_next = part->mp_next; + part->mp_next = NULL; + ntail->mp_next = part; + ntail = part; + part = prev->mp_next; + } else { + prev = part; + part = prev->mp_next; + } + } + ntail->mp_next = head->mp_next; + m->mp_parts = nhead->mp_next; + +} + +/* + * move parts that match the user's preferences (-prefer) to the head + * of the line. process preferences in reverse so first one given + * ends up first in line + */ +static void +prefer_parts(CT ct) +{ + int i; + for (i = npreferred-1; i >= 0; i--) + move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]); +} - /* 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; +/* parse_mime() arranges alternates in reverse (priority) order. This + function can be used to reverse them back. This will put, for + example, a text/plain part before a text/html part in a + multipart/alternative part, for example, where it belongs. */ +void +reverse_alternative_parts (CT ct) { + if (ct->c_type == CT_MULTIPART) { + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; - /* point at all the parts */ - for (part = m->mp_parts; part; part = part->mp_next) - *bmp++ = part; - *bmp = NULL; + if (ct->c_subtype == MULTI_ALTERNATE) { + reverse_parts (ct); + } - /* reverse the order of the parts */ - next = &m->mp_parts; - for (bmp--; bmp >= base; bmp--) { - part = *bmp; - *next = part; - next = &part->mp_next; + /* And call recursively on each part of a multipart. */ + for (part = m->mp_parts; part; part = part->mp_next) { + reverse_alternative_parts (part->mp_part); + } } - *next = NULL; - - /* free array of pointers */ - free ((char *) base); } @@ -1193,7 +1390,6 @@ reverse_parts (CT ct) static int InitMessage (CT ct) { - struct k2v *kv; CI ci = &ct->c_ctinfo; if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) { @@ -1208,10 +1404,7 @@ InitMessage (CT ct) ci->ci_subtype = add ("rfc822", ci->ci_subtype); /* match subtype */ - for (kv = SubMessage; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; + ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype); switch (ct->c_subtype) { case MESSAGE_RFC822: @@ -1219,33 +1412,33 @@ InitMessage (CT ct) case MESSAGE_PARTIAL: { - char **ap, **ep; + PM pm; struct partial *p; - if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) + if ((p = (struct partial *) mh_xcalloc (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 (!strcasecmp (*ap, "id")) { - p->pm_partid = add (*ep, NULL); + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + if (!strcasecmp (pm->pm_name, "id")) { + p->pm_partid = add (pm->pm_value, NULL); continue; } - if (!strcasecmp (*ap, "number")) { - if (sscanf (*ep, "%d", &p->pm_partno) != 1 + if (!strcasecmp (pm->pm_name, "number")) { + if (sscanf (pm->pm_value, "%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, + pm->pm_name, ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); return NOTOK; } continue; } - if (!strcasecmp (*ap, "total")) { - if (sscanf (*ep, "%d", &p->pm_maxno) != 1 + if (!strcasecmp (pm->pm_name, "total")) { + if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1 || p->pm_maxno < 1) goto invalid_param; continue; @@ -1271,7 +1464,7 @@ invalid_param: CT p; FILE *fp; - if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) + if ((e = (struct exbody *) mh_xcalloc (1, sizeof(*e))) == NULL) adios (NULL, "out of memory"); ct->c_ctparams = (void *) e; @@ -1291,6 +1484,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; @@ -1356,23 +1550,24 @@ no_body: } -static int +int params_external (CT ct, int composing) { - char **ap, **ep; + PM pm; 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 (!strcasecmp (*ap, "access-type")) { + ct->c_ceopenfnx = NULL; + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + if (!strcasecmp (pm->pm_name, "access-type")) { struct str2init *s2i; CT p = e->eb_content; for (s2i = str2methods; s2i->si_key; s2i++) - if (!strcasecmp (*ep, s2i->si_key)) + if (!strcasecmp (pm->pm_value, s2i->si_key)) break; if (!s2i->si_key) { - e->eb_access = *ep; + e->eb_access = pm->pm_value; e->eb_flags = NOTOK; p->c_encoding = CE_EXTERNAL; continue; @@ -1386,40 +1581,57 @@ params_external (CT ct, int composing) return NOTOK; continue; } - if (!strcasecmp (*ap, "name")) { - e->eb_name = *ep; + if (!strcasecmp (pm->pm_name, "name")) { + e->eb_name = pm->pm_value; continue; } - if (!strcasecmp (*ap, "permission")) { - e->eb_permission = *ep; + if (!strcasecmp (pm->pm_name, "permission")) { + e->eb_permission = pm->pm_value; continue; } - if (!strcasecmp (*ap, "site")) { - e->eb_site = *ep; + if (!strcasecmp (pm->pm_name, "site")) { + e->eb_site = pm->pm_value; continue; } - if (!strcasecmp (*ap, "directory")) { - e->eb_dir = *ep; + if (!strcasecmp (pm->pm_name, "directory")) { + e->eb_dir = pm->pm_value; continue; } - if (!strcasecmp (*ap, "mode")) { - e->eb_mode = *ep; + if (!strcasecmp (pm->pm_name, "mode")) { + e->eb_mode = pm->pm_value; continue; } - if (!strcasecmp (*ap, "size")) { - sscanf (*ep, "%lu", &e->eb_size); + if (!strcasecmp (pm->pm_name, "size")) { + sscanf (pm->pm_value, "%lu", &e->eb_size); continue; } - if (!strcasecmp (*ap, "server")) { - e->eb_server = *ep; + if (!strcasecmp (pm->pm_name, "server")) { + e->eb_server = pm->pm_value; continue; } - if (!strcasecmp (*ap, "subject")) { - e->eb_subject = *ep; + if (!strcasecmp (pm->pm_name, "subject")) { + e->eb_subject = pm->pm_value; + continue; + } + if (!strcasecmp (pm->pm_name, "url")) { + /* + * According to RFC 2017, we have to remove all whitespace from + * the URL + */ + + char *u, *p = pm->pm_value; + e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 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); + if (composing && !strcasecmp (pm->pm_name, "body")) { + e->eb_body = getcpy (pm->pm_value); continue; } } @@ -1442,14 +1654,10 @@ params_external (CT ct, int composing) static int InitApplication (CT ct) { - struct k2v *kv; CI ci = &ct->c_ctinfo; /* match subtype */ - for (kv = SubApplication; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; + ct->c_subtype = ct_str_subtype (CT_APPLICATION, ci->ci_subtype); return OK; } @@ -1462,12 +1670,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; @@ -1476,13 +1678,10 @@ init_encoding (CT ct, OpenCEFunc openfnx) } -static void +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); @@ -1497,12 +1696,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; @@ -1565,21 +1761,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, text = ct->c_type == CT_TEXT; + uint32_t bits; + unsigned char value, b; char *cp, *ep, buffer[BUFSIZ]; - /* sbeck -- handle prefixes */ + /* sbeck -- handle suffixes */ CI ci; - CE ce; + CE ce = &ct->c_cefile; 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; @@ -1594,7 +1784,6 @@ openBase64 (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -1603,16 +1792,24 @@ openBase64 (CT ct, char **file) /* 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 = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) { + 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 (cp != NULL && *cp != '\0') - 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"); @@ -1622,9 +1819,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)) @@ -1653,13 +1853,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); } @@ -1671,17 +1871,23 @@ openBase64 (CT ct, char **file) bits |= value << bitno; test_end: if ((bitno -= 6) < 0) { - putc ((char) *b1, ce->ce_fp); + b = (bits >> 16) & 0xff; + if (!text || b != '\r') + 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; + if (! text || b != '\r') + 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; + if (! text || b != '\r') + putc ((char) b, ce->ce_fp); if (digested) - MD5Update (&mdContext, b3, 1); + MD5Update (&mdContext, &b, 1); } } @@ -1736,9 +1942,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; } @@ -1778,16 +1992,17 @@ 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]; + char *bufp = NULL; + size_t buflen; + ssize_t gotlen; unsigned char mask; - CE ce; - /* sbeck -- handle prefixes */ + 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; @@ -1802,7 +2017,6 @@ openQuoted (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -1811,20 +2025,23 @@ openQuoted (CT ct, char **file) /* 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') - 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; + if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) { + 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) { @@ -1835,9 +2052,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)) @@ -1850,96 +2070,83 @@ 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) { + if ((gotlen = getline(&bufp, &buflen, ct->c_fp)) == -1) { content_error (NULL, ct, "premature eof"); goto clean_up; } - if ((cc = strlen (buffer)) > len) + if ((cc = gotlen) > len) cc = len; len -= cc; - for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) - if (!isspace (*ep)) + for (ep = (cp = bufp) + cc - 1; cp <= ep; 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); - } - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; + /* 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; } - break; - - case '=': - if (*++cp != '\n') { + 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; - cp--; + 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; + } + + /* 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); + } + } + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; } } } @@ -1973,10 +2180,20 @@ invalid_encoding: ready_to_go: *file = ce->ce_file; + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + free (bufp); return fileno (ce->ce_fp); clean_up: free_encoding (ct, 0); + if (own_ct_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + free (bufp); return NOTOK; } @@ -1996,17 +2213,16 @@ 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 prefixes */ + /* 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; @@ -2021,7 +2237,6 @@ open7Bit (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); ce->ce_unlink = 1; } else { ce->ce_file = add (*file, NULL); @@ -2030,16 +2245,24 @@ open7Bit (CT ct, char **file) /* 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 = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) { + 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 (cp != NULL && *cp != '\0') - 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"); @@ -2047,28 +2270,18 @@ open7Bit (CT ct, char **file) } if (ct->c_type == CT_MULTIPART) { - char **ap, **ep; CI ci = &ct->c_ctinfo; + char *buffer; 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); + buffer = output_params(len, ci->ci_first_pm, &len, 0); - 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 (buffer) { + fputs (buffer, ce->ce_fp); + free(buffer); } if (ci->ci_comment) { @@ -2088,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); @@ -2115,7 +2333,9 @@ open7Bit (CT ct, char **file) cc = len; len -= cc; - fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp); + if ((int) fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp) < cc) { + advise ("open7Bit", "fwrite"); + } if (ferror (ce->ce_fp)) { content_error (ce->ce_file, ct, "error writing to"); goto clean_up; @@ -2133,10 +2353,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,6 +2402,7 @@ openExternal (CT ct, CT cb, CE ce, char **file, int *fd) } } + *fd = fileno (ce->ce_fp); return OK; ready_already: @@ -2199,7 +2428,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: @@ -2241,17 +2470,19 @@ openFile (CT ct, char **file) while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); + if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) { + advise ("openFile", "fwrite"); + } fflush (fp); 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); } @@ -2282,20 +2513,17 @@ 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; -#ifndef BUILTIN_FTP if (!ftp) return NOTOK; -#endif switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { case NOTOK: @@ -2314,13 +2542,6 @@ openFTP (CT ct, char **file) return NOTOK; } - if (xpid) { - if (xpid < 0) - xpid = -xpid; - pidcheck (pidwait (xpid, NOTOK)); - xpid = 0; - } - /* Get the buffer ready to go */ bp = buffer; buflen = sizeof(buffer); @@ -2362,7 +2583,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); @@ -2386,17 +2608,20 @@ 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_scratch ("", tmp), 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"); return NOTOK; } -#ifdef BUILTIN_FTP - if (ftp) -#endif { int child_id, i, vecp; char *vec[9]; @@ -2415,7 +2640,7 @@ openFTP (CT ct, char **file) 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: @@ -2432,9 +2657,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; @@ -2442,14 +2664,6 @@ 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 (caching) @@ -2467,17 +2681,19 @@ losing_ftp: while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); + if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) { + advise ("openFTP", "fwrite"); + } fflush (fp); 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); } @@ -2509,7 +2725,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: @@ -2527,13 +2743,6 @@ openMail (CT ct, char **file) 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); @@ -2568,7 +2777,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: @@ -2589,7 +2798,12 @@ openMail (CT ct, char **file) } if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), 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); @@ -2601,6 +2815,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); @@ -2611,19 +2827,150 @@ 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; + } + + 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) + if ((int) fwrite(buffer, sizeof(*buffer), cc, fp) < cc) { + advise ("openURL", "fwrite"); + } + + 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) { 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; @@ -2645,11 +2992,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; @@ -2675,7 +3022,7 @@ invalid_digest: while (*cp) cp++; fprintf (stderr, "invalid MD5 digest (got %d octets)\n", - cp - bp); + (int)(cp - bp)); } return NOTOK; @@ -2690,3 +3037,1333 @@ 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; + int max = BUFSIZ; + char *bufp = NULL; + size_t buflen; + ssize_t gotlen; + 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 ((gotlen = getline(&bufp, &buflen, ct->c_fp)) != -1) { + read += gotlen; + /* Don't look beyond beginning of first subpart (before) or + next part (after). */ + if (read > max) bufp[read-max] = '\0'; + + if (before) { + if (! strcmp (bufp, boundary)) { + found_boundary = 1; + } + } else { + if (! found_boundary && ! strcmp (bufp, boundary)) { + found_boundary = 1; + continue; + } + } + + if ((before && ! found_boundary) || (! before && found_boundary)) { + if (content) { + char *old_content = content; + content = concat (content, bufp, NULL); + free (old_content); + } else { + content = before + ? concat ("\n", bufp, NULL) + : concat (bufp, 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); + free (bufp); + + 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"; + case MULTI_RELATED: + return "related"; + 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"; + } +} + + +int +ct_str_type (const char *type) { + struct str2init *s2i; + + for (s2i = str2cts; s2i->si_key; ++s2i) { + if (! strcasecmp (type, s2i->si_key)) { + break; + } + } + if (! s2i->si_key && ! uprf (type, "X-")) { + ++s2i; + } + + return s2i->si_val; +} + + +int +ct_str_subtype (int type, const char *subtype) { + struct k2v *kv; + + switch (type) { + case CT_APPLICATION: + for (kv = SubApplication; kv->kv_key; ++kv) { + if (! strcasecmp (subtype, kv->kv_key)) { + break; + } + } + return kv->kv_value; + case CT_MESSAGE: + for (kv = SubMessage; kv->kv_key; ++kv) { + if (! strcasecmp (subtype, kv->kv_key)) { + break; + } + } + return kv->kv_value; + case CT_MULTIPART: + for (kv = SubMultiPart; kv->kv_key; ++kv) { + if (! strcasecmp (subtype, kv->kv_key)) { + break; + } + } + return kv->kv_value; + case CT_TEXT: + for (kv = SubText; kv->kv_key; ++kv) { + if (! strcasecmp (subtype, kv->kv_key)) { + break; + } + } + return kv->kv_value; + default: + return 0; + } +} + + +/* 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; +} + +/* + * Parse a series of MIME attributes (or parameters) given a header as + * input. + * + * Arguments include: + * + * filename - Name of input file (for error messages) + * fieldname - Name of field being processed + * headerp - Pointer to pointer of the beginning of the MIME attributes. + * Updated to point to end of attributes when finished. + * param_head - Pointer to head of parameter list + * param_tail - Pointer to tail of parameter list + * commentp - Pointer to header comment pointer (may be NULL) + * + * Returns OK if parsing was successful, NOTOK if parsing failed, and + * DONE to indicate a benign error (minor parsing error, but the program + * should continue). + */ + +static int +parse_header_attrs (const char *filename, const char *fieldname, + char **header_attrp, PM *param_head, PM *param_tail, + char **commentp) +{ + char *cp = *header_attrp; + PM pm; + struct sectlist { + char *value; + int index; + int len; + struct sectlist *next; + } *sp, *sp2; + struct parmlist { + char *name; + char *charset; + char *lang; + struct sectlist *sechead; + struct parmlist *next; + } *pp, *pp2, *phead = NULL; + + while (*cp == ';') { + char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL; + int encoded = 0, partial = 0, len = 0, index = 0; + + cp++; + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, fieldname, &cp, commentp) == NOTOK) { + return NOTOK; + } + + if (*cp == 0) { + if (! suppress_extraneous_trailing_semicolon_warning) { + advise (NULL, + "extraneous trailing ';' in message %s's %s: " + "parameter list", + filename, fieldname); + } + extraneous_trailing_semicolon = 1; + return DONE; + } + + /* 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, fieldname, strlen(invo_name) + 2, "",cp, dp - cp); + return NOTOK; + } + + /* + * To handle RFC 2231, we have to deal with the following extensions: + * + * name*=encoded-value + * name*=part-N-of-a-parameter-value + * name**=encoded-part-N-of-a-parameter-value + * + * So the rule is: + * If there's a * right before the equal sign, it's encoded. + * If there's a * and one or more digits, then it's section N. + * + * Remember we can have one or the other, or both. cp points to + * beginning of name, up points past the last character in the + * parameter name. + */ + + for (vp = cp; vp < up; vp++) { + if (*vp == '*' && vp < up - 1) { + partial = 1; + continue; + } else if (*vp == '*' && vp == up - 1) { + encoded = 1; + } else if (partial) { + if (isdigit((unsigned char) *vp)) + index = *vp - '0' + index * 10; + else { + advise (NULL, "invalid parameter index in message %s's " + "%s: field\n%*s(parameter %s)", filename, + fieldname, strlen(invo_name) + 2, "", cp); + return NOTOK; + } + } else { + len++; + } + } + + /* + * Break out the parameter name and value sections and allocate + * memory for each. + */ + + nameptr = mh_xmalloc(len + 1); + strncpy(nameptr, cp, len); + nameptr[len] = '\0'; + + for (dp++; isspace ((unsigned char) *dp);) + dp++; + + if (encoded) { + /* + * Single quotes delimit the character set and language tag. + * They are required on the first section (or a complete + * parameter). + */ + if (index == 0) { + vp = dp; + while (*vp != '\'' && !isspace((unsigned char) *vp) && + *vp != '\0') + vp++; + if (*vp == '\'') { + if (vp != dp) { + len = vp - dp; + charset = mh_xmalloc(len + 1); + strncpy(charset, dp, len); + charset[len] = '\0'; + } else { + charset = NULL; + } + vp++; + } else { + advise(NULL, "missing charset in message %s's %s: " + "field\n%*s(parameter %s)", filename, fieldname, + strlen(invo_name) + 2, "", nameptr); + free(nameptr); + return NOTOK; + } + dp = vp; + + while (*vp != '\'' && !isspace((unsigned char) *vp) && + *vp != '\0') + vp++; + + if (*vp == '\'') { + if (vp != dp) { + len = vp - dp; + lang = mh_xmalloc(len + 1); + strncpy(lang, dp, len); + lang[len] = '\0'; + } else { + lang = NULL; + } + vp++; + } else { + advise(NULL, "missing language tag in message %s's %s: " + "field\n%*s(parameter %s)", filename, fieldname, + strlen(invo_name) + 2, "", nameptr); + free(nameptr); + if (charset) + free(charset); + return NOTOK; + } + + dp = vp; + } + + /* + * At this point vp should be pointing at the beginning + * of the encoded value/section. Continue until we reach + * the end or get whitespace. But first, calculate the + * length so we can allocate the correct buffer size. + */ + + for (vp = dp, len = 0; istoken(*vp); vp++) { + if (*vp == '%') { + if (*(vp + 1) == '\0' || + !isxdigit((unsigned char) *(vp + 1)) || + *(vp + 2) == '\0' || + !isxdigit((unsigned char) *(vp + 2))) { + advise(NULL, "invalid encoded sequence in message " + "%s's %s: field\n%*s(parameter %s)", + filename, fieldname, strlen(invo_name) + 2, + "", nameptr); + free(nameptr); + if (charset) + free(charset); + if (lang) + free(lang); + return NOTOK; + } + vp += 2; + } + len++; + } + + up = valptr = mh_xmalloc(len + 1); + + for (vp = dp; istoken(*vp); vp++) { + if (*vp == '%') { + *up++ = decode_qp(*(vp + 1), *(vp + 2)); + vp += 2; + } else { + *up++ = *vp; + } + } + + *up = '\0'; + cp = vp; + } else { + /* + * A "normal" string. If it's got a leading quote, then we + * strip the quotes out. Otherwise go until we reach the end + * or get whitespace. Note we scan it twice; once to get the + * length, then the second time copies it into the destination + * buffer. + */ + + len = 0; + + if (*dp == '"') { + for (cp = dp + 1;;) { + switch (*cp++) { + case '\0': +bad_quote: + advise (NULL, + "invalid quoted-string in message %s's %s: " + "field\n%*s(parameter %s)", + filename, fieldname, strlen(invo_name) + 2, "", + nameptr); + free(nameptr); + if (charset) + free(charset); + if (lang) + free(lang); + return NOTOK; + case '"': + break; + + case '\\': + if (*++cp == '\0') + goto bad_quote; + /* FALL THROUGH */ + default: + len++; + continue; + } + break; + } + + } else { + for (cp = dp; istoken (*cp); cp++) { + len++; + } + } + + valptr = mh_xmalloc(len + 1); + + if (*dp == '"') { + int i; + for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) { + if (*cp == '\\') { + cp++; + } + *vp++ = *cp++; + } + cp++; + } else { + strncpy(valptr, cp = dp, len); + cp += len; + } + + valptr[len] = '\0'; + } + + /* + * If 'partial' is set, we don't allocate a parameter now. We + * put it on the parameter linked list to be reassembled later. + * + * "phead" points to a list of all parameters we need to reassemble. + * Each parameter has a list of sections. We insert the sections in + * order. + */ + + if (partial) { + for (pp = phead; pp != NULL; pp = pp->next) { + if (strcasecmp(nameptr, pp->name) == 0) + break; + } + + if (pp == NULL) { + pp = mh_xmalloc(sizeof(*pp)); + memset(pp, 0, sizeof(*pp)); + pp->name = nameptr; + pp->next = phead; + phead = pp; + } + + /* + * Insert this into the section linked list + */ + + sp = mh_xmalloc(sizeof(*sp)); + memset(sp, 0, sizeof(*sp)); + sp->value = valptr; + sp->index = index; + sp->len = len; + + if (pp->sechead == NULL || pp->sechead->index > index) { + sp->next = pp->sechead; + pp->sechead = sp; + } else { + for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) { + if (sp2->index == sp->index) { + advise (NULL, "duplicate index (%d) in message " + "%s's %s: field\n%*s(parameter %s)", sp->index, + filename, fieldname, strlen(invo_name) + 2, "", + nameptr); + free (nameptr); + return NOTOK; + } + if (sp2->index < sp->index && + (sp2->next == NULL || sp2->next->index > sp->index)) { + sp->next = sp2->next; + sp2->next = sp; + break; + } + } + + if (sp2 == NULL) { + advise(NULL, "Internal error: cannot insert partial " + "param in message %s's %s: field\n%*s(parameter %s)", + filename, fieldname, strlen(invo_name) + 2, "", + nameptr); + free (nameptr); + return NOTOK; + } + } + + /* + * Save our charset and lang tags. + */ + + if (index == 0 && encoded) { + if (pp->charset) + free(pp->charset); + pp->charset = charset; + if (pp->lang) + free(pp->lang); + pp->lang = lang; + } + } else { + pm = add_param(param_head, param_tail, nameptr, valptr, 1); + pm->pm_charset = charset; + pm->pm_lang = lang; + } + + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, fieldname, &cp, commentp) == NOTOK) { + return NOTOK; + } + } + + /* + * Now that we're done, reassemble all of the partial parameters. + */ + + for (pp = phead; pp != NULL; ) { + char *p, *q; + size_t tlen = 0; + int pindex = 0; + for (sp = pp->sechead; sp != NULL; sp = sp->next) { + if (sp->index != pindex++) { + advise(NULL, "missing section %d for parameter in " + "message %s's %s: field\n%*s(parameter %s)", pindex - 1, + filename, fieldname, strlen(invo_name) + 2, "", + pp->name); + return NOTOK; + } + tlen += sp->len; + } + + p = q = mh_xmalloc(tlen + 1); + for (sp = pp->sechead; sp != NULL; ) { + memcpy(q, sp->value, sp->len); + q += sp->len; + free(sp->value); + sp2 = sp->next; + free(sp); + sp = sp2; + } + + p[tlen] = '\0'; + + pm = add_param(param_head, param_tail, pp->name, p, 1); + pm->pm_charset = pp->charset; + pm->pm_lang = pp->lang; + pp2 = pp->next; + free(pp); + pp = pp2; + } + + *header_attrp = cp; + return OK; +} + +/* + * Return the charset for a particular content type. + */ + +char * +content_charset (CT ct) { + char *ret_charset = NULL; + + ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0); + + return ret_charset ? ret_charset : getcpy ("US-ASCII"); +} + + +/* + * Create a string based on a list of output parameters. Assume that this + * parameter string will be appended to an existing header, so start out + * with the separator (;). Perform RFC 2231 encoding when necessary. + */ + +char * +output_params(size_t initialwidth, PM params, int *offsetout, int external) +{ + char *paramout = NULL; + char line[CPERLIN * 2], *q; + int curlen, index, cont, encode, i; + size_t valoff, numchars; + + while (params != NULL) { + encode = 0; + index = 0; + valoff = 0; + q = line; + + if (external && strcasecmp(params->pm_name, "body") == 0) + continue; + + if (strlen(params->pm_name) > CPERLIN) { + advise(NULL, "Parameter name \"%s\" is too long", params->pm_name); + if (paramout) + free(paramout); + return NULL; + } + + curlen = param_len(params, index, valoff, &encode, &cont, &numchars); + + /* + * Loop until we get a parameter that fits within a line. We + * assume new lines start with a tab, so check our overflow based + * on that. + */ + + while (cont) { + *q++ = ';'; + *q++ = '\n'; + *q++ = '\t'; + + /* + * At this point we're definitely continuing the line, so + * be sure to include the parameter name and section index. + */ + + q += snprintf(q, sizeof(line) - (q - line), "%s*%d", + params->pm_name, index); + + /* + * Both of these functions do a NUL termination + */ + + if (encode) + i = encode_param(params, q, sizeof(line) - (q - line), + numchars, valoff, index); + else + i = normal_param(params, q, sizeof(line) - (q - line), + numchars, valoff); + + if (i == 0) { + if (paramout) + free(paramout); + return NULL; + } + + valoff += numchars; + index++; + curlen = param_len(params, index, valoff, &encode, &cont, + &numchars); + q = line; + + /* + * "line" starts with a ;\n\t, so that doesn't count against + * the length. But add 8 since it starts with a tab; that's + * how we end up with 5. + */ + + initialwidth = strlen(line) + 5; + + /* + * At this point the line should be built, so add it to our + * current output buffer. + */ + + paramout = add(line, paramout); + } + + /* + * If this won't fit on the line, start a new one. Save room in + * case we need a semicolon on the end + */ + + if (initialwidth + curlen > CPERLIN - 1) { + *q++ = ';'; + *q++ = '\n'; + *q++ = '\t'; + initialwidth = 8; + } else { + *q++ = ';'; + *q++ = ' '; + initialwidth += 2; + } + + /* + * At this point, we're either finishing a contined parameter, or + * we're working on a new one. + */ + + if (index > 0) { + q += snprintf(q, sizeof(line) - (q - line), "%s*%d", + params->pm_name, index); + } else { + strncpy(q, params->pm_name, sizeof(line) - (q - line)); + q += strlen(q); + } + + if (encode) + i = encode_param(params, q, sizeof(line) - (q - line), + strlen(params->pm_value + valoff), valoff, index); + else + i = normal_param(params, q, sizeof(line) - (q - line), + strlen(params->pm_value + valoff), valoff); + + if (i == 0) { + if (paramout) + free(paramout); + return NULL; + } + + paramout = add(line, paramout); + initialwidth += strlen(line); + + params = params->pm_next; + } + + if (offsetout) + *offsetout = initialwidth; + + return paramout; +} + +/* + * Calculate the size of a parameter. + * + * Arguments include + * + * pm - The parameter being output + * index - If continuing the parameter, the index of the section + * we're on. + * valueoff - The current offset into the parameter value that we're + * working on (previous sections have consumed valueoff bytes). + * encode - Set if we should perform encoding on this parameter section + * (given that we're consuming bytesfit bytes). + * cont - Set if the remaining data in value will not fit on a single + * line and will need to be continued. + * bytesfit - The number of bytes that we can consume from the parameter + * value and still fit on a completely new line. The + * calculation assumes the new line starts with a tab, + * includes the parameter name and any encoding, and fits + * within CPERLIN bytes. Will always be at least 1. + */ + +static size_t +param_len(PM pm, int index, size_t valueoff, int *encode, int *cont, + size_t *bytesfit) +{ + char *start = pm->pm_value + valueoff, *p, indexchar[32]; + size_t len = 0, fit = 0; + int fitlimit = 0, eightbit, maxfit; + + *encode = 0; + + /* + * Add up the length. First, start with the parameter name. + */ + + len = strlen(pm->pm_name); + + /* + * Scan the parameter value and see if we need to do encoding for this + * section. + */ + + eightbit = contains8bit(start, NULL); + + /* + * Determine if we need to encode this section. Encoding is necessary if: + * + * - There are any 8-bit characters at all and we're on the first + * section. + * - There are 8-bit characters within N bytes of our section start. + * N is calculated based on the number of bytes it would take to + * reach CPERLIN. Specifically: + * 8 (starting tab) + + * strlen(param name) + + * 4 ('* for section marker, '=', opening/closing '"') + * strlen (index) + * is the number of bytes used by everything that isn't part of the + * value. So that gets subtracted from CPERLIN. + */ + + snprintf(indexchar, sizeof(indexchar), "%d", index); + maxfit = CPERLIN - (12 + len + strlen(indexchar)); + if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) { + *encode = 1; + } + + len++; /* Add in equal sign */ + + if (*encode) { + /* + * We're using maxfit as a marker for how many characters we can + * fit into the line. Bump it by two because we're not using quotes + * when encoding. + */ + + maxfit += 2; + + /* + * If we don't have a charset or language tag in this parameter, + * add them now. + */ + + if (! pm->pm_charset) { + pm->pm_charset = getcpy(write_charset_8bit()); + if (strcasecmp(pm->pm_charset, "US-ASCII") == 0) + adios(NULL, "8-bit characters in parameter \"%s\", but " + "local character set is US-ASCII", pm->pm_name); + } + if (! pm->pm_lang) + pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */ + + len++; /* For the encoding marker */ + maxfit--; + if (index == 0) { + int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2; + len += enclen; + maxfit-= enclen; + } else { + /* + * We know we definitely need to include an index. maxfit already + * includes the section marker. + */ + len += strlen(indexchar); + } + for (p = start; *p != '\0'; p++) { + if (isparamencode(*p)) { + len += 3; + maxfit -= 3; + } else { + len++; + maxfit--; + } + /* + * Just so there's no confusion: maxfit is counting OUTPUT + * characters (post-encoding). fit is counting INPUT characters. + */ + if (! fitlimit && maxfit >= 0) + fit++; + else if (! fitlimit) + fitlimit++; + } + } else { + /* + * Calculate the string length, but add room for quoting \ + * and " if necessary. Also account for quotes at beginning + * and end. + */ + for (p = start; *p != '\0'; p++) { + switch (*p) { + case '"': + case '\\': + len++; + maxfit--; + /* FALL THROUGH */ + default: + len++; + maxfit--; + } + if (! fitlimit && maxfit >= 0) + fit++; + else if (! fitlimit) + fitlimit++; + } + + len += 2; + } + + if (fit < 1) + fit = 1; + + *cont = fitlimit; + *bytesfit = fit; + + return len; +} + +/* + * Output an encoded parameter string. + */ + +static size_t +encode_param(PM pm, char *output, size_t len, size_t valuelen, + size_t valueoff, int index) +{ + size_t outlen = 0, n; + char *endptr = output + len, *p; + + /* + * First, output the marker for an encoded string. + */ + + *output++ = '*'; + *output++ = '='; + outlen += 2; + + /* + * If the index is 0, output the character set and language tag. + * If theses were NULL, they should have already been filled in + * by param_len(). + */ + + if (index == 0) { + n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset, + pm->pm_lang); + output += n; + outlen += n; + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + /* + * Copy over the value, encoding if necessary + */ + + p = pm->pm_value + valueoff; + while (valuelen-- > 0) { + if (isparamencode(*p)) { + n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++); + output += n; + outlen += n; + } else { + *output++ = *p++; + outlen++; + } + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + *output = '\0'; + + return outlen; +} + +/* + * Output a "normal" parameter, without encoding. Be sure to escape + * quotes and backslashes if necessary. + */ + +static size_t +normal_param(PM pm, char *output, size_t len, size_t valuelen, + size_t valueoff) +{ + size_t outlen = 0; + char *endptr = output + len, *p; + + *output++ = '='; + *output++ = '"'; + outlen += 2; + + p = pm->pm_value + valueoff; + + while (valuelen-- > 0) { + switch (*p) { + case '\\': + case '"': + *output++ = '\\'; + outlen++; + default: + *output++ = *p++; + outlen++; + } + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + if (output - 2 > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + + *output++ = '"'; + *output++ = '\0'; + + return outlen + 1; +} + +/* + * Add a parameter to the parameter linked list + */ + +PM +add_param(PM *first, PM *last, char *name, char *value, int nocopy) +{ + PM pm = mh_xmalloc(sizeof(*pm)); + + memset(pm, 0, sizeof(*pm)); + + pm->pm_name = nocopy ? name : getcpy(name); + pm->pm_value = nocopy ? value : getcpy(value); + + if (*first) { + (*last)->pm_next = pm; + *last = pm; + } else { + *first = pm; + *last = pm; + } + + return pm; +} + +/* + * Either replace a current parameter with a new value, or add the parameter + * to the parameter linked list. + */ + +PM +replace_param(PM *first, PM *last, char *name, char *value, int nocopy) +{ + PM pm; + + for (pm = *first; pm != NULL; pm = pm->pm_next) { + if (strcasecmp(name, pm->pm_name) == 0) { + /* + * If nocopy is set, it's assumed that we own both name + * and value. We don't need name, so we discard it now. + */ + if (nocopy) + free(name); + free(pm->pm_value); + pm->pm_value = nocopy ? value : getcpy(value); + return pm; + } + } + + return add_param(first, last, name, value, nocopy); +} + +/* + * Retrieve a parameter value from a parameter linked list. If the parameter + * value needs converted to the local character set, do that now. + */ + +char * +get_param(PM first, const char *name, char replace, int fetchonly) +{ + while (first != NULL) { + if (strcasecmp(name, first->pm_name) == 0) { + if (fetchonly) + return first->pm_value; + else + return getcpy(get_param_value(first, replace)); + } + first = first->pm_next; + } + + return NULL; +} + +/* + * Return a parameter value, converting to the local character set if + * necessary + */ + +char *get_param_value(PM pm, char replace) +{ + static char buffer[4096]; /* I hope no parameters are larger */ + size_t bufsize = sizeof(buffer); +#ifdef HAVE_ICONV + size_t inbytes; + int utf8; + iconv_t cd; + ICONV_CONST char *p; +#else /* HAVE_ICONV */ + char *p; +#endif /* HAVE_ICONV */ + + char *q; + + /* + * If we don't have a character set indicated, it's assumed to be + * US-ASCII. If it matches our character set, we don't need to convert + * anything. + */ + + if (!pm->pm_charset || check_charset(pm->pm_charset, + strlen(pm->pm_charset))) { + return pm->pm_value; + } + + /* + * In this case, we need to convert. If we have iconv support, use + * that. Otherwise, go through and simply replace every non-ASCII + * character with the substitution character. + */ + +#ifdef HAVE_ICONV + q = buffer; + bufsize = sizeof(buffer); + utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0; + + cd = iconv_open(get_charset(), pm->pm_charset); + if (cd == (iconv_t) -1) { + goto noiconv; + } + + inbytes = strlen(pm->pm_value); + p = pm->pm_value; + + while (inbytes) { + if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) { + if (errno != EILSEQ) { + iconv_close(cd); + goto noiconv; + } + /* + * Reset shift state, substitute our character, + * try to restart conversion. + */ + + iconv(cd, NULL, NULL, &q, &bufsize); + + if (bufsize == 0) { + iconv_close(cd); + goto noiconv; + } + *q++ = replace; + bufsize--; + if (bufsize == 0) { + iconv_close(cd); + goto noiconv; + } + if (utf8) { + for (++p, --inbytes; + inbytes > 0 && (((unsigned char) *q) & 0xc0) == 0x80; + ++p, --inbytes) + continue; + } else { + p++; + inbytes--; + } + } + } + + iconv_close(cd); + + if (bufsize == 0) + q--; + *q = '\0'; + + return buffer; + +noiconv: +#endif /* HAVE_ICONV */ + + /* + * Take everything non-ASCII and substituite the replacement character + */ + + q = buffer; + bufsize = sizeof(buffer); + for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) { + if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p)) + *q = *p; + else + *q = replace; + } + + *q = '\0'; + + return buffer; +}