]> 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 --cc Makefile.am
index 587dfb84ddede043bef78b16e9e05664e1f53ca9,d9b3229f1615fdd96a04a63f1b10439fa0cadb51..a73a4215a9ade950d412a4f56dfbf887ba5b8182
@@@ -71,13 -71,14 +72,14 @@@ TESTS = test/ali/test-ali test/anno/tes
        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 \
@@@ -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)
  
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
diff --cc h/mhparse.h
index 72188c6d42d903d2f040bacde1022a7d71807c7a,9b058e6887bc7bb5af0b4df60bd50065ff934215..7ade8a3c842905cbbfe30fad04e684b636676a96
@@@ -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 --cc 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 --cc 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
@@@ -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 --cc uip/mhfixmsg.c
index 47c03051c2a32848df7c99a7cb5b2616effbbc88,07433377ef2003cbff00906753d3c3a43ebe0468..f00fc97d06a71f5329f4270991abefe14515cb02
@@@ -85,7 -88,8 +85,7 @@@ int mhfixmsgsbr (CT *, const fix_transf
  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);
@@@ -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);
diff --cc uip/mhn.c
index 8cd3acddbff1590cebeb38c4ea37b302b32c6aa8,220a73d5aea5285472b0ddcb9a7de40e2d04fffe..c0b7e7816c7a4e2eb58d569bed82c7cba0b63169
+++ b/uip/mhn.c
@@@ -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 *);
diff --cc uip/mhparse.c
index 9873e0b184d679f420283f28d1b924939d2b5684,943e4233325b0a2dcf706d2a78283c94aa6cbf51..ec7db7a48895fbd7fd3a0444408bbed21253d12e
@@@ -3352,63 -3647,560 +3647,606 @@@ bad_quote
      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 --cc uip/mhshowsbr.c
index eabf387cd54a7e869ee3cd65ac3705b05396c9b6,bb0a646ec0bb98578b2dd407df9800d0eb51245c..295f43e8b7661a65b83761090e79f4f393080d31
@@@ -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);
  
  
@@@ -899,41 -915,6 +895,42 @@@ parse_display_string (CT ct, char *cp, 
                /* 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';
  }
  
  
 -/*
 - * 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;
  }
  
  
index 89a2f27854fe4a1d89481f72a1e26a76eb5ab02d,156411b7e15b013f730ff06e8b716c5287688328..fc2655425e7ad0a854410600d3047ef5e97cfa88
@@@ -225,9 -191,8 +225,8 @@@ store_generic (CT ct, mhstoreinfo_t inf
   */
  
  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 */