From: Ken Hornstein Date: Thu, 6 Mar 2014 04:17:17 +0000 (-0500) Subject: Merge branch 'extended-params' X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/28c3595a77a8c942bee1057085776dad0b3d53f4 Merge branch 'extended-params' --- 28c3595a77a8c942bee1057085776dad0b3d53f4 diff --cc Makefile.am index 587dfb84,d9b3229f..a73a4215 --- a/Makefile.am +++ b/Makefile.am @@@ -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) diff --cc docs/pending-release-notes index a4db0a16,1b90141d..780b2abe --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@@ -74,10 -74,8 +74,12 @@@ NEW FEATURE Nmh-Attachment). - The default Content-Transfer-Encoding for text parts is now 8bit. - mhbuild(1) now supports a selectable Content-Transfer-Encoding +- If nmh was configured with iconv(3) support, mhshow will convert, if + necessary, the charset of text/plain content to match the user's + locale setting. +- Added support for %{charset} display string escape to mhshow(1). + - The MIME parsing and generating routines now support RFC 2231 extended + parameter information. ----------------- OBSOLETE FEATURES diff --cc h/mhparse.h index 72188c6d,9b058e68..7ade8a3c --- a/h/mhparse.h +++ b/h/mhparse.h @@@ -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 621146c3,03c783aa..f8adbac5 --- a/h/prototypes.h +++ b/h/prototypes.h @@@ -46,7 -46,19 +46,20 @@@ void cpydgst (int, int, char *, char *) char *cpytrim (const char *); int decode_rfc2047 (char *, char *, size_t); void discard (FILE *); +char *upcase (const char *); + + /* + * Decode two characters into their quoted-printable representation. + * + * Arguments are: + * + * byte1 - First character of Q-P representation + * byte2 - Second character of Q-P representation + * + * Returns the decoded value, -1 if the conversion failed. + */ + int decode_qp(unsigned char byte1, unsigned char byte2); + int default_done (int); /* diff --cc sbr/utils.c index 59ab7c72,989a9c8b..4405f4c9 --- a/sbr/utils.c +++ b/sbr/utils.c @@@ -353,15 -353,22 +353,36 @@@ nmh_init(const char *argv0, int read_co } } + +/* Returns copy of argument str with all characters converted to upper + case, and trimmed whitespace (see cpytrim()) . */ +char * +upcase (const char *str) { + char *up = cpytrim (str); + char *cp; + + for (cp = up; *cp; ++cp) { *cp = toupper ((unsigned char) *cp); } + + return up; +} ++ ++ + /* + * Scan for any 8-bit characters. Return 1 if they exist. + * + * Scan up until the given endpoint (but not the actual endpoint itself). + * If the endpoint is NULL, scan until a '\0' is reached. + */ + + int + contains8bit(const char *start, const char *end) + { + if (! start) + return 0; + + while (*start != '\0' && (!end || (start < end))) + if (! isascii((unsigned char) *start++)) + return 1; + + return 0; + } diff --cc test/mhfixmsg/test-mhfixmsg index d3516175,aa8c9514..d60b9d76 --- a/test/mhfixmsg/test-mhfixmsg +++ b/test/mhfixmsg/test-mhfixmsg @@@ -770,7 -774,7 +770,7 @@@ f cat >"$expected" < diff --cc uip/mhfixmsg.c index 47c03051,07433377..f00fc97d --- a/uip/mhfixmsg.c +++ b/uip/mhfixmsg.c @@@ -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]; @@@ -668,10 -675,10 +668,22 @@@ if (strcasecmp (TYPE_FIELD, np)) { fprintf (fpout, "%s:%s", np, vp); } else { -- char *new_boundary = update_attr (vp, "boundary=", boundary); -- -- fprintf (fpout, "%s:%s\n", np, new_boundary); -- free (new_boundary); ++ char *new_ctline, *new_params; ++ ++ replace_param(&ct->c_ctinfo.ci_first_pm, ++ &ct->c_ctinfo.ci_last_pm, "boundary", ++ boundary, 0); ++ ++ new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/", ++ ct->c_ctinfo.ci_subtype, NULL); ++ new_params = output_params(strlen(TYPE_FIELD) + ++ strlen(new_ctline) + 1, ++ ct->c_ctinfo.ci_first_pm, NULL, 0); ++ fprintf (fpout, "%s:%s%s\n", np, new_ctline, ++ new_params ? new_params : ""); ++ free(new_ctline); ++ if (new_params) ++ free(new_params); } free (vp); diff --cc uip/mhn.c index 8cd3acdd,220a73d5..c0b7e781 --- a/uip/mhn.c +++ 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 9873e0b1,943e4233..ec7db7a4 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@@ -3352,63 -3647,560 +3647,606 @@@ bad_quote return OK; } ++/* ++ * Return the charset for a particular content type. Return pointer is ++ * only valid until the next call to content_charset(). ++ */ + +char * +content_charset (CT ct) { - const char *const charset = "charset"; - char *default_charset = NULL; - CI ctinfo = &ct->c_ctinfo; - char **ap, **vp; - char **src_charset = NULL; - - for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) { - if (! strcasecmp (*ap, charset)) { - src_charset = vp; - break; - } ++ static char *ret_charset = NULL; ++ ++ if (ret_charset != NULL) { ++ free(ret_charset); + } + - /* RFC 2045, Sec. 5.2: default to us-ascii. */ - if (src_charset == NULL) src_charset = &default_charset; - if (*src_charset == NULL) *src_charset = "US-ASCII"; ++ ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0); + - return *src_charset; ++ return ret_charset ? ret_charset : "US-ASCII"; +} + + - /* Change the value of a name=value pair in a header field body. - If the name isn't there, append them. In any case, a new - string will be allocated and must be free'd by the caller. - Trims any trailing newlines. */ + /* + * Create a string based on a list of output parameters. Assume that this + * parameter string will be appended to an existing header, so start out + * with the separator (;). Perform RFC 2231 encoding when necessary. + */ + char * - update_attr (char *body, const char *name, const char *value) { - char *bp = nmh_strcasestr (body, name); - char *new_body; + output_params(size_t initialwidth, PM params, int *offsetout, int external) + { + char *paramout = NULL; + char line[CPERLIN * 2], *q; + int curlen, index, cont, encode, i; + size_t valoff, numchars; + + while (params != NULL) { + encode = 0; + index = 0; + valoff = 0; + q = line; + + if (external && strcasecmp(params->pm_name, "body") == 0) + continue; - if (bp) { - char *other_attrs = strchr (bp, ';'); + if (strlen(params->pm_name) > CPERLIN) { + advise(NULL, "Parameter name \"%s\" is too long", params->pm_name); + if (paramout) + free(paramout); + return NULL; + } - *(bp + strlen (name)) = '\0'; - new_body = concat (body, "\"", value, "\"", NULL); + curlen = param_len(params, index, valoff, &encode, &cont, &numchars); - if (other_attrs) { - char *cp; + /* + * Loop until we get a parameter that fits within a line. We + * assume new lines start with a tab, so check our overflow based + * on that. + */ - /* Trim any trailing newlines. */ - for (cp = &other_attrs[strlen (other_attrs) - 1]; - cp > other_attrs && *cp == '\n'; - *cp-- = '\0') continue; - new_body = add (other_attrs, new_body); - } + while (cont) { + *q++ = ';'; + *q++ = '\n'; + *q++ = '\t'; + + /* + * At this point we're definitely continuing the line, so + * be sure to include the parameter name and section index. + */ + + q += snprintf(q, sizeof(line) - (q - line), "%s*%d", + params->pm_name, index); + + /* + * Both of these functions do a NUL termination + */ + + if (encode) + i = encode_param(params, q, sizeof(line) - (q - line), + numchars, valoff, index); + else + i = normal_param(params, q, sizeof(line) - (q - line), + numchars, valoff); + + if (i == 0) { + if (paramout) + free(paramout); + return NULL; + } + + valoff += numchars; + index++; + curlen = param_len(params, index, valoff, &encode, &cont, + &numchars); + q = line; + + /* + * "line" starts with a ;\n\t, so that doesn't count against + * the length. But add 8 since it starts with a tab; that's + * how we end up with 5. + */ + + initialwidth = strlen(line) + 5; + + /* + * At this point the line should be built, so add it to our + * current output buffer. + */ + + paramout = add(line, paramout); + } + + /* + * If this won't fit on the line, start a new one. Save room in + * case we need a semicolon on the end + */ + + if (initialwidth + curlen > CPERLIN - 1) { + *q++ = ';'; + *q++ = '\n'; + *q++ = '\t'; + initialwidth = 8; + } else { + *q++ = ';'; + *q++ = ' '; + initialwidth += 2; + } + + /* + * At this point, we're either finishing a contined parameter, or + * we're working on a new one. + */ + + if (index > 0) { + q += snprintf(q, sizeof(line) - (q - line), "%s*%d", + params->pm_name, index); + } else { + strncpy(q, params->pm_name, sizeof(line) - (q - line)); + q += strlen(q); + } + + if (encode) + i = encode_param(params, q, sizeof(line) - (q - line), + strlen(params->pm_value + valoff), valoff, index); + else + i = normal_param(params, q, sizeof(line) - (q - line), + strlen(params->pm_value + valoff), valoff); + + if (i == 0) { + if (paramout) + free(paramout); + return NULL; + } + + paramout = add(line, paramout); + initialwidth += strlen(line); + + params = params->pm_next; + } + + if (offsetout) + *offsetout = initialwidth; + + return paramout; + } + + /* + * Calculate the size of a parameter. + * + * Arguments include + * + * pm - The parameter being output + * index - If continuing the parameter, the index of the section + * we're on. + * valueoff - The current offset into the parameter value that we're + * working on (previous sections have consumed valueoff bytes). + * encode - Set if we should perform encoding on this parameter section + * (given that we're consuming bytesfit bytes). + * cont - Set if the remaining data in value will not fit on a single + * line and will need to be continued. + * bytesfit - The number of bytes that we can consume from the parameter + * value and still fit on a completely new line. The + * calculation assumes the new line starts with a tab, + * includes the parameter name and any encoding, and fits + * within CPERLIN bytes. Will always be at least 1. + */ + + static size_t + param_len(PM pm, int index, size_t valueoff, int *encode, int *cont, + size_t *bytesfit) + { + char *start = pm->pm_value + valueoff, *p, indexchar[32]; + size_t len = 0, fit = 0; + int fitlimit = 0, eightbit, maxfit; + + *encode = 0; + + /* + * Add up the length. First, start with the parameter name. + */ + + len = strlen(pm->pm_name); + + /* + * Scan the parameter value and see if we need to do encoding for this + * section. + */ + + eightbit = contains8bit(start, NULL); + + /* + * Determine if we need to encode this section. Encoding is necessary if: + * + * - There are any 8-bit characters at all and we're on the first + * section. + * - There are 8-bit characters within N bytes of our section start. + * N is calculated based on the number of bytes it would take to + * reach CPERLIN. Specifically: + * 8 (starting tab) + + * strlen(param name) + + * 4 ('* for section marker, '=', opening/closing '"') + * strlen (index) + * is the number of bytes used by everything that isn't part of the + * value. So that gets subtracted from CPERLIN. + */ + + snprintf(indexchar, sizeof(indexchar), "%d", index); + maxfit = CPERLIN - (12 + len + strlen(indexchar)); + if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) { + *encode = 1; + } + + len++; /* Add in equal sign */ + + if (*encode) { + /* + * We're using maxfit as a marker for how many characters we can + * fit into the line. Bump it by two because we're not using quotes + * when encoding. + */ + + maxfit += 2; + + /* + * If we don't have a charset or language tag in this parameter, + * add them now. + */ + + if (! pm->pm_charset) + pm->pm_charset = getcpy(write_charset_8bit()); + if (! pm->pm_lang) + pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */ + + len++; /* For the encoding marker */ + maxfit--; + if (index == 0) { + int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2; + len += enclen; + maxfit-= enclen; + } else { + /* + * We know we definitely need to include an index. maxfit already + * includes the section marker. + */ + len += strlen(indexchar); + } + for (p = start; *p != '\0'; p++) { + if (isparamencode(*p)) { + len += 3; + maxfit -= 3; + } else { + len++; + maxfit--; + } + /* + * Just so there's no confusion: maxfit is counting OUTPUT + * characters (post-encoding). fit is counting INPUT characters. + */ + if (! fitlimit && maxfit >= 0) + fit++; + else if (! fitlimit) + fitlimit++; + } } else { - char *cp; + /* + * Calculate the string length, but add room for quoting \ + * and " if necessary. Also account for quotes at beginning + * and end. + */ + for (p = start; *p != '\0'; p++) { + switch (*p) { + case '"': + case '\\': + len++; + maxfit--; + /* FALL THROUGH */ + default: + len++; + maxfit--; + } + if (! fitlimit && maxfit >= 0) + fit++; + else if (! fitlimit) + fitlimit++; + } + + len += 2; + } + + if (fit < 1) + fit = 1; + + *cont = fitlimit; + *bytesfit = fit; + + return len; + } + + /* + * Output an encoded parameter string. + */ + + static size_t + encode_param(PM pm, char *output, size_t len, size_t valuelen, + size_t valueoff, int index) + { + size_t outlen = 0, n; + char *endptr = output + len, *p; + + /* + * First, output the marker for an encoded string. + */ + + *output++ = '*'; + *output++ = '='; + outlen += 2; - /* Append name/value pair, after first removing a final newline - and (extraneous) semicolon. */ - if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0'; - if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0'; - new_body = concat (body, "; ", name, "\"", value, "\"", NULL); + /* + * If the index is 0, output the character set and language tag. + * If theses were NULL, they should have already been filled in + * by param_len(). + */ + + if (index == 0) { + n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset, + pm->pm_lang); + output += n; + outlen += n; + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + /* + * Copy over the value, encoding if necessary + */ + + p = pm->pm_value + valueoff; + while (valuelen-- > 0) { + if (isparamencode(*p)) { + n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++); + output += n; + outlen += n; + } else { + *output++ = *p++; + outlen++; + } + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + *output = '\0'; + + return outlen; + } + + /* + * Output a "normal" parameter, without encoding. Be sure to escape + * quotes and backslashes if necessary. + */ + + static size_t + normal_param(PM pm, char *output, size_t len, size_t valuelen, + size_t valueoff) + { + size_t outlen = 0; + char *endptr = output + len, *p; + + *output++ = '='; + *output++ = '"'; + outlen += 2; + + p = pm->pm_value + valueoff; + + while (valuelen-- > 0) { + switch (*p) { + case '\\': + case '"': + *output++ = '\\'; + outlen++; + default: + *output++ = *p++; + outlen++; + } + if (output > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + } + + if (output - 2 > endptr) { + advise(NULL, "Internal error: parameter buffer overflow"); + return 0; + } + + *output++ = '"'; + *output++ = '\0'; + + return outlen + 1; + } + + /* + * Add a parameter to the parameter linked list + */ + + PM + add_param(PM *first, PM *last, char *name, char *value, int nocopy) + { + PM pm = mh_xmalloc(sizeof(*pm)); + + memset(pm, 0, sizeof(*pm)); + + pm->pm_name = nocopy ? name : getcpy(name); + pm->pm_value = nocopy ? value : getcpy(value); + + if (*first) { + (*last)->pm_next = pm; + *last = pm; + } else { + *first = pm; + *last = pm; + } + + return pm; + } + ++/* ++ * Either replace a current parameter with a new value, or add the parameter ++ * to the parameter linked list. ++ */ ++ ++PM ++replace_param(PM *first, PM *last, char *name, char *value, int nocopy) ++{ ++ PM pm; ++ ++ for (pm = *first; pm != NULL; pm = pm->pm_next) { ++ if (strcasecmp(name, pm->pm_name) == 0) { ++ /* ++ * If nocopy is set, it's assumed that we own both name ++ * and value. We don't need name, so we discard it now. ++ */ ++ if (nocopy) ++ free(name); ++ free(pm->pm_value); ++ pm->pm_value = nocopy ? value : getcpy(value); ++ return pm; ++ } + } + - return new_body; ++ return add_param(first, last, name, value, nocopy); ++} ++ + /* + * Retrieve a parameter value from a parameter linked list. If the parameter + * value needs converted to the local character set, do that now. + */ + + char * + get_param(PM first, const char *name, char replace, int fetchonly) + { + while (first != NULL) { + if (strcasecmp(name, first->pm_name) == 0) { + if (fetchonly) + return first->pm_value; + else + return getcpy(get_param_value(first, replace)); + } + first = first->pm_next; + } + + return NULL; + } + + /* + * Return a parameter value, converting to the local character set if + * necessary + */ + + char *get_param_value(PM pm, char replace) + { + static char buffer[4096]; /* I hope no parameters are larger */ + size_t bufsize = sizeof(buffer); + #ifdef HAVE_ICONV + size_t inbytes; + int utf8; + iconv_t cd; + ICONV_CONST char *p; + #endif /* HAVE_ICONV */ + char *q; + + /* + * If we don't have a character set indicated, it's assumed to be + * US-ASCII. If it matches our character set, we don't need to convert + * anything. + */ + + if (!pm->pm_charset || check_charset(pm->pm_charset, + strlen(pm->pm_charset))) { + return pm->pm_value; + } + + /* + * In this case, we need to convert. If we have iconv support, use + * that. Otherwise, go through and simply replace every non-ASCII + * character with the substitution character. + */ + + #ifdef HAVE_ICONV + q = buffer; + bufsize = sizeof(buffer); + utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0; + + cd = iconv_open(get_charset(), pm->pm_charset); + if (cd == (iconv_t) -1) { + goto noiconv; + } + + inbytes = strlen(pm->pm_value); + p = pm->pm_value; + + while (inbytes) { + if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) { + if (errno != EILSEQ) { + iconv_close(cd); + goto noiconv; + } + /* + * Reset shift state, substitute our character, + * try to restart conversion. + */ + + iconv(cd, NULL, NULL, &q, &bufsize); + + if (bufsize == 0) { + iconv_close(cd); + goto noiconv; + } + *q++ = replace; + bufsize--; + if (bufsize == 0) { + iconv_close(cd); + goto noiconv; + } + if (utf8) { + for (++p, --inbytes; + inbytes > 0 && (((unsigned char) *q) & 0xc0) == 0x80; + ++p, --inbytes) + continue; + } else { + p++; + inbytes--; + } + } + } + + iconv_close(cd); + + if (bufsize == 0) + q--; + *q = '\0'; + + return buffer; + #endif /* HAVE_ICONV */ + + noiconv: + /* + * Take everything non-ASCII and substituite the replacement character + */ + + q = buffer; + bufsize = sizeof(buffer); + for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) { + if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p)) + *q = *p; + else + *q = replace; + } + + *q = '\0'; + + return buffer; } diff --cc uip/mhshowsbr.c index eabf387c,bb0a646e..295f43e8 --- a/uip/mhshowsbr.c +++ b/uip/mhshowsbr.c @@@ -70,10 -62,6 +66,9 @@@ static int show_multi_aux (CT, int, int static int show_message_rfc822 (CT, int, int); static int show_partial (CT, int, int); static int show_external (CT, int, int); +static int parse_display_string (CT, char *, int *, int *, int *, int *, char *, + char *, size_t, int multipart); +static int convert_content_charset (CT, char **); - static const char *parameter_value (CI, const char *); static void intrser (int); @@@ -899,41 -915,6 +895,42 @@@ parse_display_string (CT ct, char *cp, /* insert character % */ goto raw; + case '{' : { + const char *closing_brace = strchr(cp, '}'); + + if (closing_brace) { + const size_t param_len = closing_brace - cp - 1; + char *param = mh_xmalloc(param_len + 1); - const char *value; ++ char *value; + + (void) strncpy(param, cp + 1, param_len); + param[param_len] = '\0'; - value = parameter_value(ci, param); ++ value = get_param(ci->ci_first_pm, param, '?', 0); + free(param); + + cp += param_len + 1; /* Skip both braces, too. */ + + if (value) { + /* %{param} is set in the Content-Type header. + After the break below, quote it if necessary. */ + (void) strncpy(bp, value, buflen); ++ free(value); + } else { + /* %{param} not found, so skip it completely. cp + was advanced above. */ + continue; + } + } else { + /* This will get confused if there are multiple %{}'s, + but its real purpose is to avoid doing bad things + above if a closing brace wasn't found. */ + admonish(NULL, + "no closing brace for display string escape %s", + cp); + } + break; + } + default: *bp++ = *--cp; *bp = '\0'; @@@ -1045,154 -988,41 +1042,162 @@@ raw } -/* - * 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; } diff --cc uip/mhstoresbr.c index 89a2f278,156411b7..fc265542 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@@ -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 */