From: Ken Hornstein Date: Thu, 6 Mar 2014 04:17:17 +0000 (-0500) Subject: Merge branch 'extended-params' X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/28c3595a77a8c942bee1057085776dad0b3d53f4?hp=-c Merge branch 'extended-params' --- 28c3595a77a8c942bee1057085776dad0b3d53f4 diff --combined Makefile.am index 587dfb84,d9b3229f..a73a4215 --- a/Makefile.am +++ b/Makefile.am @@@ -38,7 -38,6 +38,7 @@@ TESTS_ENVIRONMENT = MH_OBJ_DIR="@abs_bu auxexecdir="$(auxexecdir)" bindir="$(bindir)" \ mandir="$(mandir)" sysconfdir="$(sysconfdir)" \ supported_locks="$(supported_locks)" \ + default_locking="${default_locking}" \ MULTIBYTE_ENABLED=$(MULTIBYTE_ENABLED) \ ICONV_ENABLED=$(ICONV_ENABLED) \ $(TESTS_SHELL) ## Keep at end of TESTS_ENVIRONMENT. @@@ -68,16 -67,18 +68,18 @@@ TESTS = test/ali/test-ali test/anno/tes test/manpages/test-manpages \ test/mhbuild/test-attach \ test/mhbuild/test-cte \ + test/mhbuild/test-ext-params \ test/mhbuild/test-forw test/mhbuild/test-header-encode \ test/mhbuild/test-utf8-body \ test/mhfixmsg/test-mhfixmsg \ - test/mhlist/test-mhlist test/mhmail/test-mhmail \ + test/mhlist/test-mhlist test/mhlist/test-ext-params \ + test/mhmail/test-mhmail \ test/mhparam/test-mhparam test/mhpath/test-mhpath \ - test/mhshow/test-charset \ + test/mhshow/test-charset test/mhshow/test-textcharset \ test/mhshow/test-cte-binary test/mhshow/test-qp \ test/mhshow/test-subpart test/mhshow/test-msg-buffer-boundaries \ - test/mhstore/test-mhstore test/new/test-basic \ - test/pick/test-pick test/pick/test-stderr \ + test/mhstore/test-mhstore test/mkstemp/test-mkstemp \ + test/new/test-basic test/pick/test-pick test/pick/test-stderr \ test/post/test-post-aliases test/post/test-post-basic \ test/post/test-post-multiple test/post/test-post-bcc \ test/post/test-post-dcc test/post/test-post-fcc \ @@@ -150,9 -151,9 +152,9 @@@ bin_SCRIPTS = uip/mhmail etc/sendfile ## ## This is all programs that get installed in the "lib" directory ## -auxexec_PROGRAMS = uip/ap uip/conflict uip/dp uip/fmtdump uip/mhl uip/post \ - uip/rcvdist uip/rcvpack uip/rcvstore uip/rcvtty uip/slocal \ - uip/viamail uip/mhtest +auxexec_PROGRAMS = uip/ap uip/conflict uip/dp uip/fmtdump uip/mhl uip/mkstemp \ + uip/post uip/rcvdist uip/rcvpack uip/rcvstore uip/rcvtty \ + uip/slocal uip/viamail uip/mhtest auxexec_SCRIPTS = uip/spost @@@ -228,8 -229,8 +230,8 @@@ man_MANS = man/ali.1 man/anno.1 man/ap. man/mh-format.5 man/mh-mail.5 man/mh-profile.5 man/mh_profile.5 \ man/mh-sequence.5 man/mh-tailor.5 man/mhbuild.1 man/mhfixmsg.1 \ man/mhl.1 man/mhlist.1 man/mhmail.1 man/mhn.1 man/mhparam.1 \ - man/mhpath.1 man/mhshow.1 man/mhstore.1 man/msgchk.1 man/msh.1 \ - man/mts.conf.5 man/new.1 man/next.1 man/nmh.7 man/packf.1 \ + man/mhpath.1 man/mhshow.1 man/mhstore.1 man/mkstemp.1 man/msgchk.1 \ + man/msh.1 man/mts.conf.5 man/new.1 man/next.1 man/nmh.7 man/packf.1 \ man/pick.1 man/post.8 man/prev.1 man/prompter.1 man/rcvdist.1 \ man/rcvpack.1 man/rcvstore.1 man/rcvtty.1 man/refile.1 \ man/repl.1 man/rmf.1 man/rmm.1 man/scan.1 man/send.1 \ @@@ -249,8 -250,8 +251,8 @@@ man_SRCS = man/ali.man man/anno.man man man/mh_profile.man man/mh-sequence.man man/mh-tailor.man \ man/mhbuild.man man/mhfixmsg.man man/mhl.man man/mhlist.man \ man/mhmail.man man/mhn.man man/mhparam.man man/mhpath.man \ - man/mhshow.man man/mhstore.man man/msgchk.man man/msh.man \ - man/mts.conf.man man/new.man man/next.man man/nmh.man \ + man/mhshow.man man/mhstore.man man/mkstemp.man man/msgchk.man \ + man/msh.man man/mts.conf.man man/new.man man/next.man man/nmh.man \ man/packf.man man/pick.man man/post.man man/prev.man \ man/prompter.man man/rcvdist.man man/rcvpack.man \ man/rcvstore.man man/rcvtty.man man/refile.man man/repl.man \ @@@ -289,7 -290,7 +291,7 @@@ uip_anno_LDADD = $(LDADD) $(POSTLINK uip_burst_SOURCES = uip/burst.c uip/mhparse.c uip/mhmisc.c uip/mhfree.c \ uip/mhcachesbr.c uip/md5.c - uip_burst_LDADD = $(LDADD) $(POSTLINK) + uip_burst_LDADD = $(LDADD) $(ICONVLIB) $(POSTLINK) uip_comp_SOURCES = uip/comp.c uip/whatnowproc.c uip/whatnowsbr.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c @@@ -321,7 -322,7 +323,7 @@@ uip_mark_LDADD = $(LDADD) $(POSTLINK uip_mhbuild_SOURCES = uip/mhbuild.c uip/mhbuildsbr.c uip/mhcachesbr.c \ uip/mhlistsbr.c uip/mhoutsbr.c uip/mhmisc.c \ uip/mhfree.c uip/mhparse.c uip/md5.c - uip_mhbuild_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) + uip_mhbuild_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_mhfixmsg_SOURCES = uip/mhfixmsg.c uip/mhparse.c uip/mhcachesbr.c \ uip/mhoutsbr.c uip/mhmisc.c uip/mhfree.c \ @@@ -330,7 -331,7 +332,7 @@@ uip_mhfixmsg_LDADD = $(LDADD) $(TERMLIB uip_mhlist_SOURCES = uip/mhlist.c uip/mhparse.c uip/mhcachesbr.c \ uip/mhlistsbr.c uip/mhmisc.c uip/mhfree.c uip/md5.c - uip_mhlist_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) + uip_mhlist_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_mhn_SOURCES = uip/mhn.c uip/mhparse.c uip/mhcachesbr.c uip/mhshowsbr.c \ uip/mhlistsbr.c uip/mhstoresbr.c uip/mhmisc.c uip/mhfree.c \ @@@ -430,11 -431,8 +432,11 @@@ uip_mhl_LDADD = $(LDADD) $(TERMLIB) $(I uip_mhtest_SOURCES = uip/mhtest.c uip/mhparse.c uip/mhcachesbr.c \ uip/mhoutsbr.c uip/mhmisc.c uip/mhfree.c \ uip/md5.c - uip_mhtest_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) + uip_mhtest_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) +uip_mkstemp_SOURCES = uip/mkstemp.c +uip_mkstemp_LDADD = $(LDADD) $(POSTLINK) + uip_post_SOURCES = uip/post.c uip/aliasbr.c uip_post_LDADD = mts/libmts.a $(LDADD) $(SASLLIB) $(TLSLIB) $(POSTLINK) @@@ -453,7 -451,7 +455,7 @@@ uip_rcvtty_LDADD = $(LDADD) $(TERMLIB) uip_slocal_SOURCES = uip/slocal.c uip/aliasbr.c uip/dropsbr.c uip_slocal_LDADD = $(LDADD) $(NDBM_LIBS) $(POSTLINK) - uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/mhoutsbr.c uip/sendsbr.c \ + uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/sendsbr.c \ uip/annosbr.c uip/distsbr.c uip_viamail_LDADD = $(LDADD) $(POSTLINK) diff --combined docs/pending-release-notes index a4db0a16,1b90141d..780b2abe --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@@ -74,10 -74,8 +74,12 @@@ NEW FEATURE Nmh-Attachment). - The default Content-Transfer-Encoding for text parts is now 8bit. - mhbuild(1) now supports a selectable Content-Transfer-Encoding +- If nmh was configured with iconv(3) support, mhshow will convert, if + necessary, the charset of text/plain content to match the user's + locale setting. +- Added support for %{charset} display string escape to mhshow(1). + - The MIME parsing and generating routines now support RFC 2231 extended + parameter information. ----------------- OBSOLETE FEATURES @@@ -127,8 -125,6 +129,8 @@@ {MHTMPDIR environment variable, TMPDIR environment variable, MH Path}. They are no longer be stored in the location specified by the TMP environment variable. +- Instead of printing PostScript attachments, by default, from mhshow, + try to find a suitable viewer. ------------------- DEPRECATED FEATURES @@@ -170,7 -166,6 +172,7 @@@ BUG FIXE $HOME/.netrc. - Added quoting of local part of invalid address in message being replied to [Bug #26780]. -- Fix segmentation fauls for %(putlit) and %(zputlit) format escapes when +- Fix segmentation faults for %(putlit) and %(zputlit) format escapes when the "str" register was NULL. - Encode and decode text MIME types with canonical line breaks properly. +- mhstore(1) now obeys its -noverbose switch. diff --combined h/mhparse.h index 72188c6d,9b058e68..7ade8a3c --- a/h/mhparse.h +++ b/h/mhparse.h @@@ -16,9 -16,10 +16,10 @@@ typedef struct hfield *HF /* * Abstract types for MIME parsing/building */ - typedef struct cefile *CE; - typedef struct CTinfo *CI; - typedef struct Content *CT; + typedef struct cefile *CE; + typedef struct CTinfo *CI; + typedef struct Content *CT; + typedef struct Parameter *PM; /* * type for Init function (both type and transfer encoding) @@@ -43,6 -44,17 +44,17 @@@ struct hfield HF next; /* link to next header field */ }; + /* + * Structure for holding MIME parameter elements. + */ + struct Parameter { + char *pm_name; /* Parameter name */ + char *pm_value; /* Parameter value */ + char *pm_charset; /* Parameter character set (optional) */ + char *pm_lang; /* Parameter language tag (optional) */ + PM pm_next; /* Pointer to next element */ + }; + /* * Structure for storing parsed elements * of the Content-Type component. @@@ -50,8 -62,8 +62,8 @@@ struct CTinfo { char *ci_type; /* content type */ char *ci_subtype; /* content subtype */ - char *ci_attrs[NPARMS + 2]; /* attribute names */ - char *ci_values[NPARMS]; /* attribute values */ + PM ci_first_pm; /* Pointer to first MIME parameter */ + PM ci_last_pm; /* Pointer to last MIME parameter */ char *ci_comment; /* RFC-822 comments */ char *ci_magic; }; @@@ -99,6 -111,9 +111,9 @@@ struct Content char *c_id; /* Content-ID: */ char *c_descr; /* Content-Description: */ char *c_dispo; /* Content-Disposition: */ + char *c_dispo_type; /* Type of Content-Disposition */ + PM c_dispo_first; /* Pointer to first disposition parm */ + PM c_dispo_last; /* Pointer to last disposition parm */ char *c_partno; /* within multipart content */ /* Content-Type info */ @@@ -307,13 -322,14 +322,14 @@@ CT parse_mime (char *) * CE_QUOTED. * maxunencoded - The maximum line length before the default encoding for * text parts is quoted-printable. + * verbose - If 1, output verbose information during message composition * * Returns a CT structure describing the resulting MIME message. If the * -auto flag is set and a MIME-Version header is encountered, the return * value is NULL. */ CT build_mime (char *infile, int autobuild, int dist, int directives, - int encoding, size_t maxunencoded); + int encoding, size_t maxunencoded, int verbose); int add_header (CT, char *, char *); int get_ctinfo (char *, CT, int); @@@ -326,9 -342,125 +342,136 @@@ 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 *); - char *update_attr (char *, const char *, const char *e); +char *content_charset (CT); +int convert_charset (CT, char *, int *); + /* + * Given a list of messages, display information about them on standard + * output. + * + * Argumens are: + * + * cts - An array of CT elements of messages that need to be + * displayed. Array is terminated by a NULL. + * headsw - If 1, display a column header. + * sizesw - If 1, display the size of the part. + * verbosw - If 1, display verbose information + * debugsw - If 1, turn on debugging for the output. + * disposw - If 1, display MIME part disposition information. + * + */ + void list_all_messages(CT *cts, int headsw, int sizesw, int verbosw, + int debugsw, int disposw); + + /* + * List the content information of a single MIME part on stdout. + * + * Arguments are: + * + * ct - MIME Content structure to display. + * toplevel - If set, we're at the top level of a message + * realsize - If set, determine the real size of the content + * verbose - If set, output verbose information + * debug - If set, turn on debugging for the output + * dispo - If set, display MIME part disposition information. + * + * Returns OK on success, NOTOK otherwise. + */ + int list_content(CT ct, int toplevel, int realsize, int verbose, int debug, + int dispo); + + /* + * Display content-appropriate information on MIME parts, decending recursively + * into multipart content if appropriate. Uses list_content() for displaying + * generic information. + * + * Arguments and return value are the same as list_content(). + */ + int list_switch(CT ct, int toplevel, int realsize, int verbose, int debug, + int dispo); + + /* + * Given a linked list of parameters, build an output string for them. This + * string is designed to be concatenated on an already-built header. + * + * Arguments are: + * + * initialwidth - Current width of the header. Used to compute when to wrap + * parameters on the first line. The following lines will + * be prefixed by a tab (\t) character. + * params - Pointer to head of linked list of parameters. + * offsetout - The final line offset after all the parameters have been + * output. May be NULL. + * external - If set, outputting an external-body type and will not + * output a "body" parameter. + + * Returns a pointer to the resulting parameter string. This string must + * be free()'d by the caller. Returns NULL on error. + */ + char *output_params(size_t initialwidth, PM params, int *offsetout, + int external); + + /* + * Add a parameter to the parameter linked list. + * + * Arguments are: + * + * first - Pointer to head of linked list + * last - Pointer to tail of linked list + * name - Name of parameter + * value - Value of parameter + * nocopy - If set, will use the pointer values directly for "name" + * and "value" instead of making their own copy. These + * pointers will be free()'d later by the MIME routines, so + * they should not be used after calling this function! + * + * Returns allocated parameter element + */ + PM add_param(PM *first, PM *last, char *name, char *value, int nocopy); + ++/* ++ * Replace (or add) a parameter to the parameter linked list. ++ * ++ * If the named parameter already exists on the parameter linked list, ++ * replace the value with the new one. Otherwise add it to the linked ++ * list. All parameters are identical to add_param(). ++ */ ++PM replace_param(PM *first, PM *last, char *name, char *value, int nocopy); ++ + /* + * Retrieve a parameter value from a parameter linked list. Convert to the + * local character set if required. + * + * Arguments are: + * + * first - Pointer to head of parameter linked list. + * name - Name of parameter. + * replace - If characters in the parameter list cannot be converted to + * the local character set, replace with this character. + * fetchonly - If true, return pointer to original value, no conversion + * performed. + * + * Returns parameter value if found, NULL otherwise. Memory must be free()'d + * unless fetchonly is set. + */ + + char *get_param(PM first, const char *name, char replace, int fetchonly); + + /* + * Fetch a parameter value from a parameter structure, converting it to + * the local character set. + * + * Arguments are: + * + * pm - Pointer to parameter structure + * replace - If characters in the parameter list cannot be converted to + * the local character set, replace with this character. + * + * Returns a pointer to the parameter value. Memory is stored in an + * internal buffer, so the returned value is only valid until the next + * call to get_param_value() or get_param() (get_param() uses get_param_value() + * internally). + */ + char *get_param_value(PM pm, char replace); + extern int checksw; /* Add Content-MD5 field */ diff --combined h/prototypes.h index 621146c3,03c783aa..f8adbac5 --- a/h/prototypes.h +++ b/h/prototypes.h @@@ -46,7 -46,19 +46,20 @@@ void cpydgst (int, int, char *, char *) char *cpytrim (const char *); int decode_rfc2047 (char *, char *, size_t); void discard (FILE *); +char *upcase (const char *); + + /* + * Decode two characters into their quoted-printable representation. + * + * Arguments are: + * + * byte1 - First character of Q-P representation + * byte2 - Second character of Q-P representation + * + * Returns the decoded value, -1 if the conversion failed. + */ + int decode_qp(unsigned char byte1, unsigned char byte2); + int default_done (int); /* diff --combined sbr/utils.c index 59ab7c72,989a9c8b..4405f4c9 --- a/sbr/utils.c +++ b/sbr/utils.c @@@ -353,15 -353,22 +353,36 @@@ nmh_init(const char *argv0, int read_co } } + +/* Returns copy of argument str with all characters converted to upper + case, and trimmed whitespace (see cpytrim()) . */ +char * +upcase (const char *str) { + char *up = cpytrim (str); + char *cp; + + for (cp = up; *cp; ++cp) { *cp = toupper ((unsigned char) *cp); } + + return up; +} ++ ++ + /* + * Scan for any 8-bit characters. Return 1 if they exist. + * + * Scan up until the given endpoint (but not the actual endpoint itself). + * If the endpoint is NULL, scan until a '\0' is reached. + */ + + int + contains8bit(const char *start, const char *end) + { + if (! start) + return 0; + + while (*start != '\0' && (!end || (start < end))) + if (! isascii((unsigned char) *start++)) + return 1; + + return 0; + } diff --combined test/mhfixmsg/test-mhfixmsg index d3516175,aa8c9514..d60b9d76 --- a/test/mhfixmsg/test-mhfixmsg +++ b/test/mhfixmsg/test-mhfixmsg @@@ -47,7 -47,7 +47,7 @@@ Usage: mhfixmsg [+folder] [msgs] [switc switches are: -decodetext 8bit|7bit -nodecodetext - -[no]textcodeset + -[no]textcharset -[no]reformat -[no]replacetextplain -[no]fixboundary @@@ -567,7 -567,7 +567,7 @@@ MIME-Version: 1. Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" ------- =_aaaaaaaaaa0 -Content-Type: text/plain; charset="iso-8859-1"; name="square.txt" +Content-Type: text/plain; charset="iso-8859-1"; name="nul+square.txt" Content-Transfer-Encoding: base64 AL2yPbwK @@@ -588,7 -588,7 +588,7 @@@ MIME-Version: 1. Content-Type: multipart/mixed; boundary=\"----- =_aaaaaaaaaa0\" ------- =_aaaaaaaaaa0 -Content-Type: text/plain; charset=\"iso-8859-1\"; name=\"square.txt\" +Content-Type: text/plain; charset=\"iso-8859-1\"; name=\"nul+square.txt\" Content-Transfer-Encoding: binary " " @@@ -674,13 -674,13 +674,13 @@@ els fi -# check -textcodeset +# check -textcharset # Also checks preservation of attributes after one (charset) that is # modified. cat >"$expected" <`mhpath new` <"$actual.err" -if grep "mhfixmsg: Can't convert .* to .* without iconv" "$actual.err" \ - >/dev/null; then - echo "$0: skipping -textcodeset check because nmh was built without iconv" - rm -f "$expected" "$actual" "$actual.err" -else +if test "$ICONV_ENABLED" -eq 1; then + run_prog mhfixmsg last -textcharset utf-8 -outfile "$actual" 2>&1 check "$expected" "$actual" - rm "$actual.err" +else + echo "$0: skipping -textcharset check because nmh was built without iconv" + rm -f "$expected" fi -set -e # check -nofixboundary @@@ -770,7 -774,7 +770,7 @@@ f cat >"$expected" < diff --combined uip/mhfixmsg.c index 47c03051,07433377..f00fc97d --- a/uip/mhfixmsg.c +++ b/uip/mhfixmsg.c @@@ -12,12 -12,15 +12,12 @@@ #include #include #include -#ifdef HAVE_ICONV -# include -#endif #define MHFIXMSG_SWITCHES \ X("decodetext 8bit|7bit", 0, DECODETEXTSW) \ X("nodecodetext", 0, NDECODETEXTSW) \ - X("textcodeset", 0, TEXTCODESETSW) \ - X("notextcodeset", 0, NTEXTCODESETSW) \ + X("textcharset", 0, TEXTCHARSETSW) \ + X("notextcharset", 0, NTEXTCHARSETSW) \ X("reformat", 0, REFORMATSW) \ X("noreformat", 0, NREFORMATSW) \ X("replacetextplain", 0, REPLACETEXTPLAINSW) \ @@@ -78,14 -81,15 +78,14 @@@ typedef struct fix_transformations int reformat; int replacetextplain; int decodetext; - char *textcodeset; + char *textcharset; } fix_transformations; int mhfixmsgsbr (CT *, const fix_transformations *, char *); static void reverse_alternative_parts (CT); static int fix_boundary (CT *, int *); static int get_multipart_boundary (CT, char **); --static int replace_boundary (CT, char *, const char *); -static char *update_attr (char *, const char *, const char *e); ++static int replace_boundary (CT, char *, char *); static int fix_multipart_cte (CT, int *); static int set_ce (CT, int); static int ensure_text_plain (CT *, CT, int *, int); @@@ -100,12 -104,15 +100,12 @@@ static int boundary_in_content (FILE ** static void transfer_noncontent_headers (CT, CT); static int set_ct_type (CT, int type, int subtype, int encoding); static int decode_text_parts (CT, int, int *); -static int content_encoding (CT); +static int content_encoding (CT, const char **); static int strip_crs (CT, int *); -static int convert_codesets (CT, char *, int *); -static int convert_codeset (CT, char *, int *); -static char *content_codeset (CT); +static int convert_charsets (CT, char *, int *); static int write_content (CT, char *, char *, int, int); static int remove_file (char *); static void report (char *, char *, char *, ...); -static char *upcase (char *); static void pipeser (int); @@@ -125,7 -132,7 +125,7 @@@ main (int argc, char **argv) fx.reformat = fx.fixcte = fx.fixboundary = 1; fx.replacetextplain = 0; fx.decodetext = CE_8BIT; - fx.textcodeset = NULL; + fx.textcharset = NULL; if (nmh_init(argv[0], 1)) { return 1; } @@@ -169,13 -176,13 +169,13 @@@ case NDECODETEXTSW: fx.decodetext = 0; continue; - case TEXTCODESETSW: + case TEXTCHARSETSW: if (! (cp = *argp++) || (*cp == '-' && cp[1])) adios (NULL, "missing argument to %s", argp[-2]); - fx.textcodeset = cp; + fx.textcharset = cp; continue; - case NTEXTCODESETSW: - fx.textcodeset = 0; + case NTEXTCHARSETSW: + fx.textcharset = 0; continue; case FIXBOUNDARYSW: fx.fixboundary = 1; @@@ -408,8 -415,8 +408,8 @@@ mhfixmsgsbr (CT *ctp, const fix_transfo if (status == OK && fx->decodetext) { status = decode_text_parts (*ctp, fx->decodetext, &message_mods); } - if (status == OK && fx->textcodeset != NULL) { - status = convert_codesets (*ctp, fx->textcodeset, &message_mods); + if (status == OK && fx->textcharset != NULL) { + status = convert_charsets (*ctp, fx->textcharset, &message_mods); } if (! (*ctp)->c_umask) { @@@ -622,7 -629,7 +622,7 @@@ get_multipart_boundary (CT ct, char **p /* Open and copy ct->c_file to file, replacing the multipart boundary. */ static int --replace_boundary (CT ct, char *file, const char *boundary) { ++replace_boundary (CT ct, char *file, char *boundary) { FILE *fpin, *fpout; int compnum, state; char buf[BUFSIZ], name[NAMESZ]; @@@ -668,10 -675,10 +668,22 @@@ if (strcasecmp (TYPE_FIELD, np)) { fprintf (fpout, "%s:%s", np, vp); } else { -- char *new_boundary = update_attr (vp, "boundary=", boundary); -- -- fprintf (fpout, "%s:%s\n", np, new_boundary); -- free (new_boundary); ++ char *new_ctline, *new_params; ++ ++ replace_param(&ct->c_ctinfo.ci_first_pm, ++ &ct->c_ctinfo.ci_last_pm, "boundary", ++ boundary, 0); ++ ++ new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/", ++ ct->c_ctinfo.ci_subtype, NULL); ++ new_params = output_params(strlen(TYPE_FIELD) + ++ strlen(new_ctline) + 1, ++ ct->c_ctinfo.ci_first_pm, NULL, 0); ++ fprintf (fpout, "%s:%s%s\n", np, new_ctline, ++ new_params ? new_params : ""); ++ free(new_ctline); ++ if (new_params) ++ free(new_params); } free (vp); @@@ -711,6 -718,44 +723,6 @@@ } -/* Change the value of a name=value pair in a header field body. - If the name isn't there, append them. In any case, a new - string will be allocated and must be free'd by the caller. - Trims any trailing newlines. */ -static char * -update_attr (char *body, const char *name, const char *value) { - char *bp = nmh_strcasestr (body, name); - char *new_body; - - if (bp) { - char *other_attrs = strchr (bp, ';'); - - *(bp + strlen (name)) = '\0'; - new_body = concat (body, "\"", value, "\"", NULL); - - if (other_attrs) { - char *cp; - - /* Trim any trailing newlines. */ - for (cp = &other_attrs[strlen (other_attrs) - 1]; - cp > other_attrs && *cp == '\n'; - *cp-- = '\0') continue; - new_body = add (other_attrs, new_body); - } - } else { - char *cp; - - /* Append name/value pair, after first removing a final newline - and (extraneous) semicolon. */ - if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0'; - if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0'; - new_body = concat (body, "; ", name, "\"", value, "\"", NULL); - } - - return new_body; -} - - static int fix_multipart_cte (CT ct, int *message_mods) { int status = OK; @@@ -1028,19 -1073,19 +1040,19 @@@ divide_part (CT ct) static void copy_ctinfo (CI dest, CI src) { - char **s_ap, **d_ap, **s_vp, **d_vp; + PM s_pm, d_pm; dest->ci_type = src->ci_type ? add (src->ci_type, NULL) : NULL; dest->ci_subtype = src->ci_subtype ? add (src->ci_subtype, NULL) : NULL; - for (s_ap = src->ci_attrs, d_ap = dest->ci_attrs, - s_vp = src->ci_values, d_vp = dest->ci_values; - *s_ap; - ++s_ap, ++d_ap, ++s_vp, ++d_vp) { - *d_ap = add (*s_ap, NULL); - *d_vp = *s_vp; + for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) { + d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name, + s_pm->pm_value, 0); + if (s_pm->pm_charset) + d_pm->pm_charset = getcpy(s_pm->pm_charset); + if (s_pm->pm_lang) + d_pm->pm_lang = getcpy(s_pm->pm_lang); } - *d_ap = NULL; dest->ci_comment = src->ci_comment ? add (src->ci_comment, NULL) : NULL; dest->ci_magic = src->ci_magic ? add (src->ci_magic, NULL) : NULL; @@@ -1151,11 -1196,11 +1163,11 @@@ reformat_part (CT ct, char *file, char static int charset_encoding (CT ct) { /* norm_charmap() is case sensitive. */ - char *codeset = upcase (content_codeset (ct)); + char *charset = upcase (content_charset (ct)); int encoding = - strcmp (norm_charmap (codeset), "US-ASCII") ? CE_8BIT : CE_7BIT; + strcmp (norm_charmap (charset), "US-ASCII") ? CE_8BIT : CE_7BIT; - free (codeset); + free (charset); return encoding; } @@@ -1169,7 -1214,6 +1181,6 @@@ build_multipart_alt (CT first_alt, CT n CT ct; struct part *p; struct multipart *m; - char *cp; const struct str2init *ctinit; if ((ct = (CT) calloc (1, sizeof *ct)) == NULL) @@@ -1269,23 -1313,8 +1280,8 @@@ ct->c_ctinfo.ci_subtype = add (subtypename, NULL); } - /* - * The boundary indicator string must be stored in a - * c_ctinfo.ci_values slot. We use the first slot because this is - * a new object. ci_attrs[0] can be anything, it is never output. - * It is chosen to be very unlikely to collide with a real - * attribute, to contains text that ci_values[0] can point to, and - * gives traceability back to the origin of this part, though that - * would only be useful in a debugger. - */ - name = concat (" ", typename, "/", subtypename, boundary_indicator, - boundary, NULL); - if ((cp = strstr (name, boundary_indicator))) { - ct->c_ctinfo.ci_attrs[0] = name; - ct->c_ctinfo.ci_attrs[1] = NULL; - /* ci_values don't get free'd, so point into ci_attrs. */ - ct->c_ctinfo.ci_values[0] = cp + strlen (boundary_indicator); - } + add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm, + "boundary", boundary, 0); p = (struct part *) mh_xmalloc (sizeof *p); p->mp_next = (struct part *) mh_xmalloc (sizeof *p->mp_next); @@@ -1439,19 -1468,16 +1435,19 @@@ decode_text_parts (CT ct, int encoding int ct_encoding; if (decode_part (ct) == OK && ct->c_cefile.ce_file) { - if ((ct_encoding = content_encoding (ct)) == CE_BINARY && - encoding != CE_BINARY) { + const char *reason = NULL; + + if ((ct_encoding = content_encoding (ct, &reason)) == CE_BINARY + && encoding != CE_BINARY) { /* The decoding isn't acceptable so discard it. Leave status as OK to allow other transformations. */ if (verbosw) { report (ct->c_partno, ct->c_file, - "will not decode%s because it is binary", + "will not decode%s because it is binary (%s)", ct->c_partno ? "" : ct->c_ctline ? ct->c_ctline - : ""); + : "", + reason); } (void) m_unlink (ct->c_cefile.ce_file); free (ct->c_cefile.ce_file); @@@ -1535,10 -1561,9 +1531,10 @@@ /* See if the decoded content is 7bit, 8bit, or binary. It's binary if it has any NUL characters, a CR not followed by a LF, or lines - greater than 998 characters in length. */ + greater than 998 characters in length. If binary, reason is set + to a string explaining why. */ static int -content_encoding (CT ct) { +content_encoding (CT ct, const char **reason) { CE ce = &ct->c_cefile; int encoding = CE_7BIT; @@@ -1563,16 -1588,6 +1559,16 @@@ if (*cp == '\0' || ++line_len > 998 || (*cp != '\n' && last_char_was_cr)) { encoding = CE_BINARY; + if (*cp == '\0') { + *reason = "null character"; + } else if (line_len > 998) { + *reason = "line length > 998"; + } else if (*cp != '\n' && last_char_was_cr) { + *reason = "CR not followed by LF"; + } else { + /* Should not reach this. */ + *reason = ""; + } break; } else if (*cp == '\n') { line_len = 0; @@@ -1595,18 -1610,18 +1591,18 @@@ static int strip_crs (CT ct, int *message_mods) { /* norm_charmap() is case sensitive. */ - char *codeset = upcase (content_codeset (ct)); + char *charset = upcase (content_charset (ct)); int status = OK; /* Only strip carriage returns if content is ASCII or another - codeset that has the same readily recognizable CR followed by a + charset that has the same readily recognizable CR followed by a LF. We can include UTF-8 here because if the high-order bit of a UTF-8 byte is 0, then it must be a single-byte ASCII character. */ - if (! strcmp (norm_charmap (codeset), "US-ASCII") || - ! strncmp (norm_charmap (codeset), "ISO-8859-", 9) || - ! strncmp (norm_charmap (codeset), "UTF-8", 5) || - ! strncmp (norm_charmap (codeset), "WINDOWS-12", 10)) { + if (! strcmp (norm_charmap (charset), "US-ASCII") || + ! strncmp (norm_charmap (charset), "ISO-8859-", 9) || + ! strncmp (norm_charmap (charset), "UTF-8", 5) || + ! strncmp (norm_charmap (charset), "WINDOWS-12", 10)) { char **file = NULL; FILE **fp = NULL; size_t begin; @@@ -1668,7 -1683,7 +1664,7 @@@ if (has_crs) { int fd; char *stripped_content_file; - char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL); + char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL); if (tempfile == NULL) { adios (NULL, "unable to create temporary file in %s", @@@ -1730,23 -1745,45 +1726,23 @@@ } } - free (codeset); + free (charset); return status; } -char * -content_codeset (CT ct) { - const char *const charset = "charset"; - char *default_codeset = NULL; - CI ctinfo = &ct->c_ctinfo; - char **src_codeset = NULL; - PM pm; - - for (pm = ctinfo->ci_first_pm; pm; pm = pm->pm_next) { - if (! strcasecmp (pm->pm_name, charset)) { - src_codeset = &pm->pm_value; - break; - } - } - - /* RFC 2045, Sec. 5.2: default to us-ascii. */ - if (src_codeset == NULL) { - src_codeset = &default_codeset; - if (*src_codeset == NULL) - *src_codeset = "US-ASCII"; - } - - return *src_codeset; -} - - static int -convert_codesets (CT ct, char *dest_codeset, int *message_mods) { +convert_charsets (CT ct, char *dest_charset, int *message_mods) { int status = OK; switch (ct->c_type) { case CT_TEXT: if (ct->c_subtype == TEXT_PLAIN) { - status = convert_codeset (ct, dest_codeset, message_mods); + status = convert_charset (ct, dest_charset, message_mods); + if (verbosw && status == OK) { + report (ct->c_partno, ct->c_file, "convert %s to %s", + content_charset(ct), dest_charset); + } } break; @@@ -1758,7 -1795,7 +1754,7 @@@ For now, it gets passed along as-is by InitMultiPart(). */ for (part = m->mp_parts; status == OK && part; part = part->mp_next) { status = - convert_codesets (part->mp_part, dest_codeset, message_mods); + convert_charsets (part->mp_part, dest_charset, message_mods); } break; } @@@ -1769,7 -1806,7 +1765,7 @@@ e = (struct exbody *) ct->c_ctparams; status = - convert_codesets (e->eb_content, dest_codeset, message_mods); + convert_charsets (e->eb_content, dest_charset, message_mods); } break; @@@ -1781,6 -1818,161 +1777,6 @@@ } -static int -convert_codeset (CT ct, char *dest_codeset, int *message_mods) { - char *src_codeset = content_codeset (ct); - int status = OK; - - /* norm_charmap() is case sensitive. */ - char *src_codeset_u = upcase (src_codeset); - char *dest_codeset_u = upcase (dest_codeset); - int different_codesets = - strcmp (norm_charmap (src_codeset), norm_charmap (dest_codeset)); - - free (dest_codeset_u); - free (src_codeset_u); - - if (different_codesets) { -#ifdef HAVE_ICONV - iconv_t conv_desc = NULL; - char *dest; - int fd = -1; - char **file = NULL; - FILE **fp = NULL; - size_t begin; - size_t end; - int opened_input_file = 0; - char src_buffer[BUFSIZ]; - HF hf; - char *tempfile; - - if ((conv_desc = iconv_open (dest_codeset, src_codeset)) == - (iconv_t) -1) { - advise (NULL, "Can't convert %s to %s", src_codeset, dest_codeset); - return -1; - } - - if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) { - adios (NULL, "unable to create temporary file in %s", - get_temp_dir()); - } - dest = add (tempfile, NULL); - - if (ct->c_cefile.ce_file) { - file = &ct->c_cefile.ce_file; - fp = &ct->c_cefile.ce_fp; - begin = end = 0; - } else if (ct->c_file) { - file = &ct->c_file; - fp = &ct->c_fp; - begin = (size_t) ct->c_begin; - end = (size_t) ct->c_end; - } /* else no input file: shouldn't happen */ - - if (file && *file && fp) { - if (! *fp) { - if ((*fp = fopen (*file, "r")) == NULL) { - advise (*file, "unable to open for reading"); - status = NOTOK; - } else { - opened_input_file = 1; - } - } - } - - if (fp && *fp) { - size_t inbytes; - size_t bytes_to_read = - end > 0 && end > begin ? end - begin : sizeof src_buffer; - - fseeko (*fp, begin, SEEK_SET); - while ((inbytes = fread (src_buffer, 1, - min (bytes_to_read, sizeof src_buffer), - *fp)) > 0) { - char dest_buffer[BUFSIZ]; - ICONV_CONST char *ib = src_buffer; - char *ob = dest_buffer; - size_t outbytes = sizeof dest_buffer; - size_t outbytes_before = outbytes; - - if (end > 0) bytes_to_read -= inbytes; - - if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) == - (size_t) -1) { - status = NOTOK; - break; - } else { - write (fd, dest_buffer, outbytes_before - outbytes); - } - } - - if (opened_input_file) { - fclose (*fp); - *fp = NULL; - } - } - - iconv_close (conv_desc); - close (fd); - - if (status == OK) { - /* Replace the decoded file with the converted one. */ - if (ct->c_cefile.ce_file) { - if (ct->c_cefile.ce_unlink) { - (void) m_unlink (ct->c_cefile.ce_file); - } - free (ct->c_cefile.ce_file); - } - ct->c_cefile.ce_file = dest; - ct->c_cefile.ce_unlink = 1; - - ++*message_mods; - if (verbosw) { - report (ct->c_partno, ct->c_file, "convert %s to %s", - src_codeset, dest_codeset); - } - - /* Update ci_attrs. */ - src_codeset = dest_codeset; - - /* Update ct->c_ctline. */ - if (ct->c_ctline) { - char *ctline = - update_attr (ct->c_ctline, "charset=", dest_codeset); - - free (ct->c_ctline); - ct->c_ctline = ctline; - } /* else no CT line, which is odd */ - - /* Update Content-Type header field. */ - for (hf = ct->c_first_hf; hf; hf = hf->next) { - if (! strcasecmp (TYPE_FIELD, hf->name)) { - char *ctline_less_newline = - update_attr (hf->value, "charset=", dest_codeset); - char *ctline = concat (ctline_less_newline, "\n", NULL); - free (ctline_less_newline); - - free (hf->value); - hf->value = ctline; - break; - } - } - } else { - (void) m_unlink (dest); - } -#else /* ! HAVE_ICONV */ - NMH_UNUSED (message_mods); - - advise (NULL, "Can't convert %s to %s without iconv", src_codeset, - dest_codeset); - status = NOTOK; -#endif /* ! HAVE_ICONV */ - } - - return status; -} - - static int write_content (CT ct, char *input_filename, char *outfile, int modify_inplace, int message_mods) { @@@ -1894,6 -2086,17 +1890,6 @@@ report (char *partno, char *filename, c } -static char * -upcase (char *str) { - char *up = cpytrim (str); - char *cp; - - for (cp = up; *cp; ++cp) *cp = toupper ((unsigned char) *cp); - - return up; -} - - static void pipeser (int i) { diff --combined uip/mhn.c index 8cd3acdd,220a73d5..c0b7e781 --- a/uip/mhn.c +++ b/uip/mhn.c @@@ -91,6 -91,10 +91,6 @@@ extern int nolist extern int nomore; /* flags for moreproc/header display */ extern char *formsw; -/* mhstoresbr.c */ -extern int autosw; -extern char *cwd; /* cache current working directory */ - /* mhmisc.c */ extern int npart; extern int ntype; @@@ -128,14 -132,8 +128,11 @@@ void flush_errors (void) /* mhshowsbr.c */ void show_all_messages (CT *); - /* mhlistsbr.c */ - void list_all_messages (CT *, int, int, int, int); - /* mhstoresbr.c */ -void store_all_messages (CT *); +typedef struct mhstoreinfo *mhstoreinfo_t; +mhstoreinfo_t mhstoreinfo_create(CT *, char *, const char *, int, int); +void mhstoreinfo_free(mhstoreinfo_t); +void store_all_messages (mhstoreinfo_t); /* mhcachesbr.c */ void cache_all_messages (CT *); @@@ -153,17 -151,15 +150,17 @@@ static void pipeser (int) int main (int argc, char **argv) { - int sizesw = 1, headsw = 1; + int sizesw = 1, headsw = 1, autosw = 0; int msgnum, *icachesw; char *cp, *file = NULL, *folder = NULL; char *maildir, buf[100], **argp; char **arguments; + char *cwd; struct msgs_array msgs = { 0, 0, NULL }; struct msgs *mp = NULL; CT ct, *ctp; FILE *fp; + mhstoreinfo_t info; if (nmh_init(argv[0], 1)) { return 1; } @@@ -403,7 -399,7 +400,7 @@@ do_cache /* * Cache the current directory before we do any chdirs()'s. */ - cwd = getcpy (pwd()); + cwd = add(pwd(), NULL); if (!context_find ("path")) free (path ("./", TFOLDER)); @@@ -557,16 -553,13 +554,16 @@@ * List the message content */ if (listsw) - list_all_messages (cts, headsw, sizesw, verbosw, debugsw); + list_all_messages (cts, headsw, sizesw, verbosw, debugsw, 0); /* * Store the message content */ - if (storesw) - store_all_messages (cts); + if (storesw) { + info = mhstoreinfo_create (cts, cwd, "always", autosw, verbosw);; + store_all_messages (info); + mhstoreinfo_free (info); + } /* * Cache the message content @@@ -584,7 -577,7 +581,7 @@@ for (ctp = cts; *ctp; ctp++) free_content (*ctp); - free ((char *) cts); + free (cts); cts = NULL; /* If reading from a folder, do some updating */ diff --combined uip/mhparse.c index 9873e0b1,943e4233..ec7db7a4 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@@ -16,6 -16,9 +16,9 @@@ #include #include #include + #ifdef HAVE_ICONV + # include + #endif /* HAVE_ICONV */ extern int debugsw; @@@ -109,7 -112,7 +112,7 @@@ void free_encoding (CT, int) * static prototypes */ static CT get_content (FILE *, char *, int); - static int get_comment (const char *, CI, char **, int); + static int get_comment (const char *, const char *, char **, char **); static int InitGeneric (CT); static int InitText (CT); @@@ -135,6 -138,12 +138,12 @@@ static int readDigest (CT, char *) 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 }, @@@ -377,7 -386,7 +386,7 @@@ get_content (FILE *in, char *file, int fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); if (*cp == '(' && - get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) + get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK) goto out; for (dp = cp; istoken (*dp); dp++) @@@ -487,7 -496,7 +496,7 @@@ fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); if (*cp == '(' && - get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) { + get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) { free (ep); goto out; } @@@ -510,7 -519,8 +519,8 @@@ } else if (!strcasecmp (hp->name, DISPO_FIELD)) { /* Get Content-Disposition field */ - ct->c_dispo = add (hp->value, ct->c_dispo); + if (get_dispo(hp->value, ct, 0) == NOTOK) + goto out; } next_header: @@@ -587,88 -597,6 +597,6 @@@ add_header (CT ct, char *name, char *va } - /* 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 (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; - char *cp, *prefix, *suffix; - - /* Trim trailing space, esp. newline. */ - for (cp = &buf[strlen (buf) - 1]; - cp >= buf && isspace ((unsigned char) *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; - } - /* * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition * directives. Fills in the information of the CTinfo structure. @@@ -676,14 -604,12 +604,12 @@@ int get_ctinfo (char *cp, CT ct, int magic) { - int i; 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); @@@ -704,7 -630,8 +630,8 @@@ if (debugsw) fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); - if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &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++) @@@ -727,7 -654,8 +654,8 @@@ while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_comment) == NOTOK) return NOTOK; if (*cp != '/') { @@@ -740,7 -668,8 +668,8 @@@ while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &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++) @@@ -765,11 -694,14 +694,14 @@@ magic_skip while (isspace ((unsigned char) *cp)) cp++; - if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK) + if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp, + &ci->ci_comment) == NOTOK) return NOTOK; - if (parse_header_attrs (ct->c_file, i, &cp, ci, &status) == NOTOK) { - return status; + 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; } /* @@@ -828,7 -760,7 +760,7 @@@ * Get any {Content-Disposition} given in buffer. */ if (magic && *cp == '{') { - ct->c_dispo = ++cp; + ++cp; for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) if (*dp == '}') break; @@@ -840,10 -772,10 +772,10 @@@ c = *dp; *dp = '\0'; - if (*ct->c_dispo) - ct->c_dispo = concat (ct->c_dispo, "\n", NULL); - else - ct->c_dispo = NULL; + + if (get_dispo(cp, ct, 1) != OK) + return NOTOK; + *dp++ = c; cp = dp; @@@ -903,27 -835,98 +835,98 @@@ 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), - '/')); + 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, i, i, "", cp); + "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 (const char *filename, CI ci, char **ap, int istype) + get_comment (const char *filename, const char *fieldname, char **ap, + char **commentp) { int i; char *bp, *cp; @@@ -938,7 -941,7 +941,7 @@@ case '\0': invalid: advise (NULL, "invalid comment in message %s's %s: field", - filename, istype ? TYPE_FIELD : VRSN_FIELD); + filename, fieldname); return NOTOK; case '\\': @@@ -965,12 -968,12 +968,12 @@@ } *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); } } @@@ -1007,7 -1010,8 +1010,8 @@@ InitText (CT ct { char buffer[BUFSIZ]; char *chset = NULL; - char **ap, **ep, *cp; + char *cp; + PM pm; struct k2v *kv; struct text *t; CI ci = &ct->c_ctinfo; @@@ -1028,13 -1032,13 +1032,13 @@@ 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; /* check if content specified a character set */ - if (*ap) { - chset = *ep; + if (pm) { + chset = pm->pm_value; t->tx_charset = CHARSET_SPECIFIED; } else { t->tx_charset = CHARSET_UNSPECIFIED; @@@ -1066,7 -1070,8 +1070,8 @@@ InitMultiPart (CT ct { int inout; long last, pos; - char *cp, *dp, **ap, **ep; + char *cp, *dp; + PM pm; char *bp, buffer[BUFSIZ]; struct multipart *m; struct k2v *kv; @@@ -1111,15 -1116,15 +1116,15 @@@ * 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); @@@ -1324,7 -1329,7 +1329,7 @@@ InitMessage (CT ct case MESSAGE_PARTIAL: { - char **ap, **ep; + PM pm; struct partial *p; if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) @@@ -1332,25 -1337,25 +1337,25 @@@ 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; @@@ -1465,21 -1470,21 +1470,21 @@@ no_body 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; ct->c_ceopenfnx = NULL; - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "access-type")) { + 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; @@@ -1493,46 -1498,46 +1498,46 @@@ 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 (*ap, "url")) { + if (!strcasecmp (pm->pm_name, "url")) { /* * According to RFC 2017, we have to remove all whitespace from * the URL */ - char *u, *p = *ep; - e->eb_url = u = mh_xmalloc(strlen(*ep) + 1); + 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)) @@@ -1542,8 -1547,8 +1547,8 @@@ *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; } } @@@ -2206,28 -2211,18 +2211,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) { @@@ -3231,33 -3226,56 +3226,56 @@@ 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; + /* + * 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, 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; - } + 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, ci, &cp, 1) == NOTOK) { - *status = NOTOK; + get_comment (filename, fieldname, &cp, commentp) == NOTOK) { return NOTOK; } @@@ -3265,9 -3283,8 +3283,8 @@@ advise (NULL, "extraneous trailing ';' in message %s's %s: " "parameter list", - filename, TYPE_FIELD); - *status = OK; - return NOTOK; + filename, fieldname); + return DONE; } /* down case the attribute name */ @@@ -3280,135 -3297,910 +3297,956 @@@ 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; + "field\n%*sparameter %s (error detected at offset %d)", + filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp); return NOTOK; } - vp = (*attr = add (cp, NULL)) + (up - cp); - *vp = '\0'; + /* + * 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++; - /* Now store the attribute value. */ - ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (dp - cp); + 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); - if (*dp == '"') { - for (cp = ++dp, dp = vp;;) { - switch (c = *cp++) { + 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, TYPE_FIELD, len, len, "", *attr); - *status = NOTOK; + "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 '\\': - *dp++ = c; - if ((c = *cp++) == '\0') + if (*++cp == '\0') goto bad_quote; - /* else fall... */ - + /* FALL THROUGH */ default: - *dp++ = c; + len++; continue; + } + break; + } - case '"': - *dp = '\0'; + } 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); + 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); + return NOTOK; } - break; + } + + /* + * 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 { - 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; + 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, ci, &cp, 1) == NOTOK) { - *status = NOTOK; + get_comment (filename, fieldname, &cp, commentp) == NOTOK) { return NOTOK; } + } + + /* + * Now that we're done, reassemble all of the partial parameters. + */ - ++attr; + 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. Return pointer is ++ * only valid until the next call to content_charset(). ++ */ + +char * +content_charset (CT ct) { - const char *const charset = "charset"; - char *default_charset = NULL; - CI ctinfo = &ct->c_ctinfo; - char **ap, **vp; - char **src_charset = NULL; - - for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) { - if (! strcasecmp (*ap, charset)) { - src_charset = vp; - break; - } ++ static char *ret_charset = NULL; ++ ++ if (ret_charset != NULL) { ++ free(ret_charset); + } + - /* RFC 2045, Sec. 5.2: default to us-ascii. */ - if (src_charset == NULL) src_charset = &default_charset; - if (*src_charset == NULL) *src_charset = "US-ASCII"; ++ ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0); + - return *src_charset; ++ return ret_charset ? ret_charset : "US-ASCII"; +} + + - /* Change the value of a name=value pair in a header field body. - If the name isn't there, append them. In any case, a new - string will be allocated and must be free'd by the caller. - Trims any trailing newlines. */ + /* + * 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 * - update_attr (char *body, const char *name, const char *value) { - char *bp = nmh_strcasestr (body, name); - char *new_body; + 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 (bp) { - char *other_attrs = strchr (bp, ';'); + if (strlen(params->pm_name) > CPERLIN) { + advise(NULL, "Parameter name \"%s\" is too long", params->pm_name); + if (paramout) + free(paramout); + return NULL; + } - *(bp + strlen (name)) = '\0'; - new_body = concat (body, "\"", value, "\"", NULL); + curlen = param_len(params, index, valoff, &encode, &cont, &numchars); - if (other_attrs) { - char *cp; + /* + * 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. + */ - /* Trim any trailing newlines. */ - for (cp = &other_attrs[strlen (other_attrs) - 1]; - cp > other_attrs && *cp == '\n'; - *cp-- = '\0') continue; - new_body = add (other_attrs, new_body); - } + 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 (! 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 { - char *cp; + /* + * 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; - /* Append name/value pair, after first removing a final newline - and (extraneous) semicolon. */ - if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0'; - if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0'; - new_body = concat (body, "; ", name, "\"", value, "\"", NULL); + /* + * 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 new_body; ++ 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; + #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; + #endif /* HAVE_ICONV */ + + noiconv: + /* + * 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; } diff --combined uip/mhshowsbr.c index eabf387c,bb0a646e..295f43e8 --- a/uip/mhshowsbr.c +++ b/uip/mhshowsbr.c @@@ -17,9 -17,6 +17,9 @@@ #include #include #include +#ifdef HAVE_ICONV +# include +#endif /* ! HAVE_ICONV */ extern int debugsw; @@@ -44,10 -41,6 +44,6 @@@ int type_ok (CT, int) void content_error (char *, CT, char *, ...); void flush_errors (void); - /* mhlistsbr.c */ - int list_switch (CT, int, int, int, int); - int list_content (CT, int, int, int, int); - /* * prototypes */ @@@ -61,8 -54,7 +57,8 @@@ static void show_single_message (CT, ch static void DisplayMsgHeader (CT, char *); static int show_switch (CT, int, int); static int show_content (CT, int, int); -static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, int); +static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, + int); static int show_text (CT, int, int); static int show_multi (CT, int, int); static int show_multi_internal (CT, int, int); @@@ -70,10 -62,6 +66,9 @@@ static int show_multi_aux (CT, int, int static int show_message_rfc822 (CT, int, int); static int show_partial (CT, int, int); static int show_external (CT, int, int); +static int parse_display_string (CT, char *, int *, int *, int *, int *, char *, + char *, size_t, int multipart); +static int convert_content_charset (CT, char **); - static const char *parameter_value (CI, const char *); static void intrser (int); @@@ -272,13 -260,13 +267,13 @@@ show_content (CT ct, int serial, int al char *cp, buffer[BUFSIZ]; CI ci = &ct->c_ctinfo; - /* Check for mhn-show-type/subtype */ + /* Check for invo_name-show-type/subtype */ snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", invo_name, ci->ci_type, ci->ci_subtype); if ((cp = context_find (buffer)) && *cp != '\0') return show_content_aux (ct, serial, alternate, cp, NULL); - /* Check for mhn-show-type */ + /* Check for invo_name-show-type */ snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); if ((cp = context_find (buffer)) && *cp != '\0') return show_content_aux (ct, serial, alternate, cp, NULL); @@@ -301,9 -289,10 +296,9 @@@ int show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked) { - int fd, len, buflen, quoted; - int xstdin, xlist, xpause, xtty; - char *bp, *pp, *file, buffer[BUFSIZ]; - CI ci = &ct->c_ctinfo; + int fd; + int xstdin = 0, xlist = 0, xpause = 0, xtty = 0; + char *file, buffer[BUFSIZ]; if (!ct->c_ceopenfnx) { if (!alternate) @@@ -317,32 -306,169 +312,32 @@@ return NOTOK; if (ct->c_showproc && !strcmp (ct->c_showproc, "true")) return (alternate ? DONE : OK); - - xlist = 0; - xpause = 0; - xstdin = 0; - xtty = 0; + + if (! strcmp(invo_name, "mhshow") && + ct->c_type == CT_TEXT && ct->c_subtype == TEXT_PLAIN) { + /* This has to be done after calling c_ceopenfnx, so + unfortunately the type checks are necessary without + some code rearrangement. And to make this really ugly, + only do it in mhshow, not mhfixmsg, mhn, or mhstore. */ + if (convert_content_charset (ct, &file) != OK) { + admonish (NULL, "unable to convert character set%s to %s", + ct->c_partno ? "of part " : "", + ct->c_partno ? ct->c_partno : "", + content_charset (ct)); + } + } if (cracked) { strncpy (buffer, cp, sizeof(buffer)); goto got_command; } - /* get buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer) - 1; - bp[0] = bp[buflen] = '\0'; - quoted = 0; - - /* Now parse display string */ - for ( ; *cp && buflen > 0; cp++) { - if (*cp == '%') { - pp = bp; - - switch (*++cp) { - case 'a': - /* insert parameters from Content-Type field */ - { - PM pm; - char *s = ""; - - for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { - snprintf (bp, buflen, "%s%s=\"%s\"", s, pm->pm_name, - get_param_value(pm, '?')); - len = strlen (bp); - bp += len; - buflen -= len; - s = " "; - } - } - break; - - case 'd': - /* insert content description */ - if (ct->c_descr) { - char *s; - - s = trimcpy (ct->c_descr); - strncpy (bp, s, buflen); - free (s); - } - break; - - case 'e': - /* exclusive execution */ - xtty = 1; - break; - - case 'F': - /* %e, %f, and stdin is terminal not content */ - xstdin = 1; - xtty = 1; - /* and fall... */ - - case 'f': - /* insert filename containing content */ - snprintf (bp, buflen, "'%s'", file); - /* since we've quoted the file argument, set things up - * to look past it, to avoid problems with the quoting - * logic below. (I know, I should figure out what's - * broken with the quoting logic, but..) - */ - len = strlen(bp); - buflen -= len; - bp += len; - pp = bp; - break; - - case 'p': - /* %l, and pause prior to displaying content */ - xpause = pausesw; - /* and fall... */ - - case 'l': - /* display listing prior to displaying content */ - xlist = !nolist; - break; - - case 's': - /* insert subtype of content */ - strncpy (bp, ci->ci_subtype, buflen); - break; - - case '%': - /* insert character % */ - goto raw; - - default: - *bp++ = *--cp; - *bp = '\0'; - buflen--; - continue; - } - len = strlen (bp); - bp += len; - buflen -= len; - - /* Did we actually insert something? */ - if (bp != pp) { - /* Insert single quote if not inside quotes already */ - if (!quoted && buflen) { - len = strlen (pp); - memmove (pp + 1, pp, len); - *pp++ = '\''; - buflen--; - bp++; - } - /* Escape existing quotes */ - while ((pp = strchr (pp, '\'')) && buflen > 3) { - len = strlen (pp++); - memmove (pp + 3, pp, len); - *pp++ = '\\'; - *pp++ = '\''; - *pp++ = '\''; - buflen -= 3; - bp += 3; - } - /* If pp is still set, that means we ran out of space. */ - if (pp) - buflen = 0; - if (!quoted && buflen) { - *bp++ = '\''; - *bp = '\0'; - buflen--; - } - } - } else { -raw: - *bp++ = *cp; - *bp = '\0'; - buflen--; - - if (*cp == '\'') - quoted = !quoted; - } - } - - if (buflen <= 0 || - (ct->c_termproc && (size_t) buflen <= strlen(ct->c_termproc))) { - /* content_error would provide a more useful error message - * here, except that if we got overrun, it probably would - * too. - */ - fprintf(stderr, "Buffer overflow constructing show command!\n"); + if (parse_display_string (ct, cp, &xstdin, &xlist, &xpause, &xtty, file, + buffer, sizeof(buffer) - 1, 0)) { + admonish (NULL, "Buffer overflow constructing show command!\n"); return NOTOK; } - /* use charset string to modify display method */ - if (ct->c_termproc) { - char term[BUFSIZ]; - - strncpy (term, buffer, sizeof(term)); - snprintf (buffer, sizeof(buffer), ct->c_termproc, term); - } - got_command: return show_content_aux2 (ct, serial, alternate, cracked, buffer, fd, xlist, xpause, xstdin, xtty); @@@ -358,9 -484,9 +353,9 @@@ show_content_aux2 (CT ct, int serial, i int fd, int xlist, int xpause, int xstdin, int xtty) { pid_t child_id; - int i; - char *vec[4], exec[BUFSIZ + sizeof "exec "]; - + int i, vecp; + char **vec, *file; + if (debugsw || cracked) { fflush (stdout); @@@ -385,9 -511,9 +380,9 @@@ char prompt[BUFSIZ]; if (ct->c_type == CT_MULTIPART) - list_content (ct, -1, 1, 0, 0); + list_content (ct, -1, 1, 0, 0, 0); else - list_switch (ct, -1, 1, 0, 0); + list_switch (ct, -1, 1, 0, 0, 0); if (xpause && isatty (fileno (stdout))) { int intr; @@@ -411,8 -537,12 +406,8 @@@ } } - snprintf (exec, sizeof(exec), "exec %s", buffer); - - vec[0] = "/bin/sh"; - vec[1] = "-c"; - vec[2] = exec; - vec[3] = NULL; + vec = argsplit(buffer, &file, &vecp); + vec[vecp++] = NULL; fflush (stdout); @@@ -430,15 -560,13 +425,15 @@@ if (!xstdin) dup2 (fd, 0); close (fd); - execvp ("/bin/sh", vec); + execvp (file, vec); fprintf (stderr, "unable to exec "); perror ("/bin/sh"); _exit (-1); /* NOTREACHED */ default: + arglist_free(file, vec); + if (!serial) { ct->c_pid = child_id; if (xtty) @@@ -464,13 -592,13 +459,13 @@@ show_text (CT ct, int serial, int alter char *cp, buffer[BUFSIZ]; CI ci = &ct->c_ctinfo; - /* Check for mhn-show-type/subtype */ + /* Check for invo_name-show-type/subtype */ snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", invo_name, ci->ci_type, ci->ci_subtype); if ((cp = context_find (buffer)) && *cp != '\0') return show_content_aux (ct, serial, alternate, cp, NULL); - /* Check for mhn-show-type */ + /* Check for invo_name-show-type */ snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); if ((cp = context_find (buffer)) && *cp != '\0') return show_content_aux (ct, serial, alternate, cp, NULL); @@@ -480,7 -608,7 +475,7 @@@ * if it is not a text part of a multipart/alternative */ if (!alternate || ct->c_subtype == TEXT_PLAIN) { - snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw : + snprintf (buffer, sizeof(buffer), "%%p%s %%F", progsw ? progsw : moreproc && *moreproc ? moreproc : DEFAULT_PAGER); cp = (ct->c_showproc = add (buffer, NULL)); return show_content_aux (ct, serial, alternate, cp, NULL); @@@ -500,13 -628,13 +495,13 @@@ show_multi (CT ct, int serial, int alte char *cp, buffer[BUFSIZ]; CI ci = &ct->c_ctinfo; - /* Check for mhn-show-type/subtype */ + /* Check for invo_name-show-type/subtype */ snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", invo_name, ci->ci_type, ci->ci_subtype); if ((cp = context_find (buffer)) && *cp != '\0') return show_multi_aux (ct, serial, alternate, cp); - /* Check for mhn-show-type */ + /* Check for invo_name-show-type */ snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); if ((cp = context_find (buffer)) && *cp != '\0') return show_multi_aux (ct, serial, alternate, cp); @@@ -567,7 -695,7 +562,7 @@@ show_multi_internal (CT ct, int serial /* * alternate -> we are a part inside an multipart/alternative - * alternating -> we are a multipart/alternative + * alternating -> we are a multipart/alternative */ result = alternate ? NOTOK : OK; @@@ -663,12 -791,12 +658,12 @@@ out static int show_multi_aux (CT ct, int serial, int alternate, char *cp) { - int len, buflen, quoted; - int xlist, xpause, xtty; - char *bp, *pp, *file, buffer[BUFSIZ]; + /* xstdin is only used in the call to parse_display_string(): + its value is ignored in the function. */ + int xstdin = 0, xlist = 0, xpause = 0, xtty = 0; + char *file, buffer[BUFSIZ]; struct multipart *m = (struct multipart *) ct->c_ctparams; struct part *part; - CI ci = &ct->c_ctinfo; CT p; for (part = m->mp_parts; part; part = part->mp_next) { @@@ -685,6 -813,7 +680,6 @@@ if ((*p->c_ceopenfnx) (p, &file) == NOTOK) return NOTOK; - /* I'm not sure if this is necessary? */ p->c_storage = add (file, NULL); if (p->c_showproc && !strcmp (p->c_showproc, "true")) @@@ -693,113 -822,30 +688,114 @@@ } } - xlist = 0; - xpause = 0; - xtty = 0; + if (parse_display_string (ct, cp, &xstdin, &xlist, &xpause, &xtty, file, + buffer, sizeof(buffer) - 1, 1)) { + admonish (NULL, "Buffer overflow constructing show command!\n"); + return NOTOK; + } + + return show_content_aux2 (ct, serial, alternate, NULL, buffer, + NOTOK, xlist, xpause, 0, xtty); +} + + +/* + * show content of type "message/rfc822" + */ + +static int +show_message_rfc822 (CT ct, int serial, int alternate) +{ + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* Check for invo_name-show-type/subtype */ + snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* Check for invo_name-show-type */ + snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + if ((cp = ct->c_showproc)) + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* default method for message/rfc822 */ + if (ct->c_subtype == MESSAGE_RFC822) { + cp = (ct->c_showproc = add ("%pecho -file %F", NULL)); + return show_content_aux (ct, serial, alternate, cp, NULL); + } + + /* complain if we are not a part of a multipart/alternative */ + if (!alternate) + content_error (NULL, ct, "don't know how to display content"); + + return NOTOK; +} + + +/* + * Show content of type "message/partial". + */ + +static int +show_partial (CT ct, int serial, int alternate) +{ + NMH_UNUSED (serial); + NMH_UNUSED (alternate); + + content_error (NULL, ct, + "in order to display this message, you must reassemble it"); + return NOTOK; +} + + +/* + * Show content of type "message/external". + * + * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET. + */ + +static int +show_external (CT ct, int serial, int alternate) +{ + struct exbody *e = (struct exbody *) ct->c_ctparams; + CT p = e->eb_content; + + if (!type_ok (p, 0)) + return OK; + + return show_switch (p, serial, alternate); +} + + +static int +parse_display_string (CT ct, char *cp, int *xstdin, int *xlist, int *xpause, + int *xtty, char *file, char *buffer, size_t buflen, + int multipart) { + int len, quoted = 0; + char *bp = buffer, *pp; + CI ci = &ct->c_ctinfo; - /* get buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer) - 1; bp[0] = bp[buflen] = '\0'; - quoted = 0; - /* Now parse display string */ for ( ; *cp && buflen > 0; cp++) { if (*cp == '%') { pp = bp; + switch (*++cp) { case 'a': /* insert parameters from Content-Type field */ { - char **ap, **ep; + PM pm; char *s = ""; - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, pm->pm_name, + get_param_value(pm, '?')); len = strlen (bp); bp += len; buflen -= len; @@@ -821,73 -867,43 +817,73 @@@ case 'e': /* exclusive execution */ - xtty = 1; + *xtty = 1; break; case 'F': - /* %e and %f */ - xtty = 1; + /* %e, %f, and stdin is terminal not content */ + *xstdin = 1; + *xtty = 1; /* and fall... */ case 'f': - /* insert filename(s) containing content */ - { - char *s = ""; - - for (part = m->mp_parts; part; part = part->mp_next) { - p = part->mp_part; - - snprintf (bp, buflen, "%s'%s'", s, p->c_storage); - len = strlen (bp); - bp += len; - buflen -= len; - s = " "; + if (multipart) { + /* insert filename(s) containing content */ + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + char *s = ""; + CT p; + + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + snprintf (bp, buflen, "%s%s", s, p->c_storage); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } else { + /* insert filename containing content */ + snprintf (bp, buflen, "%s", file); + + /* + * Old comments below are left here for posterity. + * This was/is tricky. + */ + /* since we've quoted the file argument, set things up + * to look past it, to avoid problems with the quoting + * logic below. (I know, I should figure out what's + * broken with the quoting logic, but..) + */ + /* + * Here's the email that submitted the patch with + * the comment above: + * https://www.mail-archive.com/nmh-workers@mhost.com/ + * msg00288.html + * I can't tell from that exactly what was broken, + * beyond misquoting of the filename. The profile + * had appearances of %F both with and without quotes. + * The unquoted ones should have been quoted by the + * code below. + * The fix was to always quote the filename. But + * that broke '%F' because it expanded to ''filename''. + */ + /* + * Old comments above are left here for posterity. + * The quoting below should work properly now. + */ } - /* set our starting pointer back to bp, to avoid - * requoting the filenames we just added - */ - pp = bp; - } - break; + break; case 'p': /* %l, and pause prior to displaying content */ - xpause = pausesw; + *xpause = pausesw; /* and fall... */ case 'l': /* display listing prior to displaying content */ - xlist = !nolist; + *xlist = !nolist; break; case 's': @@@ -899,41 -915,6 +895,42 @@@ /* insert character % */ goto raw; + case '{' : { + const char *closing_brace = strchr(cp, '}'); + + if (closing_brace) { + const size_t param_len = closing_brace - cp - 1; + char *param = mh_xmalloc(param_len + 1); - const char *value; ++ char *value; + + (void) strncpy(param, cp + 1, param_len); + param[param_len] = '\0'; - value = parameter_value(ci, param); ++ value = get_param(ci->ci_first_pm, param, '?', 0); + free(param); + + cp += param_len + 1; /* Skip both braces, too. */ + + if (value) { + /* %{param} is set in the Content-Type header. + After the break below, quote it if necessary. */ + (void) strncpy(bp, value, buflen); ++ free(value); + } else { + /* %{param} not found, so skip it completely. cp + was advanced above. */ + continue; + } + } else { + /* This will get confused if there are multiple %{}'s, + but its real purpose is to avoid doing bad things + above if a closing brace wasn't found. */ + admonish(NULL, + "no closing brace for display string escape %s", + cp); + } + break; + } + default: *bp++ = *--cp; *bp = '\0'; @@@ -943,93 -924,54 +940,93 @@@ len = strlen (bp); bp += len; buflen -= len; + *bp = '\0'; /* Did we actually insert something? */ if (bp != pp) { /* Insert single quote if not inside quotes already */ if (!quoted && buflen) { len = strlen (pp); - memmove (pp + 1, pp, len); + memmove (pp + 1, pp, len+1); *pp++ = '\''; buflen--; bp++; + quoted = 1; } /* Escape existing quotes */ while ((pp = strchr (pp, '\'')) && buflen > 3) { len = strlen (pp++); - memmove (pp + 3, pp, len); - *pp++ = '\\'; - *pp++ = '\''; - *pp++ = '\''; - buflen -= 3; - bp += 3; + if (quoted) { + /* Quoted. Let this quote close that quoting. + Insert an escaped quote to replace it and + another quote to reopen quoting, which will be + closed below. */ + memmove (pp + 2, pp, len); + *pp++ = '\\'; + *pp++ = '\''; + buflen -= 2; + bp += 2; + quoted = 0; + } else { + /* Not quoted. This should not be reached with + the current code, but handle the condition + in case the code changes. Just escape the + quote. */ + memmove (pp, pp-1, len+1); + *(pp++-1) = '\\'; + buflen -= 1; + bp += 1; + } } /* If pp is still set, that means we ran out of space. */ if (pp) buflen = 0; - if (!quoted && buflen) { - *bp++ = '\''; - *bp = '\0'; - buflen--; + /* Close quoting. */ + if (quoted && buflen) { + /* See if we need to close the quote by looking + for an odd number of unescaped close quotes in + the remainder of the display string. */ + int found_quote = 0, escaped = 0; + char *c; + + for (c = cp+1; *c; ++c) { + if (*c == '\\') { + escaped = ! escaped; + } else { + if (escaped) { + escaped = 0; + } else { + if (*c == '\'') { + found_quote = ! found_quote; + } + } + } + } + if (! found_quote) { + *bp++ = '\''; + buflen--; + quoted = 0; + } } } } else { raw: *bp++ = *cp; - *bp = '\0'; buflen--; if (*cp == '\'') quoted = !quoted; } + + *bp = '\0'; } if (buflen <= 0 || - (ct->c_termproc && buflen <= (ssize_t) strlen(ct->c_termproc))) { + (ct->c_termproc && buflen <= strlen(ct->c_termproc))) { /* content_error would provide a more useful error message * here, except that if we got overrun, it probably would * too. */ - fprintf(stderr, "Buffer overflow constructing show command!\n"); return NOTOK; } @@@ -1038,210 -980,87 +1035,198 @@@ char term[BUFSIZ]; strncpy (term, buffer, sizeof(term)); - snprintf (buffer, sizeof(buffer), ct->c_termproc, term); + snprintf (buffer, buflen, ct->c_termproc, term); } - return show_content_aux2 (ct, serial, alternate, NULL, buffer, - NOTOK, xlist, xpause, 0, xtty); + return OK; } -/* - * show content of type "message/rfc822" - */ - -static int -show_message_rfc822 (CT ct, int serial, int alternate) -{ - char *cp, buffer[BUFSIZ]; - CI ci = &ct->c_ctinfo; - - /* Check for mhn-show-type/subtype */ - snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", - invo_name, ci->ci_type, ci->ci_subtype); - if ((cp = context_find (buffer)) && *cp != '\0') - return show_content_aux (ct, serial, alternate, cp, NULL); - - /* Check for mhn-show-type */ - snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); - if ((cp = context_find (buffer)) && *cp != '\0') - return show_content_aux (ct, serial, alternate, cp, NULL); - - if ((cp = ct->c_showproc)) - return show_content_aux (ct, serial, alternate, cp, NULL); +int +convert_charset (CT ct, char *dest_charset, int *message_mods) { + char *src_charset = content_charset (ct); + int status = OK; + + /* norm_charmap() is case sensitive. */ + char *src_charset_u = upcase (src_charset); + char *dest_charset_u = upcase (dest_charset); + int different_charsets = + strcmp (norm_charmap (src_charset), norm_charmap (dest_charset)); + + free (dest_charset_u); + free (src_charset_u); + + if (different_charsets) { +#ifdef HAVE_ICONV + iconv_t conv_desc = NULL; + char *dest; + int fd = -1; + char **file = NULL; + FILE **fp = NULL; + size_t begin; + size_t end; + int opened_input_file = 0; + char src_buffer[BUFSIZ]; + HF hf; + char *tempfile; + + if ((conv_desc = iconv_open (dest_charset, src_charset)) == + (iconv_t) -1) { + advise (NULL, "Can't convert %s to %s", src_charset, dest_charset); + return NOTOK; + } + + if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) { + adios (NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + dest = add (tempfile, NULL); + + if (ct->c_cefile.ce_file) { + file = &ct->c_cefile.ce_file; + fp = &ct->c_cefile.ce_fp; + begin = end = 0; + } else if (ct->c_file) { + file = &ct->c_file; + fp = &ct->c_fp; + begin = (size_t) ct->c_begin; + end = (size_t) ct->c_end; + } /* else no input file: shouldn't happen */ + + if (file && *file && fp) { + if (! *fp) { + if ((*fp = fopen (*file, "r")) == NULL) { + advise (*file, "unable to open for reading"); + status = NOTOK; + } else { + opened_input_file = 1; + } + } + } + + if (fp && *fp) { + size_t inbytes; + size_t bytes_to_read = + end > 0 && end > begin ? end - begin : sizeof src_buffer; + + fseeko (*fp, begin, SEEK_SET); + while ((inbytes = fread (src_buffer, 1, + min (bytes_to_read, sizeof src_buffer), + *fp)) > 0) { + char dest_buffer[BUFSIZ]; + ICONV_CONST char *ib = src_buffer; + char *ob = dest_buffer; + size_t outbytes = sizeof dest_buffer; + size_t outbytes_before = outbytes; + + if (end > 0) bytes_to_read -= inbytes; + + if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) == + (size_t) -1) { + status = NOTOK; + break; + } else { + write (fd, dest_buffer, outbytes_before - outbytes); + } + } + + if (opened_input_file) { + fclose (*fp); + *fp = NULL; + } + } + + iconv_close (conv_desc); + close (fd); + + if (status == OK) { + /* Replace the decoded file with the converted one. */ + if (ct->c_cefile.ce_file) { + if (ct->c_cefile.ce_unlink) { + (void) m_unlink (ct->c_cefile.ce_file); + } + free (ct->c_cefile.ce_file); + } + ct->c_cefile.ce_file = dest; + ct->c_cefile.ce_unlink = 1; + + ++*message_mods; + + /* Update ci_attrs. */ + src_charset = dest_charset; + + /* Update ct->c_ctline. */ + if (ct->c_ctline) { - char *ctline = - update_attr (ct->c_ctline, "charset=", dest_charset); ++ char *ctline = concat(" ", ct->c_ctinfo.ci_type, "/", ++ ct->c_ctinfo.ci_subtype, NULL); ++ replace_param(&ct->c_ctinfo.ci_first_pm, ++ &ct->c_ctinfo.ci_last_pm, "charset", ++ dest_charset, 0); ++ char *outline = output_params(strlen(TYPE_FIELD) + 1 + ++ strlen(ctline), ++ ct->c_ctinfo.ci_first_pm, ++ NULL, 0); ++ if (outline) { ++ ctline = add(outline, ctline); ++ free(outline); ++ } - /* default method for message/rfc822 */ - if (ct->c_subtype == MESSAGE_RFC822) { - cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL)); - return show_content_aux (ct, serial, alternate, cp, NULL); + free (ct->c_ctline); + ct->c_ctline = ctline; + } /* else no CT line, which is odd */ + + /* Update Content-Type header field. */ + for (hf = ct->c_first_hf; hf; hf = hf->next) { + if (! strcasecmp (TYPE_FIELD, hf->name)) { - char *ctline_less_newline = - update_attr (hf->value, "charset=", dest_charset); - char *ctline = concat (ctline_less_newline, "\n", NULL); - free (ctline_less_newline); ++ char *ctline = concat (ct->c_ctline, "\n", NULL); + + free (hf->value); + hf->value = ctline; + break; + } + } + } else { + (void) m_unlink (dest); + } +#else /* ! HAVE_ICONV */ + NMH_UNUSED (message_mods); + + advise (NULL, "Can't convert %s to %s without iconv", src_charset, + dest_charset); + status = NOTOK; +#endif /* ! HAVE_ICONV */ } - /* complain if we are not a part of a multipart/alternative */ - if (!alternate) - content_error (NULL, ct, "don't know how to display content"); - - return NOTOK; + return status; } -/* - * Show content of type "message/partial". - */ - static int -show_partial (CT ct, int serial, int alternate) -{ - NMH_UNUSED (serial); - NMH_UNUSED (alternate); - - content_error (NULL, ct, - "in order to display this message, you must reassemble it"); - return NOTOK; -} +convert_content_charset (CT ct, char **file) { +#ifdef HAVE_ICONV + /* Using current locale, see if the content needs to be converted. */ + /* content_charset() cannot return NULL. */ + char *charset = content_charset (ct); -/* - * Show content of type "message/external". - * - * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET. - */ - -static int -show_external (CT ct, int serial, int alternate) -{ - struct exbody *e = (struct exbody *) ct->c_ctparams; - CT p = e->eb_content; + if (! check_charset (charset, strlen (charset))) { + int unused = 0; - if (!type_ok (p, 0)) - return OK; + if (convert_charset (ct, get_charset (), &unused) == 0) { + *file = ct->c_cefile.ce_file; + } else { + return NOTOK; + } + } +#else /* ! HAVE_ICONV */ + NMH_UNUSED (ct); + NMH_UNUSED (file); +#endif /* ! HAVE_ICONV */ - return show_switch (p, serial, alternate); + return OK; } - /* - * If a Content-Type parameter named param exists, returns its value. - * Otherwise, returns NULL. - */ - static const char * - parameter_value (CI ci, const char *param) { - const char *value = NULL; - char **ap, **vp; - - for (ap = ci->ci_attrs, vp = ci->ci_values; *ap; ++ap, ++vp) { - if (! strcasecmp (*ap, param)) { - value = *vp; - break; - } - } - - return value; - } - - static void intrser (int i) { diff --combined uip/mhstoresbr.c index 89a2f278,156411b7..fc265542 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@@ -17,58 -17,24 +17,58 @@@ #include #include +enum clobber_policy_t { + NMH_CLOBBER_ALWAYS = 0, + NMH_CLOBBER_AUTO, + NMH_CLOBBER_SUFFIX, + NMH_CLOBBER_ASK, + NMH_CLOBBER_NEVER +}; -/* - * The list of top-level contents to display - */ -extern CT *cts; +static enum clobber_policy_t clobber_policy (const char *); -int autosw = 0; +struct mhstoreinfo { + CT *cts; /* Top-level list of contents to store. */ + char *cwd; /* cached current directory */ + int autosw; /* -auto enabled */ + int verbosw; /* -verbose enabled */ + int files_not_clobbered; /* output flag indicating that store failed + in order to not clobber an existing file */ -/* - * Cache of current directory. This must be - * set before these routines are called. - */ -char *cwd; + /* The following must never be touched by a caller: they are for + internal use by the mhstoresbr functions. */ + char *dir; /* directory in which to store contents */ + enum clobber_policy_t clobber_policy; /* -clobber selection */ +}; + +typedef struct mhstoreinfo *mhstoreinfo_t; + +mhstoreinfo_t +mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw) { + mhstoreinfo_t info = mh_xmalloc (sizeof *info); + + info->cts = ct; + info->cwd = pwd; + info->autosw = asw; + info->verbosw = vsw; + info->files_not_clobbered = 0; + info->dir = NULL; + info->clobber_policy = clobber_policy (csw); + + return info; +} + +void +mhstoreinfo_free (mhstoreinfo_t info) { + free (info->cwd); + free (info); +} + +int +mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info) { + return info->files_not_clobbered; +} -/* - * The directory in which to store the contents. - */ -static char *dir; /* * Type for a compare function for qsort. This keeps @@@ -88,26 -54,26 +88,26 @@@ int show_content_aux (CT, int, int, cha /* * prototypes */ -void store_all_messages (CT *); +void store_all_messages (mhstoreinfo_t); /* * static prototypes */ -static void store_single_message (CT); -static int store_switch (CT); -static int store_generic (CT); -static int store_application (CT); -static int store_multi (CT); -static int store_partial (CT); -static int store_external (CT); +static void store_single_message (CT, mhstoreinfo_t); +static int store_switch (CT, mhstoreinfo_t); +static int store_generic (CT, mhstoreinfo_t); +static int store_application (CT, mhstoreinfo_t); +static int store_multi (CT, mhstoreinfo_t); +static int store_partial (CT, mhstoreinfo_t); +static int store_external (CT, mhstoreinfo_t); static int ct_compar (CT *, CT *); -static int store_content (CT, CT); +static int store_content (CT, CT, mhstoreinfo_t); static int output_content_file (CT, int); static int output_content_folder (char *, char *); static int parse_format_string (CT, char *, char *, int, char *); static void get_storeproc (CT); static int copy_some_headers (FILE *, CT); -static char *clobber_check (char *); +static char *clobber_check (char *, mhstoreinfo_t); /* * Main entry point to store content @@@ -115,7 -81,7 +115,7 @@@ */ void -store_all_messages (CT *cts) +store_all_messages (mhstoreinfo_t info) { CT ct, *ctp; char *cp; @@@ -125,13 -91,13 +125,13 @@@ * store any contents. */ if ((cp = context_find (nmhstorage)) && *cp) - dir = getcpy (cp); + info->dir = getcpy (cp); else - dir = getcpy (cwd); + info->dir = getcpy (info->cwd); - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { ct = *ctp; - store_single_message (ct); + store_single_message (ct, info); } flush_errors (); @@@ -144,11 -110,11 +144,11 @@@ */ static void -store_single_message (CT ct) +store_single_message (CT ct, mhstoreinfo_t info) { if (type_ok (ct, 1)) { umask (ct->c_umask); - store_switch (ct); + store_switch (ct, info); if (ct->c_fp) { fclose (ct->c_fp); ct->c_fp = NULL; @@@ -164,33 -130,33 +164,33 @@@ */ static int -store_switch (CT ct) +store_switch (CT ct, mhstoreinfo_t info) { switch (ct->c_type) { case CT_MULTIPART: - return store_multi (ct); + return store_multi (ct, info); case CT_MESSAGE: switch (ct->c_subtype) { case MESSAGE_PARTIAL: - return store_partial (ct); + return store_partial (ct, info); case MESSAGE_EXTERNAL: - return store_external (ct); + return store_external (ct, info); case MESSAGE_RFC822: default: - return store_generic (ct); + return store_generic (ct, info); } case CT_APPLICATION: - return store_application (ct); + return store_application (ct, info); case CT_TEXT: case CT_AUDIO: case CT_IMAGE: case CT_VIDEO: - return store_generic (ct); + return store_generic (ct, info); default: adios (NULL, "unknown content type %d", ct->c_type); @@@ -206,17 -172,17 +206,17 @@@ */ static int -store_generic (CT ct) +store_generic (CT ct, mhstoreinfo_t info) { /* * Check if the content specifies a filename. * Don't bother with this for type "message" * (only "message/rfc822" will use store_generic). */ - if (autosw && ct->c_type != CT_MESSAGE) + if (info->autosw && ct->c_type != CT_MESSAGE) get_storeproc (ct); - return store_content (ct, NULL); + return store_content (ct, NULL, info); } @@@ -225,13 -191,12 +225,12 @@@ */ static int -store_application (CT ct) +store_application (CT ct, mhstoreinfo_t info) { - char **ap, **ep; CI ci = &ct->c_ctinfo; /* Check if the content specifies a filename */ - if (autosw) + if (info->autosw) get_storeproc (ct); /* @@@ -241,28 -206,23 +240,23 @@@ */ if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) { int tarP = 0, zP = 0, gzP = 0; + char *cp; - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - /* check for "type=tar" attribute */ - if (!strcasecmp (*ap, "type")) { - if (strcasecmp (*ep, "tar")) - break; - + if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) { + if (strcasecmp (cp, "tar") == 0) tarP = 1; - continue; - } + } - /* check for "conversions=compress" attribute */ - if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions")) - && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) { + /* check for "conversions=compress" attribute */ + if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) || + (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) { + if (strcasecmp (cp, "compress") == 0 || + strcasecmp (cp, "x-compress") == 0) { zP = 1; - continue; } - /* check for "conversions=gzip" attribute */ - if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions")) - && (!strcasecmp (*ep, "gzip") || !strcasecmp (*ep, "x-gzip"))) { + if (strcasecmp (cp, "gzip") == 0 || + strcasecmp (cp, "x-gzip") == 0) { gzP = 1; - continue; } } @@@ -271,7 -231,7 +265,7 @@@ : (gzP ? "%egzip -dc | tar tvf -" : "%etar tvf -"), NULL); if (!ct->c_storeproc) { - if (autosw) { + if (info->autosw) { ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -" : (gzP ? "| gzip -dc | tar xvpf -" : "| tar xvpf -"), NULL); @@@ -285,7 -245,7 +279,7 @@@ } } - return store_content (ct, NULL); + return store_content (ct, NULL, info); } @@@ -294,7 -254,7 +288,7 @@@ */ static int -store_multi (CT ct) +store_multi (CT ct, mhstoreinfo_t info) { int result; struct multipart *m = (struct multipart *) ct->c_ctparams; @@@ -311,7 -271,7 +305,7 @@@ NULL here. */ p->c_storage = ct->c_storage; } - result = store_switch (p); + result = store_switch (p, info); p->c_storage = NULL; if (result == OK && ct->c_subtype == MULTI_ALTERNATE) @@@ -329,7 -289,7 +323,7 @@@ */ static int -store_partial (CT ct) +store_partial (CT ct, mhstoreinfo_t info) { int cur, hi, i; CT p, *ctp, *ctq; @@@ -341,7 -301,7 +335,7 @@@ return OK; hi = i = 0; - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { p = *ctp; if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { pm = (struct partial *) p->c_ctparams; @@@ -367,7 -327,7 +361,7 @@@ adios (NULL, "out of memory"); ctq = base; - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { p = *ctp; if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { pm = (struct partial *) p->c_ctparams; @@@ -413,7 -373,7 +407,7 @@@ missing_part ctq = base; ct = *ctq++; - if (store_content (ct, NULL) == NOTOK) { + if (store_content (ct, NULL, info) == NOTOK) { losing: free ((char *) base); return NOTOK; @@@ -421,7 -381,7 +415,7 @@@ for (; *ctq; ctq++) { p = *ctq; - if (store_content (p, ct) == NOTOK) + if (store_content (p, ct, info) == NOTOK) goto losing; } @@@ -435,7 -395,7 +429,7 @@@ */ static int -store_external (CT ct) +store_external (CT ct, mhstoreinfo_t info) { int result = NOTOK; struct exbody *e = (struct exbody *) ct->c_ctparams; @@@ -448,7 -408,7 +442,7 @@@ * Check if the parameters for the external body * specified a filename. */ - if (autosw) { + if (info->autosw) { char *cp; if ((cp = e->eb_name) @@@ -478,7 -438,7 +472,7 @@@ c_storage, so we know that p->c_storage is NULL here. */ p->c_storage = ct->c_storage; } - result = store_switch (p); + result = store_switch (p, info); p->c_storage = NULL; p->c_partno = NULL; @@@ -513,7 -473,7 +507,7 @@@ ct_compar (CT *a, CT *b */ static int -store_content (CT ct, CT p) +store_content (CT ct, CT p, mhstoreinfo_t info) { int appending = 0, msgnum = 0; int is_partial = 0, first_partial = 0; @@@ -624,24 -584,23 +618,24 @@@ * Parse and expand the storage formatting string * in `cp' into `buffer'. */ - parse_format_string (ct, cp, buffer, sizeof(buffer), dir); + parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir); /* * If formatting begins with '|' or '!', then pass * content to standard input of a command and return. */ if (buffer[0] == '|' || buffer[0] == '!') - return show_content_aux (ct, 1, 0, buffer + 1, dir); + return show_content_aux (ct, 1, 0, buffer + 1, info->dir); /* record the filename */ - if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) { + if ((ct->c_storage = clobber_check (add (buffer, NULL), info)) == + NULL) { return NOTOK; } } else { - /* The output filename was explicitly specified, so use it. */ - if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL))) == - NULL) { + /* The output filename was explicitly specified, so use it. */ + if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL), info)) == + NULL) { return NOTOK; } } @@@ -666,44 -625,42 +660,44 @@@ got_filename return NOTOK; } - /* - * Now print out the name/number of the message - * that we are storing. - */ - if (is_partial) { - if (first_partial) - fprintf (stderr, "reassembling partials "); - if (last_partial) - fprintf (stderr, "%s", ct->c_file); - else - fprintf (stderr, "%s,", ct->c_file); - } else { - fprintf (stderr, "storing message %s", ct->c_file); - if (ct->c_partno) - fprintf (stderr, " part %s", ct->c_partno); - } - - /* - * Unless we are in the "middle" of group of message/partials, - * we now print the name of the file, folder, and/or message - * to which we are storing the content. - */ - if (!is_partial || last_partial) { - if (ct->c_folder) { - fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, msgnum); - } else if (!strcmp(ct->c_storage, "-")) { - fprintf (stderr, " to stdout\n"); - } else { - int cwdlen; + if (info->verbosw) { + /* + * Now print out the name/number of the message + * that we are storing. + */ + if (is_partial) { + if (first_partial) + fprintf (stderr, "reassembling partials "); + if (last_partial) + fprintf (stderr, "%s", ct->c_file); + else + fprintf (stderr, "%s,", ct->c_file); + } else { + fprintf (stderr, "storing message %s", ct->c_file); + if (ct->c_partno) + fprintf (stderr, " part %s", ct->c_partno); + } - cwdlen = strlen (cwd); - fprintf (stderr, " as file %s\n", - strncmp (ct->c_storage, cwd, cwdlen) - || ct->c_storage[cwdlen] != '/' - ? ct->c_storage : ct->c_storage + cwdlen + 1); - } + /* + * Unless we are in the "middle" of group of message/partials, + * we now print the name of the file, folder, and/or message + * to which we are storing the content. + */ + if (!is_partial || last_partial) { + if (ct->c_folder) { + fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, + msgnum); + } else if (!strcmp(ct->c_storage, "-")) { + fprintf (stderr, " to stdout\n"); + } else { + int cwdlen = strlen (info->cwd); + + fprintf (stderr, " as file %s\n", + strncmp (ct->c_storage, info->cwd, cwdlen) + || ct->c_storage[cwdlen] != '/' + ? ct->c_storage : ct->c_storage + cwdlen + 1); + } + } } return OK; @@@ -998,12 -955,12 +992,12 @@@ parse_format_string (CT ct, char *cp, c buflen--; continue; } else { - char **ap, **ep; + PM pm; char *s = ""; - for (ap = ci->ci_attrs, ep = ci->ci_values; - *ap; ap++, ep++) { - snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, + pm->pm_name, get_param_value(pm, '?')); len = strlen (bp); bp += len; buflen -= len; @@@ -1075,7 -1032,7 +1069,7 @@@ raw static void get_storeproc (CT ct) { - char **ap, **ep, *cp; + char *cp; CI ci; /* @@@ -1091,29 -1048,18 +1085,18 @@@ * 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); - } + if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0)) + && *cp != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { + ct->c_storeproc = add (cp, NULL); + free(cp); + return; } - - free (ci); - if (found_filename) return; + if (cp) + free(cp); } /* @@@ -1122,17 -1068,17 +1105,17 @@@ * the storeproc. */ ci = &ct->c_ctinfo; - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (! strcasecmp (*ap, "name") - && *(cp = *ep) != '/' - && *cp != '.' - && *cp != '|' - && *cp != '!' - && !strchr (cp, '%')) { + if ((cp = get_param(ci->ci_first_pm, "name", '_', 0)) + && *cp != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { ct->c_storeproc = add (cp, NULL); - return; - } + } + if (cp) + free(cp); } @@@ -1168,23 -1114,35 +1151,23 @@@ copy_some_headers (FILE *out, CT ct /******************************************************************************/ /* -clobber support */ -enum clobber_policy_t { - NMH_CLOBBER_ALWAYS, - NMH_CLOBBER_AUTO, - NMH_CLOBBER_SUFFIX, - NMH_CLOBBER_ASK, - NMH_CLOBBER_NEVER -}; - -static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS; - -int files_not_clobbered = 0; - -int -save_clobber_policy (const char *value) { - if (! strcasecmp (value, "always")) { - clobber_policy = NMH_CLOBBER_ALWAYS; +static +enum clobber_policy_t +clobber_policy (const char *value) { + if (value == NULL || ! strcasecmp (value, "always")) { } else if (! strcasecmp (value, "auto")) { - clobber_policy = NMH_CLOBBER_AUTO; + return NMH_CLOBBER_AUTO; } else if (! strcasecmp (value, "suffix")) { - clobber_policy = NMH_CLOBBER_SUFFIX; + return NMH_CLOBBER_SUFFIX; } else if (! strcasecmp (value, "ask")) { - clobber_policy = NMH_CLOBBER_ASK; + return NMH_CLOBBER_ASK; } else if (! strcasecmp (value, "never")) { - clobber_policy = NMH_CLOBBER_NEVER; + return NMH_CLOBBER_NEVER; } else { - return 1; + adios (NULL, "invalid argument, %s, to clobber", value); } - return 0; + return NMH_CLOBBER_ALWAYS; } @@@ -1221,6 -1179,7 +1204,6 @@@ next_version (char *file, enum clobber_ /* Should never get here. */ advise (NULL, "will not overwrite %s, invalid clobber policy", buffer); free (buffer); - ++files_not_clobbered; return NULL; } @@@ -1243,6 -1202,7 +1226,6 @@@ advise (NULL, "will not overwrite %s, too many versions", buffer); free (buffer); buffer = NULL; - ++files_not_clobbered; } return buffer; @@@ -1250,7 -1210,7 +1233,7 @@@ static char * -clobber_check (char *original_file) { +clobber_check (char *original_file, mhstoreinfo_t info) { /* clobber policy return value * -------------- ------------ * -always original_file @@@ -1264,7 -1224,7 +1247,7 @@@ char *cwd = NULL; int check_again; - if (clobber_policy == NMH_CLOBBER_ASK) { + if (info->clobber_policy == NMH_CLOBBER_ASK) { /* Save cwd for possible use in loop below. */ char *slash; @@@ -1285,17 -1245,14 +1268,17 @@@ file = original_file; check_again = 0; - switch (clobber_policy) { + switch (info->clobber_policy) { case NMH_CLOBBER_ALWAYS: break; case NMH_CLOBBER_SUFFIX: case NMH_CLOBBER_AUTO: if (stat (file, &st) == OK) { - file = next_version (original_file, clobber_policy); + if ((file = next_version (original_file, info->clobber_policy)) == + NULL) { + ++info->files_not_clobbered; + } } break; @@@ -1303,10 -1260,7 +1286,10 @@@ if (stat (file, &st) == OK) { enum answers { NMH_YES, NMH_NO, NMH_RENAME }; static struct swit answer[4] = { - { "yes", 0, NMH_YES }, { "no", 0, NMH_NO }, { "rename", 0, NMH_RENAME }, { NULL, 0, 0 } }; + { "yes", 0, NMH_YES }, + { "no", 0, NMH_NO }, + { "rename", 0, NMH_RENAME }, + { NULL, 0, 0 } }; char **ans; if (isatty (fileno (stdin))) { @@@ -1326,7 -1280,7 +1309,7 @@@ case NMH_NO: free (file); file = NULL; - ++files_not_clobbered; + ++info->files_not_clobbered; break; case NMH_RENAME: { char buf[PATH_MAX]; @@@ -1334,7 -1288,7 +1317,7 @@@ if (fgets (buf, sizeof buf, stdin) == NULL || buf[0] == '\0') { file = NULL; - ++files_not_clobbered; + ++info->files_not_clobbered; } else { char *newline = strchr (buf, '\n'); if (newline) { @@@ -1366,7 -1320,7 +1349,7 @@@ advise (NULL, "will not overwrite %s with -clobber never", file); free (file); file = NULL; - ++files_not_clobbered; + ++info->files_not_clobbered; } break; }