]> diplodocus.org Git - nmh/commitdiff
Merge branch 'extended-params'
authorKen Hornstein <kenh@pobox.com>
Thu, 6 Mar 2014 04:17:17 +0000 (23:17 -0500)
committerKen Hornstein <kenh@pobox.com>
Thu, 6 Mar 2014 04:17:17 +0000 (23:17 -0500)
1  2 
Makefile.am
docs/pending-release-notes
h/mhparse.h
h/prototypes.h
sbr/utils.c
test/mhfixmsg/test-mhfixmsg
uip/mhfixmsg.c
uip/mhn.c
uip/mhparse.c
uip/mhshowsbr.c
uip/mhstoresbr.c

diff --combined Makefile.am
index 587dfb84ddede043bef78b16e9e05664e1f53ca9,d9b3229f1615fdd96a04a63f1b10439fa0cadb51..a73a4215a9ade950d412a4f56dfbf887ba5b8182
@@@ -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)
  
index a4db0a1649097763f6e7bcd23bd81d3fddb3db7b,1b90141d7af10a162d6aabdc2eb498128dfb109a..780b2abea2a83e61f64bae16433d0a823c43d37f
@@@ -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
    {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 72188c6d42d903d2f040bacde1022a7d71807c7a,9b058e6887bc7bb5af0b4df60bd50065ff934215..7ade8a3c842905cbbfe30fad04e684b636676a96
@@@ -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 621146c3f401850dd2540fcacc211cff9202d4f1,03c783aa010296ce81ccf4b4e57088a769d55412..f8adbac56888a82e517c44e446ddb92a9c54f1f2
@@@ -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 59ab7c72754bf6c0bcd3bb85e2393ae22de5d517,989a9c8b2224f6211e28c022f741c70c0ac85901..4405f4c95a5c52d5cf406d52cc95535479b71781
@@@ -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;
+ }
index d3516175ff3e6fa9ade0ace0752ec97953e67e33,aa8c9514e646d6e3d8096c5e5144697ef9912903..d60b9d768d7942021bfa080e69971061925a2167
@@@ -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" <<EOF
  To: recipient@example.com
  From: sender@example.com
 -Subject: mhfixmsg textcodeset test
 +Subject: mhfixmsg textcharset test
  MIME-Version: 1.0
  Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
  
@@@ -699,7 -699,7 +699,7 @@@ EO
  cat >`mhpath new` <<EOF
  To: recipient@example.com
  From: sender@example.com
 -Subject: mhfixmsg textcodeset test
 +Subject: mhfixmsg textcharset test
  MIME-Version: 1.0
  Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
  
@@@ -713,13 -713,17 +713,13 @@@ vbI9vAo
  ------- =_aaaaaaaaaa0--
  EOF
  
 -set +e
 -run_prog mhfixmsg last -textcodeset utf-8 -outfile "$actual" 2>"$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" <<EOF
  Date: Fri, 13 May 2011 08:21:12 -0500
  Content-Type: multipart/alternative;
--        boundary="----=_NextPart_000_1781A1A_01CC1147.81EBA8D4"
++      boundary="----=_NextPart_000_1781A1A_01CC1147.81EBA8D4"
  Content-Transfer-Encoding: 8bit
  MIME-Version: 1.0
  From: <sender@example.com>
diff --combined uip/mhfixmsg.c
index 47c03051c2a32848df7c99a7cb5b2616effbbc88,07433377ef2003cbff00906753d3c3a43ebe0468..f00fc97d06a71f5329f4270991abefe14515cb02
  #include <h/utils.h>
  #include <h/signals.h>
  #include <fcntl.h>
 -#ifdef HAVE_ICONV
 -#   include <iconv.h>
 -#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; }
  
              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];
              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);
  }
  
  
 -/* 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)
          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);
  
  /* 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;
  
                  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;
  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;
              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",
          }
      }
  
 -    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;
  
             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;
      }
  
              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;
  
  }
  
  
 -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 8cd3acddbff1590cebeb38c4ea37b302b32c6aa8,220a73d5aea5285472b0ddcb9a7de40e2d04fffe..c0b7e7816c7a4e2eb58d569bed82c7cba0b63169
+++ 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));
       * 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
      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 9873e0b184d679f420283f28d1b924939d2b5684,943e4233325b0a2dcf706d2a78283c94aa6cbf51..ec7db7a48895fbd7fd3a0444408bbed21253d12e
@@@ -16,6 -16,9 +16,9 @@@
  #include <h/mime.h>
  #include <h/mhparse.h>
  #include <h/utils.h>
+ #ifdef HAVE_ICONV
+ # include <iconv.h>
+ #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++)
                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;
            }
        }
        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.
  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);
      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++)
      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 != '/') {
      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;
      }
  
      /*
       * 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;
        
        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;
  
                 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;
        case '\0':
  invalid:
        advise (NULL, "invalid comment in message %s's %s: field",
-               filename, istype ? TYPE_FIELD : VRSN_FIELD);
+               filename, fieldname);
        return NOTOK;
  
        case '\\':
      }
      *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;
      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;
       * 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)
                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;
                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))
            *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;
          }
  
            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 */
        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*<N>=part-N-of-a-parameter-value
