From: David Levine Date: Thu, 21 Mar 2013 02:48:57 +0000 (-0500) Subject: Added support for Content-Disposition header (RFC 2183) to X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/afb68522438886ed22cbc1433da165db74d5baa1?ds=sidebyside;hp=--cc Added support for Content-Disposition header (RFC 2183) to mhstore(1) and mhn(1) when used with -auto. --- afb68522438886ed22cbc1433da165db74d5baa1 diff --git a/docs/TODO b/docs/TODO index 61dfbaa9..75379cd5 100644 --- a/docs/TODO +++ b/docs/TODO @@ -110,7 +110,6 @@ MHN/MHSHOW/MHLIST/MHSTORE handling type, such as multipart/mixed. * add way so user can tell mhn to use a certain `proc' such as moreproc, for certain content types. -* add support for Content-Disposition header (rfc1806, rfc2183). * merge the two places in which mhshowsbr.c reads display strings. * when storing to a folder, should we save the folder context diff --git a/docs/pending-release-notes b/docs/pending-release-notes index 75377dc3..899318ef 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -48,6 +48,8 @@ NEW FEATURES - A new program, mhfixmsg(1), is included to rewrite MIME messages with various transformations. - Added -[no]rmmproc switches to rmm(1). +- Added support for Content-Disposition header (RFC 2183) to mhstore(1) + and mhn(1) when used with -auto. ---------------------------- OBSOLETE/DEPRECATED FEATURES diff --git a/h/mhparse.h b/h/mhparse.h index e8e7be88..1f8ab719 100644 --- a/h/mhparse.h +++ b/h/mhparse.h @@ -286,5 +286,6 @@ char *ct_subtype_str (int, int); const struct str2init *get_ct_init (int); const char *ce_str (int); const struct str2init *get_ce_method (const char *); +int parse_header_attrs (const char *, int, char **, CI, int *); extern int checksw; /* Add Content-MD5 field */ diff --git a/man/mhstore.man b/man/mhstore.man index ccf1ee9c..9216bf66 100644 --- a/man/mhstore.man +++ b/man/mhstore.man @@ -1,4 +1,4 @@ -.TH MHSTORE %manext1% "September 15, 2012" "%nmhversion%" +.TH MHSTORE %manext1% "March 20, 2013" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -157,15 +157,15 @@ If the .B \-auto switch is given, then .B mhstore -will check if -the message contains information indicating the filename that should -be used to store the content. This information should be specified -as the attribute \*(lqname=filename\*(rq in the \*(lqContent-Type\*(rq header -for the content you are storing. For security reasons, this filename -will be ignored if it begins with the character '/', '.', '|', or '!', -or if it contains the character '%'. For the sake of security, -this switch is not the default, and it is recommended that you do -NOT put the +will check if the message contains information indicating the filename +that should be used to store the content. This information should be +specified as the \*(lqfilename\*(rq attribute in the +\*(lqContent-Disposition\*(rq header or as the \*(lqname\*(rq +attribute in the \*(lqContent-Type\*(rq header for the content you are +storing. For security reasons, this filename will be ignored if it +begins with the character '/', '.', '|', or '!', or if it contains the +character '%'. For the sake of security, this switch is not the +default, and it is recommended that you do NOT put the .B \-auto switch in your .I \&.mh\(ruprofile diff --git a/test/mhstore/test-mhstore b/test/mhstore/test-mhstore index 3dde1ca4..d509abe5 100755 --- a/test/mhstore/test-mhstore +++ b/test/mhstore/test-mhstore @@ -169,6 +169,14 @@ run_test 'mhstore last -part 4 -verbose -noverbose' \ 'storing message 11 part 4 as file 11.4.txt' check $expected 11.4.txt +# check that -auto obeys Content-Disposition header +cat > $expected < $expected <c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) goto out; for (dp = cp; istoken (*dp); dp++) @@ -480,7 +481,8 @@ get_content (FILE *in, char *file, int toplevel) if (debugsw) fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { + if (*cp == '(' && + get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) { free (ep); goto out; } @@ -670,9 +672,10 @@ int get_ctinfo (char *cp, CT ct, int magic) { int i; - char *dp, **ap, **ep; + char *dp; char c; CI ci; + int status; ci = &ct->c_ctinfo; i = strlen (invo_name) + 2; @@ -696,7 +699,7 @@ get_ctinfo (char *cp, CT ct, int magic) if (debugsw) fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -719,7 +722,7 @@ get_ctinfo (char *cp, CT ct, int magic) while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; if (*cp != '/') { @@ -732,7 +735,7 @@ get_ctinfo (char *cp, CT ct, int magic) while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; for (dp = cp; istoken (*dp); dp++) @@ -757,103 +760,11 @@ magic_skip: while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) return NOTOK; - /* - * Parse attribute/value pairs given with Content-Type - */ - ep = (ap = ci->ci_attrs) + NPARMS; - while (*cp == ';') { - char *vp, *up; - - if (ap >= ep) { - advise (NULL, - "too many parameters in message %s's %s: field (%d max)", - ct->c_file, TYPE_FIELD, NPARMS); - return NOTOK; - } - - cp++; - while (isspace ((unsigned char) *cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - if (*cp == 0) { - advise (NULL, - "extraneous trailing ';' in message %s's %s: parameter list", - ct->c_file, TYPE_FIELD); - return OK; - } - - /* down case the attribute name */ - for (dp = cp; istoken ((unsigned char) *dp); dp++) - if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) - *dp = tolower ((unsigned char) *dp); - - for (up = dp; isspace ((unsigned char) *dp);) - dp++; - if (dp == cp || *dp != '=') { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)", - ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp); - return NOTOK; - } - - vp = (*ap = add (cp, NULL)) + (up - cp); - *vp = '\0'; - for (dp++; isspace ((unsigned char) *dp);) - dp++; - - /* now add the attribute value */ - ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); - - if (*dp == '"') { - for (cp = ++dp, dp = vp;;) { - switch (c = *cp++) { - case '\0': -bad_quote: - advise (NULL, - "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - - case '\\': - *dp++ = c; - if ((c = *cp++) == '\0') - goto bad_quote; - /* else fall... */ - - default: - *dp++ = c; - continue; - - case '"': - *dp = '\0'; - break; - } - break; - } - } else { - for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) - continue; - *dp = '\0'; - } - if (!*vp) { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - } - ap++; - - while (isspace ((unsigned char) *cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; + if (parse_header_attrs (ct->c_file, i, &cp, ci, &status) == NOTOK) { + return status; } /* @@ -966,14 +877,12 @@ bad_quote: static int -get_comment (CT ct, char **ap, int istype) +get_comment (const char *filename, CI ci, char **ap, int istype) { int i; char *bp, *cp; char c, buffer[BUFSIZ], *dp; - CI ci; - ci = &ct->c_ctinfo; cp = *ap; bp = buffer; cp++; @@ -983,7 +892,7 @@ get_comment (CT ct, char **ap, int istype) case '\0': invalid: advise (NULL, "invalid comment in message %s's %s: field", - ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); + filename, istype ? TYPE_FIELD : VRSN_FIELD); return NOTOK; case '\\': @@ -3249,3 +3158,124 @@ get_ce_method (const char *method) { return NULL; } + +int +parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci, + int *status) { + char **attr = ci->ci_attrs; + char *cp = *header_attrp; + + while (*cp == ';') { + char *dp, *vp, *up, c; + + /* Relies on knowledge of this declaration: + * char *ci_attrs[NPARMS + 2]; + */ + if (attr >= ci->ci_attrs + sizeof ci->ci_attrs/sizeof (char *) - 2) { + advise (NULL, + "too many parameters in message %s's %s: field (%d max)", + filename, TYPE_FIELD, NPARMS); + *status = NOTOK; + return NOTOK; + } + + cp++; + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, ci, &cp, 1) == NOTOK) { + *status = NOTOK; + return NOTOK; + } + + if (*cp == 0) { + advise (NULL, + "extraneous trailing ';' in message %s's %s: " + "parameter list", + filename, TYPE_FIELD); + *status = OK; + return NOTOK; + } + + /* down case the attribute name */ + for (dp = cp; istoken ((unsigned char) *dp); dp++) + if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp)) + *dp = tolower ((unsigned char) *dp); + + for (up = dp; isspace ((unsigned char) *dp);) + dp++; + if (dp == cp || *dp != '=') { + advise (NULL, + "invalid parameter in message %s's %s: " + "field\n%*.*sparameter %s (error detected at offset %d)", + filename, TYPE_FIELD, len, len, "", cp, dp - cp); + *status = NOTOK; + return NOTOK; + } + + vp = (*attr = add (cp, NULL)) + (up - cp); + *vp = '\0'; + for (dp++; isspace ((unsigned char) *dp);) + dp++; + + /* Now store the attribute value. */ + ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (dp - cp); + + if (*dp == '"') { + for (cp = ++dp, dp = vp;;) { + switch (c = *cp++) { + case '\0': +bad_quote: + advise (NULL, + "invalid quoted-string in message %s's %s: " + "field\n%*.*s(parameter %s)", + filename, TYPE_FIELD, len, len, "", *attr); + *status = NOTOK; + return NOTOK; + + case '\\': + *dp++ = c; + if ((c = *cp++) == '\0') + goto bad_quote; + /* else fall... */ + + default: + *dp++ = c; + continue; + + case '"': + *dp = '\0'; + break; + } + break; + } + } else { + for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) + continue; + *dp = '\0'; + } + if (!*vp) { + advise (NULL, + "invalid parameter in message %s's %s: " + "field\n%*.*s(parameter %s)", + filename, TYPE_FIELD, len, len, "", *attr); + *status = NOTOK; + return NOTOK; + } + + while (isspace ((unsigned char) *cp)) + cp++; + + if (*cp == '(' && + get_comment (filename, ci, &cp, 1) == NOTOK) { + *status = NOTOK; + return NOTOK; + } + + ++attr; + } + + *header_attrp = cp; + return OK; +} diff --git a/uip/mhstoresbr.c b/uip/mhstoresbr.c index b978dbfb..56214167 100644 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@ -1021,7 +1021,7 @@ static void get_storeproc (CT ct) { char **ap, **ep, *cp; - CI ci = &ct->c_ctinfo; + CI ci; /* * If the storeproc has already been defined, @@ -1031,13 +1031,44 @@ get_storeproc (CT ct) if (ct->c_storeproc) return; + /* + * If there's a Content-Disposition header and it has a filename, + * use that (RFC-2183). + */ + if (ct->c_dispo) { + char *cp = strchr (ct->c_dispo, ';'); + CI ci = calloc (1, sizeof *ci); + int status; + int found_filename = 0; + + if (cp && parse_header_attrs (ct->c_file, strlen (invo_name) + 2, &cp, + ci, &status) == OK) { + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (! strcasecmp (*ap, "filename") + && *(cp = *ep) != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { + ct->c_storeproc = add (cp, NULL); + found_filename = 1; + } + free (*ap); + } + } + + free (ci); + if (found_filename) return; + } + /* * Check the attribute/value pairs, for the attribute "name". * If found, do a few sanity checks and copy the value into * the storeproc. */ + ci = &ct->c_ctinfo; for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!mh_strcasecmp (*ap, "name") + if (! strcasecmp (*ap, "name") && *(cp = *ep) != '/' && *cp != '.' && *cp != '|'