From 95ad91ca30e1cda09efe454d12ed790a60661f56 Mon Sep 17 00:00:00 2001 From: Ken Hornstein Date: Thu, 27 Feb 2014 21:38:09 -0500 Subject: [PATCH] Support complete encoding for encoding Content-Type parameters. Start work on retrieving parameters and converting them to the correct charset. --- Makefile.am | 14 ++--- h/mhparse.h | 26 +++++++- test/mhbuild/test-ext-params | 15 +++-- uip/mhbuildsbr.c | 60 +++--------------- uip/mhparse.c | 115 ++++++++++++++++++++++++++++++++++- 5 files changed, 159 insertions(+), 71 deletions(-) diff --git a/Makefile.am b/Makefile.am index bf933ef3..b977d7ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -289,7 +289,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 +321,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,12 +330,12 @@ uip_mhfixmsg_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) 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 \ uip/md5.c -uip_mhn_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) +uip_mhn_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_mhparam_SOURCES = uip/mhparam.c uip_mhparam_LDADD = $(LDADD) $(POSTLINK) @@ -346,12 +346,12 @@ uip_mhpath_LDADD = $(LDADD) $(POSTLINK) uip_mhshow_SOURCES = uip/mhshow.c uip/mhparse.c uip/mhcachesbr.c \ uip/mhshowsbr.c uip/mhlistsbr.c uip/mhmisc.c \ uip/mhfree.c uip/md5.c -uip_mhshow_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) +uip_mhshow_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_mhstore_SOURCES = uip/mhstore.c uip/mhparse.c uip/mhcachesbr.c \ uip/mhshowsbr.c uip/mhlistsbr.c uip/mhstoresbr.c \ uip/mhmisc.c uip/mhfree.c uip/md5.c -uip_mhstore_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK) +uip_mhstore_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_msgchk_SOURCES = uip/msgchk.c uip/popsbr.c uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(POSTLINK) @@ -430,7 +430,7 @@ uip_mhl_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) 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_post_SOURCES = uip/post.c uip/aliasbr.c uip_post_LDADD = mts/libmts.a $(LDADD) $(SASLLIB) $(TLSLIB) $(POSTLINK) diff --git a/h/mhparse.h b/h/mhparse.h index 0d4ef467..d6afe095 100644 --- a/h/mhparse.h +++ b/h/mhparse.h @@ -376,11 +376,14 @@ int parse_header_attrs (const char *filename, const char *fieldname, * 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); +char *output_params(size_t initialwidth, PM params, int *offsetout, + int external); /* * Add a parameter to the parameter linked list. @@ -396,4 +399,23 @@ char *output_params(size_t initialwidth, PM params, int *offsetout); */ PM add_param(PM *first, PM *last, const char *name, const char *value); +/* + * 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); + extern int checksw; /* Add Content-MD5 field */ diff --git a/test/mhbuild/test-ext-params b/test/mhbuild/test-ext-params index 5e4a681b..c962cf43 100755 --- a/test/mhbuild/test-ext-params +++ b/test/mhbuild/test-ext-params @@ -101,17 +101,18 @@ EOF check "$draft" "$expected" # -# Try out both! +# Try out attach with a filename with 8-bit characters # +cp "${srcdir}/test/mhbuild/tiny.jpg" "$MH_TEST_DIR/tïny.jpg" + cat > "$draft" < cc: Fcc: +outbox +Attach: $MH_TEST_DIR/tïny.jpg ------ This is a test message -#image/jpeg {attachment; filename="This is an ëxample of a rather long filename that is longer than would fit on a normal line.jpg"} \ -${srcdir}/test/mhbuild/tiny.jpg EOF run_prog mhbuild "$draft" @@ -129,11 +130,9 @@ Content-Type: text/plain; charset="us-ascii" This is a test message ------- =_aaaaaaaaaa0 -Content-Type: image/jpeg -Content-Disposition: attachment; - filename*0*=UTF-8''This%20is%20an%20%C3%ABxample%20of%20a%20rather; - filename*1=" long filename that is longer than would fit on a norma"; - filename*2="l line.jpg" +Content-Type: image/jpeg; name*=UTF-8''t%C3%AFny.jpg +Content-Description: =?UTF-8?B?dMOvbnkuanBn?= +Content-Disposition: attachment; filename*=UTF-8''t%C3%AFny.jpg Content-Transfer-Encoding: base64 /9g= diff --git a/uip/mhbuildsbr.c b/uip/mhbuildsbr.c index 0077f6da..0d20a671 100644 --- a/uip/mhbuildsbr.c +++ b/uip/mhbuildsbr.c @@ -1528,7 +1528,6 @@ build_headers (CT ct, int header_encoding) int cc, mailbody, extbody, len; char *np, *vp, buffer[BUFSIZ]; CI ci = &ct->c_ctinfo; - PM pm; /* * If message is type multipart, then add the multipart @@ -1568,59 +1567,16 @@ build_headers (CT ct, int header_encoding) * Append the attribute/value pairs to * the end of the Content-Type line. */ - for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { - if (mailbody && !strcasecmp (pm->pm_name, "body")) - continue; - - vp = add (";", vp); - len++; - - /* - * According to RFC 2017, if we have a URL longer than 40 characters - * we have to break it across multiple lines - */ - - if (extbody && strcasecmp (pm->pm_name, "url") == 0) { - char *value = pm->pm_value; - - /* 7 here refers to " url=\"\"" */ - if (len + 1 + (cc = (min(MAXURLTOKEN, strlen(value)) + 7)) >= - CPERLIN) { - vp = add ("\n\t", vp); - len = 8; - } else { - vp = add (" ", vp); - len++; - } - - vp = add ("url=\"", vp); - len += 5; - while (strlen(value) > MAXURLTOKEN) { - strncpy(buffer, value, MAXURLTOKEN); - buffer[MAXURLTOKEN] = '\0'; - vp = add (buffer, vp); - vp = add ("\n\t", vp); - value += MAXURLTOKEN; - len = 8; - } + if (ci->ci_first_pm) { + char *s = output_params(len, ci->ci_first_pm, &len, mailbody); - vp = add (value, vp); - vp = add ("\"", vp); - len += strlen(value) + 1; - continue; - } + if (!s) + adios(NULL, "Internal error: failed outputting Content-Type " + "parameters"); - snprintf (buffer, sizeof(buffer), "%s=\"%s\"", pm->pm_name, pm->pm_value); - if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { - vp = add ("\n\t", vp); - len = 8; - } else { - vp = add (" ", vp); - len++; - } - vp = add (buffer, vp); - len += cc; + vp = add (s, vp); + free(s); } /* @@ -1672,7 +1628,7 @@ build_headers (CT ct, int header_encoding) } else if (ct->c_dispo_type) { vp = concat (" ", ct->c_dispo_type, NULL); len = strlen(DISPO_FIELD) + strlen(vp) + 1; - np = output_params(len, ct->c_dispo_first, NULL); + np = output_params(len, ct->c_dispo_first, NULL, 0); vp = add(np, vp); vp = add("\n", vp); if (np) diff --git a/uip/mhparse.c b/uip/mhparse.c index 5bbaf449..a9ad26e7 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -16,6 +16,9 @@ #include #include #include +#ifdef HAVE_ICONV +# include +#endif /* HAVE_ICONV */ extern int debugsw; @@ -2298,7 +2301,7 @@ open7Bit (CT ct, char **file) 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); - buffer = output_params(len, ci->ci_first_pm, &len); + buffer = output_params(len, ci->ci_first_pm, &len, 0); if (buffer) { fputs (buffer, ce->ce_fp); @@ -3433,7 +3436,7 @@ bad_quote: */ char * -output_params(size_t initialwidth, PM params, int *offsetout) +output_params(size_t initialwidth, PM params, int *offsetout, int external) { char *paramout = NULL; char line[CPERLIN * 2], *q; @@ -3446,6 +3449,9 @@ output_params(size_t initialwidth, PM params, int *offsetout) valoff = 0; q = line; + if (external && strcasecmp(params->pm_name, "body") == 0) + continue; + if (strlen(params->pm_name) > CPERLIN) { advise(NULL, "Parameter name \"%s\" is too long", params->pm_name); if (paramout) @@ -3849,3 +3855,108 @@ add_param(PM *first, PM *last, const char *name, const char *value) return pm; } + +/* + * 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 { + char convbuf[BUFSIZ]; + size_t inbytes, outbytes = sizeof(convbuf); +#ifdef HAVE_ICONV + int utf8; + iconv_t cd; + ICONV_CONST char *p; + char *q; +#endif /* HAVE_ICONV */ + if (!first->pm_charset || + check_charset(first->pm_charset, + strlen(first->pm_charset))) { + /* + * No conversion necessary + */ + return getcpy(first->pm_value); + } +#ifdef HAVE_ICONV + utf8 = strcasecmp(first->pm_charset, "UTF-8") == 0; + + cd = iconv_open(get_charset(), first->pm_charset); + if (cd == (iconv_t) -1) { + goto noconvert; + } + + inbytes = strlen(first->pm_value); + outbytes = sizeof(convbuf); + p = first->pm_value; + q = convbuf; + + while (inbytes) { + if (iconv(cd, &p, &inbytes, &q, &outbytes) == -1) { + if (errno != EILSEQ) { + iconv_close(cd); + goto noconvert; + } + /* + * Reset shift state, substitute our character, + * try to restart conversion. + */ + iconv(cd, NULL, NULL, &q, &outbytes); + if (outbytes == 0) { + iconv_close(cd); + goto noconvert; + } + *q++ = replace; + outbytes--; + if (outbytes == 0) { + iconv_close(cd); + goto noconvert; + } + if (utf8) { + for (++p, --inbytes; inbytes > 0 && + (((unsigned char) *q) & 0xc0) == 0x80; + ++p, --inbytes) + continue; + } else { + p++; + inbytes--; + } + } + } + + iconv_close(cd); + + if (outbytes == 0) + q--; + *q = '\0'; + + return getcpy(convbuf); +#endif /* HAVE_ICONV */ +noconvert: + for (p = first->pm_value, q = convbuf; *p && outbytes > 1; + p++, q++, outbytes--) { + if (isascii((unsigned char) p) && + isprint((unsigned char) p)) + *q = *p; + else + *q = replace; + } + + *q = '\0'; + + return getcpy(convbuf); + } + } + + first = first->pm_next; + } + + return NULL; +} -- 2.56.0