+        * name*<N>*=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;
  }
  
-     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;
-         }
++/*
++ * 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) {
-     /* 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";
++    static char *ret_charset = NULL;
++
++    if (ret_charset != NULL) {
++      free(ret_charset);
 +    }
 +
-     return *src_charset;
++    ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
 +
- /* 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. */
++    return ret_charset ? ret_charset : "US-ASCII";
 +}
 +
 +
+ /*
+  * Create a string based on a list of output parameters.  Assume that this
+  * parameter string will be appended to an existing header, so start out
+  * with the separator (;).  Perform RFC 2231 encoding when necessary.
+  */
+ 
  char *
- 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 eabf387cd54a7e869ee3cd65ac3705b05396c9b6,bb0a646ec0bb98578b2dd407df9800d0eb51245c..295f43e8b7661a65b83761090e79f4f393080d31
@@@ -17,9 -17,6 +17,9 @@@
  #include <h/mime.h>
  #include <h/mhparse.h>
  #include <h/utils.h>
 +#ifdef HAVE_ICONV
 +#   include <iconv.h>
 +#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 const char *parameter_value (CI, const char *);
 +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 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);
  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)
        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);
  
        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;
        }
      }
  
 -    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);
  
            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);
       * 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) {
            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"))
        }
      }
  
 -    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;
  
            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':
                /* insert character % */
                goto raw;
  
-                   const char *value;
 +          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);
-                   value = parameter_value(ci, param);
++                  char *value;
 +
 +                  (void) strncpy(param, cp + 1, param_len);
 +                  param[param_len] = '\0';
++                  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';
            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;
      }
  
        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 89a2f27854fe4a1d89481f72a1e26a76eb5ab02d,156411b7e15b013f730ff06e8b716c5287688328..fc2655425e7ad0a854410600d3047ef5e97cfa88
  #include <h/mhparse.h>
  #include <h/utils.h>
  
 +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
   */
  
  void
 -store_all_messages (CT *cts)
 +store_all_messages (mhstoreinfo_t info)
  {
      CT ct, *ctp;
      char *cp;
       * 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 ();
   */
  
  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;
   */
  
  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);
   */
  
  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);
  }
  
  
   */
  
  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);
  
      /*
       */
      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;
            }
        }
  
                                  : (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);
        }
      }
  
 -    return store_content (ct, NULL);
 +    return store_content (ct, NULL, info);
  }
  
  
   */
  
  static int
 -store_multi (CT ct)
 +store_multi (CT ct, mhstoreinfo_t info)
  {
      int       result;
      struct multipart *m = (struct multipart *) ct->c_ctparams;
                   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)
   */
  
  static int
 -store_partial (CT ct)
 +store_partial (CT ct, mhstoreinfo_t info)
  {
      int       cur, hi, i;
      CT p, *ctp, *ctq;
        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;
        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;
  
      for (; *ctq; ctq++) {
        p = *ctq;
 -      if (store_content (p, ct) == NOTOK)
 +      if (store_content (p, ct, info) == NOTOK)
            goto losing;
      }
  
   */
  
  static int
 -store_external (CT ct)
 +store_external (CT ct, mhstoreinfo_t info)
  {
      int       result = NOTOK;
      struct exbody *e = (struct exbody *) ct->c_ctparams;
       * Check if the parameters for the external body
       * specified a filename.
       */
 -    if (autosw) {
 +    if (info->autosw) {
        char *cp;
  
        if ((cp = e->eb_name)
           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;
         * 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;
  
      /*
       * 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);
      }
  
      /*
       * 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;
      }
  
      advise (NULL, "will not overwrite %s, too many versions", buffer);
      free (buffer);
      buffer = NULL;
 -    ++files_not_clobbered;
    }
  
    return buffer;
  
  
  static char *
 -clobber_check (char *original_file) {
 +clobber_check (char *original_file, mhstoreinfo_t info) {
    /* clobber policy        return value
     * --------------        ------------
     *   -always             original_file
    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;
  
      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;
  
          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))) {
              case NMH_NO:
                free (file);
                file = NULL;
 -              ++files_not_clobbered;
 +              ++info->files_not_clobbered;
                break;
              case NMH_RENAME: {
                char buf[PATH_MAX];
                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) {
            advise (NULL, "will not overwrite %s with -clobber never", file);
            free (file);
            file = NULL;
 -          ++files_not_clobbered;
 +          ++info->files_not_clobbered;
          }
          break;
      }