type=tar; \\
conversions=compress \\
[this is the nmh distribution] \\
+ {application; filename="nmh.tar.gz"} \\
name="nmh.tar.gz"; \\
directory="/pub/nmh"; \\
site="ftp.math.gatech.edu"; \\
You must give a description string to separate the content parameters
from the external-parameters (although this string may be empty).
This description string is specified by enclosing it within
-\*(lq[]\*(rq.
+\*(lq[]\*(rq. A disposition string, to appear in a
+Content-Disposition header, may appear in the optional \*(lq{}\*(rq.
.PP
These parameters are of the form:
.PP
.fi
.RE
.PP
+Similarly, a disposition string may optionally be provided between
+\*(lq{\*(rq and \*(lq}\*(rq characters; it will be copied into the
+\*(lqContent-Disposition\*(rq header when the directive is processed.
+If a disposition string is provided that does not contain a filename
+parameter, and a filename is provided in the directive, it will be
+added to the \*(lqContent-Disposition\*(rq header. For example, the
+following directive:
+.PP
+.RS 5
+.nf
+#text/plain; charset=iso-8859-1 <>{attachment} /tmp/summary.txt
+.fi
+.RE
+.PP
+creates these message part headers:
+.PP
+.RS 5
+.nf
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Disposition: attachment; filename="summary.txt"
+.fi
+.RE
+.PP
By default,
.B mhbuild
will generate a unique \*(lqContent-ID:\*(rq for each directive,
[ "(" comment ")" ]
[ "<" id ">" ]
[ "[" description "]" ]
+ [ "{" disposition "}" ]
[ filename ]
EOL
[ "(" comment ")" ]
[ "<" id ">" ]
[ "[" description "]" ]
+ [ "{" disposition "}" ]
external-parameters
EOL
| "#forw"
[ "<" id ">" ]
[ "[" description "]" ]
+ [ "{" disposition "}" ]
[ "+"folder ] [ 0*msg ]
EOL
| "#begin"
[ "<" id ">" ]
[ "[" description "]" ]
+ [ "{" disposition "}" ]
[ "alternative"
| "parallel"
| something-else ]
0*(";" attribute "=" value)
[ "(" comment ")" ]
[ "[" description "]" ]
+ [ "{" disposition "}" ]
EOL
1*line
[ "#" EOL ]
static int build_headers (CT);
static char *calculate_digest (CT, int);
static int readDigest (CT, char *);
+static char *incl_name_value (char *, char *, char *);
+static char *extract_name_value (char *, char *);
/*
* Structures for mapping (content) types to
goto got_header;
}
+ /* Get Content-Disposition field */
+ if (!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;
}
/*
+ * 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)",
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 (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);
/*
* 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;
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, append both name and value. Note that name
+ should not contain a trailing =. And quotes will be added around
+ the value. Typical usage: make sure that a Content-Disposition
+ header contains filename="foo". If it doesn't and value does, use
+ value from that. */
+static char *
+incl_name_value (char *buf, char *name, char *value) {
+ char *newbuf = buf;
+
+ /* Assume that name is non-null. */
+ if (buf && value) {
+ char *name_plus_equal = concat (name, "=", NULL);
+
+ if (! strstr (buf, name_plus_equal)) {
+ char *appendage;
+ char *cp;
+
+ /* Trim trailing space, esp. newline. */
+ for (cp = &buf[strlen (buf) - 1]; cp >= buf && isspace (*cp); --cp) {
+ *cp = '\0';
+ }
+
+ appendage = concat ("; ", name, "=", "\"", value, "\"\n", NULL);
+ newbuf = add (appendage, buf);
+ free (appendage);
+ }
+
+ 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;
+}