* mhbuildsbr.c -- routines to expand/translate MIME composition files
*
* $Id$
+ *
+ * This code is Copyright (c) 2002, by the authors of nmh. See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
*/
/*
#include <h/md5.h>
#include <errno.h>
#include <signal.h>
-#include <zotnet/mts/mts.h>
+#include <h/mts.h>
#include <h/tws.h>
#include <h/mime.h>
#include <h/mhparse.h>
+#include <h/utils.h>
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
#endif
-extern int errno;
-
extern int debugsw;
extern int verbosw;
extern int ebcdicsw;
extern int listsw;
extern int rfc934sw;
+extern int contentidsw;
extern int endian; /* mhmisc.c */
*/
static CT get_content (FILE *, char *, int);
static int add_header (CT, char *, char *);
-static int get_ctinfo (char *, CT, int);
-static int get_comment (CT, char **, int);
+static int get_ctinfo (unsigned char *, CT, int);
+static int get_comment (CT, unsigned char **, int);
static int InitGeneric (CT);
static int InitText (CT);
static int InitMultiPart (CT);
static int build_headers (CT);
static char *calculate_digest (CT, int);
static int readDigest (CT, char *);
+static char *incl_name_value (unsigned char *, char *, char *);
+static char *extract_name_value (char *, char *);
/*
* Structures for mapping (content) types to
compnum++;
/* abort if draft has Mime-Version header field */
- if (!strcasecmp (name, VRSN_FIELD))
+ if (!mh_strcasecmp (name, VRSN_FIELD))
adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
/* abort if draft has Content-Transfer-Encoding header field */
- if (!strcasecmp (name, ENCODING_FIELD))
+ if (!mh_strcasecmp (name, ENCODING_FIELD))
adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
/* ignore any Content-Type fields in the header */
- if (!strcasecmp (name, TYPE_FIELD)) {
+ if (!mh_strcasecmp (name, TYPE_FIELD)) {
while (state == FLDPLUS)
state = m_getfld (state, name, buf, sizeof(buf), in);
goto finish_field;
compnum++;
/* Get MIME-Version field */
- if (!strcasecmp (name, VRSN_FIELD)) {
+ if (!mh_strcasecmp (name, VRSN_FIELD)) {
int ucmp;
- char c, *cp, *dp;
+ char c;
+ unsigned char *cp, *dp;
cp = add (buf, NULL);
while (state == FLDPLUS) {
for (dp = cp; istoken (*dp); dp++)
continue;
c = *dp, *dp = '\0';
- ucmp = !strcasecmp (cp, VRSN_VALUE);
+ ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
*dp = c;
if (!ucmp)
admonish (NULL,
}
/* Get Content-Type field */
- if (!strcasecmp (name, TYPE_FIELD)) {
+ if (!mh_strcasecmp (name, TYPE_FIELD)) {
char *cp;
struct str2init *s2i;
CI ci = &ct->c_ctinfo;
* flag for this content type.
*/
for (s2i = str2cts; s2i->si_key; s2i++)
- if (!strcasecmp (ci->ci_type, s2i->si_key))
+ if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
break;
if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
s2i++;
}
/* Get Content-Transfer-Encoding field */
- if (!strcasecmp (name, ENCODING_FIELD)) {
- char *cp, *dp;
+ if (!mh_strcasecmp (name, ENCODING_FIELD)) {
+ unsigned char *cp, *dp;
char c;
struct str2init *s2i;
* for this transfer encoding.
*/
for (s2i = str2ces; s2i->si_key; s2i++)
- if (!strcasecmp (cp, s2i->si_key))
+ if (!mh_strcasecmp (cp, s2i->si_key))
break;
if (!s2i->si_key && !uprf (cp, "X-"))
s2i++;
}
/* Get Content-ID field */
- if (!strcasecmp (name, ID_FIELD)) {
+ if (!mh_strcasecmp (name, ID_FIELD)) {
ct->c_id = add (buf, ct->c_id);
while (state == FLDPLUS) {
state = m_getfld (state, name, buf, sizeof(buf), in);
}
/* Get Content-Description field */
- if (!strcasecmp (name, DESCR_FIELD)) {
+ if (!mh_strcasecmp (name, DESCR_FIELD)) {
ct->c_descr = add (buf, ct->c_descr);
while (state == FLDPLUS) {
state = m_getfld (state, name, buf, sizeof(buf), in);
goto got_header;
}
+ /* Get Content-Disposition field */
+ if (!mh_strcasecmp (name, DISPO_FIELD)) {
+ ct->c_dispo = add (buf, ct->c_dispo);
+ while (state == FLDPLUS) {
+ state = m_getfld (state, name, buf, sizeof(buf), in);
+ ct->c_dispo = add (buf, ct->c_dispo);
+ }
+ goto got_header;
+ }
+
/* Get Content-MD5 field */
- if (!strcasecmp (name, MD5_FIELD)) {
- char *cp, *dp, *ep;
+ if (!mh_strcasecmp (name, MD5_FIELD)) {
+ unsigned char *cp, *dp;
+ char *ep;
cp = add (buf, NULL);
while (state == FLDPLUS) {
HF hp;
/* allocate header field structure */
- if (!(hp = malloc (sizeof(*hp))))
- adios (NULL, "out of memory");
+ hp = mh_xmalloc (sizeof(*hp));
/* link data into header structure */
hp->name = name;
*/
static int
-get_ctinfo (char *cp, CT ct, int magic)
+get_ctinfo (unsigned char *cp, CT ct, int magic)
{
int i;
- char *dp, **ap, **ep;
+ unsigned char *dp;
+ char **ap, **ep;
char c;
CI ci;
*/
ep = (ap = ci->ci_attrs) + NPARMS;
while (*cp == ';') {
- char *vp, *up;
+ char *vp;
+ unsigned char *up;
if (ap >= ep) {
advise (NULL,
}
/*
+ * Get any {Content-Disposition} given in buffer.
+ */
+ if (magic && *cp == '{') {
+ ct->c_dispo = ++cp;
+ for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
+ if (*dp == '}')
+ break;
+ if (dp < cp) {
+ advise (NULL, "invalid disposition in message %s", ct->c_file);
+ ct->c_dispo = NULL;
+ return NOTOK;
+ }
+
+ c = *dp;
+ *dp = '\0';
+ if (*ct->c_dispo)
+ ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
+ else
+ ct->c_dispo = NULL;
+ *dp++ = c;
+ cp = dp;
+
+ while (isspace (*cp))
+ cp++;
+ }
+
+ /*
* Check if anything is left over
*/
if (*cp) {
- if (magic)
+ if (magic) {
ci->ci_magic = add (cp, NULL);
+
+ /* If there is a Content-Disposition header and it doesn't
+ have a *filename=, extract it from the magic contents.
+ The r1bindex call skips any leading directory
+ components. */
+ if (ct->c_dispo)
+ ct->c_dispo =
+ incl_name_value (ct->c_dispo,
+ "filename",
+ r1bindex (extract_name_value ("name",
+ ci->
+ ci_magic),
+ '/'));
+ }
else
advise (NULL,
"extraneous information in message %s's %s: field\n%*.*s(%s)",
- ct->c_file, TYPE_FIELD, i, i, "", cp);
+ ct->c_file, TYPE_FIELD, i, i, "", cp);
}
return OK;
static int
-get_comment (CT ct, char **ap, int istype)
+get_comment (CT ct, unsigned char **ap, int istype)
{
int i;
- char *bp, *cp;
+ char *bp;
+ unsigned char *cp;
char c, buffer[BUFSIZ], *dp;
CI ci;
/* match subtype */
for (kv = SubText; kv->kv_key; kv++)
- if (!strcasecmp (ci->ci_subtype, kv->kv_key))
+ if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
break;
ct->c_subtype = kv->kv_value;
/* scan for charset parameter */
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
- if (!strcasecmp (*ap, "charset"))
+ if (!mh_strcasecmp (*ap, "charset"))
break;
/* check if content specified a character set */
if (*ap) {
/* match character set or set to CHARSET_UNKNOWN */
for (kv = Charset; kv->kv_key; kv++)
- if (!strcasecmp (*ep, kv->kv_key))
+ if (!mh_strcasecmp (*ep, kv->kv_key))
break;
t->tx_charset = kv->kv_value;
}
{
int inout;
long last, pos;
- char *cp, *dp, **ap, **ep;
+ unsigned char *cp, *dp;
+ char **ap, **ep;
char *bp, buffer[BUFSIZ];
struct multipart *m;
struct k2v *kv;
/* match subtype */
for (kv = SubMultiPart; kv->kv_key; kv++)
- if (!strcasecmp (ci->ci_subtype, kv->kv_key))
+ if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
break;
ct->c_subtype = kv->kv_value;
* required for multipart messages.
*/
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "boundary")) {
+ if (!mh_strcasecmp (*ap, "boundary")) {
bp = *ep;
break;
}
/* match subtype */
for (kv = SubMessage; kv->kv_key; kv++)
- if (!strcasecmp (ci->ci_subtype, kv->kv_key))
+ if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
break;
ct->c_subtype = kv->kv_value;
/* scan for parameters "id", "number", and "total" */
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "id")) {
+ if (!mh_strcasecmp (*ap, "id")) {
p->pm_partid = add (*ep, NULL);
continue;
}
- if (!strcasecmp (*ap, "number")) {
+ if (!mh_strcasecmp (*ap, "number")) {
if (sscanf (*ep, "%d", &p->pm_partno) != 1
|| p->pm_partno < 1) {
invalid_param:
}
continue;
}
- if (!strcasecmp (*ap, "total")) {
+ if (!mh_strcasecmp (*ap, "total")) {
if (sscanf (*ep, "%d", &p->pm_maxno) != 1
|| p->pm_maxno < 1)
goto invalid_param;
goto no_body;
}
- if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL)
- adios (NULL, "out of memory");
+ e->eb_body = bp = mh_xmalloc ((unsigned) size);
fseek (p->c_fp, p->c_begin, SEEK_SET);
while (size > 0)
switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
CI ci = &ct->c_ctinfo;
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "access-type")) {
+ if (!mh_strcasecmp (*ap, "access-type")) {
struct str2init *s2i;
CT p = e->eb_content;
for (s2i = str2methods; s2i->si_key; s2i++)
- if (!strcasecmp (*ep, s2i->si_key))
+ if (!mh_strcasecmp (*ep, s2i->si_key))
break;
if (!s2i->si_key) {
return NOTOK;
continue;
}
- if (!strcasecmp (*ap, "name")) {
+ if (!mh_strcasecmp (*ap, "name")) {
e->eb_name = *ep;
continue;
}
- if (!strcasecmp (*ap, "permission")) {
+ if (!mh_strcasecmp (*ap, "permission")) {
e->eb_permission = *ep;
continue;
}
- if (!strcasecmp (*ap, "site")) {
+ if (!mh_strcasecmp (*ap, "site")) {
e->eb_site = *ep;
continue;
}
- if (!strcasecmp (*ap, "directory")) {
+ if (!mh_strcasecmp (*ap, "directory")) {
e->eb_dir = *ep;
continue;
}
- if (!strcasecmp (*ap, "mode")) {
+ if (!mh_strcasecmp (*ap, "mode")) {
e->eb_mode = *ep;
continue;
}
- if (!strcasecmp (*ap, "size")) {
+ if (!mh_strcasecmp (*ap, "size")) {
sscanf (*ep, "%lu", &e->eb_size);
continue;
}
- if (!strcasecmp (*ap, "server")) {
+ if (!mh_strcasecmp (*ap, "server")) {
e->eb_server = *ep;
continue;
}
- if (!strcasecmp (*ap, "subject")) {
+ if (!mh_strcasecmp (*ap, "subject")) {
e->eb_subject = *ep;
continue;
}
- if (composing && !strcasecmp (*ap, "body")) {
+ if (composing && !mh_strcasecmp (*ap, "body")) {
e->eb_body = getcpy (*ep);
continue;
}
/* match subtype */
for (kv = SubApplication; kv->kv_key; kv++)
- if (!strcasecmp (ci->ci_subtype, kv->kv_key))
+ if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
break;
ct->c_subtype = kv->kv_value;
int fd, len, skip;
unsigned long bits;
unsigned char value, *b, *b1, *b2, *b3;
- char *cp, *ep, buffer[BUFSIZ];
+ unsigned char *cp, *ep;
+ char buffer[BUFSIZ];
CE ce;
MD5_CTX mdContext;
openQuoted (CT ct, char **file)
{
int cc, digested, len, quoted;
- char *cp, *ep;
+ unsigned char *cp, *ep;
char buffer[BUFSIZ];
unsigned char mask;
CE ce;
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");
}
return NOTOK;
}
- if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
+ if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
&& find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
cachefile, sizeof(cachefile)) != NOTOK) {
int mask;
ce->ce_unlink = (*file == NULL);
caching = 0;
cachefile[0] = '\0';
- if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
+ if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
&& find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
cachefile, sizeof(cachefile)) != NOTOK) {
if (*file == NULL) {
vec[vecp++] = e->eb_dir;
vec[vecp++] = e->eb_name;
vec[vecp++] = ce->ce_file,
- vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
+ vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
? "ascii" : "binary";
vec[vecp] = NULL;
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)
+ e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
== NOTOK)
goto losing_ftp;
#endif
user_content (FILE *in, char *file, char *buf, CT *ctp)
{
int extrnal, vrsn;
- char *cp, **ap;
+ unsigned char *cp;
+ char **ap;
char buffer[BUFSIZ];
struct multipart *m;
struct part **pp;
}
}
+ if (headers >= 0 && uprf (buffer, DISPO_FIELD)
+ && buffer[i = strlen (DISPO_FIELD)] == ':') {
+ headers = 1;
+
+again_dispo:
+ ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
+ if (!fgetstr (buffer, sizeof(buffer) - 1, in))
+ adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
+ switch (buffer[0]) {
+ case ' ':
+ case '\t':
+ i = -1;
+ goto again_dispo;
+
+ case '#':
+ adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
+ /* NOTREACHED */
+
+ default:
+ break;
+ }
+ }
+
if (headers != 1 || buffer[0] != '\n')
fputs (buffer, out);
done (1);
for (s2i = str2cts; s2i->si_key; s2i++)
- if (!strcasecmp (ci->ci_type, s2i->si_key))
+ if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
break;
if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
s2i++;
*/
switch (ct->c_type = s2i->si_val) {
case CT_MESSAGE:
- if (!strcasecmp (ci->ci_subtype, "rfc822")) {
+ if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
ct->c_encoding = CE_7BIT;
goto call_init;
}
/* check directive against the list of MIME types */
for (s2i = str2cts; s2i->si_key; s2i++)
- if (!strcasecmp (ci->ci_type, s2i->si_key))
+ if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
break;
/*
/* NOTREACHED */
case CT_MESSAGE:
- if (!strcasecmp (ci->ci_subtype, "partial"))
+ if (!mh_strcasecmp (ci->ci_subtype, "partial"))
adios (NULL, "sorry, \"#%s/%s\" isn't supported",
ci->ci_type, ci->ci_subtype);
- if (!strcasecmp (ci->ci_subtype, "external-body"))
+ if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
ci->ci_type, ci->ci_subtype);
use_forw:
* Message directive
* #forw [+folder] [msgs]
*/
- if (!strcasecmp (ci->ci_type, "forw")) {
+ if (!mh_strcasecmp (ci->ci_type, "forw")) {
int msgnum;
char *folder, *arguments[MAXARGS];
struct msgs *mp;
if (folder)
adios (NULL, "only one folder per #forw directive");
else
- folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
+ folder = pluspath (cp);
}
}
/*
* #end
*/
- if (!strcasecmp (ci->ci_type, "end")) {
+ if (!mh_strcasecmp (ci->ci_type, "end")) {
free_content (ct);
*ctp = NULL;
return DONE;
/*
* #begin [ alternative | parallel ]
*/
- if (!strcasecmp (ci->ci_type, "begin")) {
+ if (!mh_strcasecmp (ci->ci_type, "begin")) {
if (!ci->ci_magic) {
vrsn = MULTI_MIXED;
cp = SubMultiPart[vrsn - 1].kv_key;
- } else if (!strcasecmp (ci->ci_magic, "alternative")) {
+ } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
vrsn = MULTI_ALTERNATE;
cp = SubMultiPart[vrsn - 1].kv_key;
- } else if (!strcasecmp (ci->ci_magic, "parallel")) {
+ } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
vrsn = MULTI_PARALLEL;
cp = SubMultiPart[vrsn - 1].kv_key;
} else if (uprf (ci->ci_magic, "digest")) {
int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary */
int checklinespace, linespace = 0; /* check if any line ends with space */
int checkebcdic, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
- char *cp, buffer[BUFSIZ];
+ unsigned char *cp, buffer[BUFSIZ];
struct text *t;
FILE *in;
CE ce = ct->c_cefile;
/*
* Skip the output of Content-Type, parameters, content
- * description, and Content-ID if the content is of type
- * "message" and the rfc934 compatibility flag is set
- * (which means we are inside multipart/digest and the
- * switch -rfc934mode was given).
+ * description and disposition, and Content-ID if the
+ * content is of type "message" and the rfc934 compatibility
+ * flag is set (which means we are inside multipart/digest
+ * and the switch -rfc934mode was given).
*/
if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
goto skip_headers;
* the end of the Content-Type line.
*/
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (mailbody && !strcasecmp (*ap, "body"))
+ if (mailbody && !mh_strcasecmp (*ap, "body"))
continue;
vp = add (";", vp);
add_header (ct, np, vp);
/*
- * output the Content-ID
+ * output the Content-ID, unless disabled by -nocontentid
*/
- if (ct->c_id) {
+ if (contentidsw && ct->c_id) {
np = add (ID_FIELD, NULL);
vp = concat (" ", ct->c_id, NULL);
add_header (ct, np, vp);
add_header (ct, np, vp);
}
+ /*
+ * output the Content-Disposition
+ */
+ if (ct->c_dispo) {
+ np = add (DISPO_FIELD, NULL);
+ vp = concat (" ", ct->c_dispo, NULL);
+ add_header (ct, np, vp);
+ }
+
skip_headers:
/*
* If this is the internal content structure for a
return OK;
}
+
+
+/* Make sure that buf contains at least one appearance of name,
+ followed by =. If not, insert both name and value, just after
+ first semicolon, if any. Note that name should not contain a
+ trailing =. And quotes will be added around the value. Typical
+ usage: make sure that a Content-Disposition header contains
+ filename="foo". If it doesn't and value does, use value from
+ that. */
+static char *
+incl_name_value (unsigned char *buf, char *name, char *value) {
+ char *newbuf = buf;
+
+ /* Assume that name is non-null. */
+ if (buf && value) {
+ char *name_plus_equal = concat (name, "=", NULL);
+
+ if (! strstr (buf, name_plus_equal)) {
+ char *insertion;
+ unsigned char *cp;
+ char *prefix, *suffix;
+
+ /* Trim trailing space, esp. newline. */
+ for (cp = &buf[strlen (buf) - 1];
+ cp >= buf && isspace (*cp);
+ --cp) {
+ *cp = '\0';
+ }
+
+ insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
+
+ /* Insert at first semicolon, if any. If none, append to
+ end. */
+ prefix = add (buf, NULL);
+ if ((cp = strchr (prefix, ';'))) {
+ suffix = concat (cp, NULL);
+ *cp = '\0';
+ newbuf = concat (prefix, insertion, suffix, "\n", NULL);
+ free (suffix);
+ } else {
+ /* Append to end. */
+ newbuf = concat (buf, insertion, "\n", NULL);
+ }
+
+ free (prefix);
+ free (insertion);
+ free (buf);
+ }
+
+ free (name_plus_equal);
+ }
+
+ return newbuf;
+}
+
+
+/* Extract just name_suffix="foo", if any, from value. If there isn't
+ one, return the entire value. Note that, for example, a name_suffix
+ of name will match filename="foo", and return foo. */
+static char *
+extract_name_value (char *name_suffix, char *value) {
+ char *extracted_name_value = value;
+ char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
+ char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
+ char *cp;
+
+ free (name_suffix_plus_quote);
+ if (name_suffix_equals) {
+ char *name_suffix_begin;
+
+ /* Find first \". */
+ for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
+ name_suffix_begin = ++cp;
+ /* Find second \". */
+ for (; *cp != '"'; ++cp) /* empty */;
+
+ extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
+ memcpy (extracted_name_value,
+ name_suffix_begin,
+ cp - name_suffix_begin);
+ extracted_name_value[cp - name_suffix_begin] = '\0';
+ }
+
+ return extracted_name_value;
+}