diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-04-22 12:50:41 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-04-22 20:14:02 +0300 |
commit | eaf6c0f065f0b1b09203dcd543acafcbcf414a61 (patch) | |
tree | ada72b08fe1bee1957b0c427f42a69bf774234a5 | |
parent | 5aea5b2d9b0c87fe81ee566dba830a568398679c (diff) | |
download | mailutils-eaf6c0f065f0b1b09203dcd543acafcbcf414a61.tar.gz mailutils-eaf6c0f065f0b1b09203dcd543acafcbcf414a61.tar.bz2 |
Functions for formatting RFC-2231-compliant mail headers fields.
* include/mailutils/cctype.h (MU_CTYPE_TSPEC): New class. Represents
tspecials as per RFC 2045, section 5.1.
(mu_istspec): New define.
* libmailutils/string/muctype.c (mu_c_tab): Mark tspecials
* include/mailutils/assoc.h (mu_assoc_mark)
(mu_assoc_sweep): New protos.
* libmailutils/base/assoc.c (_mu_assoc_elem): New field: mark
(mu_assoc_mark,mu_assoc_sweep): New functions.
* libmailutils/filter/Makefile.am (libfilter_la_SOURCES): Add dq.c
and percent.c.
* libmailutils/filter/dq.c: New file.
* libmailutils/filter/percent.c: New file.
* include/mailutils/filter.h (mu_percent_filter)
(mu_dq_filter): New externs.
* libmailutils/filter/filter.c (mu_filter_get_list): Register
mu_percent_filter and mu_dq_filter.
* include/mailutils/mime.h (mu_mime_header_set)
(mu_mime_header_set_w): New protos.
* libmailutils/mime/Makefile.am (libmime_la_SOURCES): Add mimehdrset.c
* libmailutils/mime/mimehdrset.c: New file.
* libmailutils/mime/mime.c (_mime_set_content_type): For multipart/alternative,
remove also all parameters except charset from the Content-Type header.
* mail/send.c (saveatt): Remove the now unneeded conditionals.
* libmailutils/tests/mimehdr.at: Test formatting functions.
* include/mailutils/stream.h (MU_IOCTL_FILTER_SET_OUTBUF_SIZE): New ioctl.
* include/mailutils/sys/filter.h (_MU_FILTER_DISABLED)
(_MU_FILTER_EOF): Remove. Use bitfields instead.
(_mu_filter_stream): Remove fltflag. New fields: flag_disabled,
flag_eof, outbuf_size.
* libmailutils/stream/fltstream.c (MFB_BASE)
(MFB_CURPTR, MFB_ENDPTR, MFB_SIZE, MFB_LEVEL)
(MFB_POS, MFB_RDBYTES, MFB_FREESIZE)
(MBF_CLEAR, MBF_FREE): Replace with inline functions.
(init_iobuf): Use the outbuf_size field (unless 0) to
set the output buffer size.
(filter_read): Stop if on success if outbuf_size is set,
without trying to fill the entire buffer.
(filter_ctl): Handle MU_IOCTL_FILTER_SET_OUTBUF_SIZE.
* libmailutils/tests/mimehdr.c: New option -width: format and
print the value assuming given line width.
-rw-r--r-- | include/mailutils/assoc.h | 4 | ||||
-rw-r--r-- | include/mailutils/cctype.h | 28 | ||||
-rw-r--r-- | include/mailutils/filter.h | 4 | ||||
-rw-r--r-- | include/mailutils/mime.h | 7 | ||||
-rw-r--r-- | include/mailutils/stream.h | 6 | ||||
-rw-r--r-- | include/mailutils/sys/filter.h | 8 | ||||
-rw-r--r-- | libmailutils/base/assoc.c | 31 | ||||
-rw-r--r-- | libmailutils/filter/Makefile.am | 2 | ||||
-rw-r--r-- | libmailutils/filter/dq.c | 143 | ||||
-rw-r--r-- | libmailutils/filter/filter.c | 2 | ||||
-rw-r--r-- | libmailutils/filter/percent.c | 186 | ||||
-rw-r--r-- | libmailutils/mime/Makefile.am | 3 | ||||
-rw-r--r-- | libmailutils/mime/mime.c | 36 | ||||
-rw-r--r-- | libmailutils/mime/mimehdrset.c | 314 | ||||
-rw-r--r-- | libmailutils/stream/fltstream.c | 242 | ||||
-rw-r--r-- | libmailutils/string/muctype.c | 30 | ||||
-rw-r--r-- | libmailutils/tests/mimehdr.at | 121 | ||||
-rw-r--r-- | libmailutils/tests/mimehdr.c | 27 | ||||
-rw-r--r-- | mail/send.c | 4 |
19 files changed, 1069 insertions, 129 deletions
diff --git a/include/mailutils/assoc.h b/include/mailutils/assoc.h index 23ae7cda4..a8f9845e1 100644 --- a/include/mailutils/assoc.h +++ b/include/mailutils/assoc.h @@ -53,6 +53,10 @@ typedef int (*mu_assoc_comparator_t) (const char *, const void *, int mu_assoc_sort_r (mu_assoc_t assoc, mu_assoc_comparator_t cmp, void *data); +int mu_assoc_mark (mu_assoc_t asc, int (*cond) (char const *, void *, void *), + void *data); +int mu_assoc_sweep (mu_assoc_t asc); + #ifdef __cplusplus } diff --git a/include/mailutils/cctype.h b/include/mailutils/cctype.h index ecd9c4007..bed749b96 100644 --- a/include/mailutils/cctype.h +++ b/include/mailutils/cctype.h @@ -24,18 +24,19 @@ extern "C" { #endif -#define MU_CTYPE_ALPHA 0x001 -#define MU_CTYPE_DIGIT 0x002 -#define MU_CTYPE_BLANK 0x004 -#define MU_CTYPE_CNTRL 0x008 -#define MU_CTYPE_GRAPH 0x010 -#define MU_CTYPE_LOWER 0x020 -#define MU_CTYPE_UPPER 0x040 -#define MU_CTYPE_PRINT 0x080 -#define MU_CTYPE_PUNCT 0x100 -#define MU_CTYPE_SPACE 0x200 -#define MU_CTYPE_XLETR 0x400 -#define MU_CTYPE_ENDLN 0x800 +#define MU_CTYPE_ALPHA 0x0001 +#define MU_CTYPE_DIGIT 0x0002 +#define MU_CTYPE_BLANK 0x0004 +#define MU_CTYPE_CNTRL 0x0008 +#define MU_CTYPE_GRAPH 0x0010 +#define MU_CTYPE_LOWER 0x0020 +#define MU_CTYPE_UPPER 0x0040 +#define MU_CTYPE_PRINT 0x0080 +#define MU_CTYPE_PUNCT 0x0100 +#define MU_CTYPE_SPACE 0x0200 +#define MU_CTYPE_XLETR 0x0400 +#define MU_CTYPE_ENDLN 0x0800 +#define MU_CTYPE_TSPEC 0x1000 /* tspecials: RFC 2045, section 5.1. */ #define MU_C_TAB_MAX 128 @@ -58,7 +59,8 @@ extern int mu_c_tab[MU_C_TAB_MAX]; #define mu_isascii(c) (((unsigned)c) < MU_C_TAB_MAX) #define mu_isblank(c) mu_c_is_class (c, MU_CTYPE_BLANK) #define mu_isendln(c) mu_c_is_class (c, MU_CTYPE_ENDLN) - +#define mu_istspec(c) mu_c_is_class (c, MU_CTYPE_TSPEC) + #define mu_tolower(c) \ ({ int __c = (c); \ (__c >= 'A' && __c <= 'Z' ? __c - 'A' + 'a' : __c); \ diff --git a/include/mailutils/filter.h b/include/mailutils/filter.h index 8bd107100..c8bddd40b 100644 --- a/include/mailutils/filter.h +++ b/include/mailutils/filter.h @@ -126,7 +126,9 @@ extern mu_filter_record_t mu_iconv_filter; extern mu_filter_record_t mu_c_escape_filter; extern mu_filter_record_t mu_htmlent_filter; extern mu_filter_record_t mu_xml_filter; - +extern mu_filter_record_t mu_percent_filter; +extern mu_filter_record_t mu_dq_filter; + enum mu_iconv_fallback_mode { mu_fallback_none, diff --git a/include/mailutils/mime.h b/include/mailutils/mime.h index 786f96f2b..503ebe399 100644 --- a/include/mailutils/mime.h +++ b/include/mailutils/mime.h @@ -75,6 +75,13 @@ int mu_mime_header_parse (const char *text, const char *charset, char **pvalue, int mu_mime_header_parse_subset (const char *text, const char *charset, char **pvalue, mu_assoc_t assoc); + +int mu_mime_header_set_w (mu_header_t hdr, const char *name, + const char *value, mu_assoc_t params, + size_t line_width); +int mu_mime_header_set (mu_header_t hdr, const char *name, + const char *value, mu_assoc_t params); + #ifdef __cplusplus } diff --git a/include/mailutils/stream.h b/include/mailutils/stream.h index 2e7b86cb5..f7f58ba8a 100644 --- a/include/mailutils/stream.h +++ b/include/mailutils/stream.h @@ -195,6 +195,12 @@ enum mu_buffer_type #define MU_IOCTL_FILTER_GET_DISABLED 0 #define MU_IOCTL_FILTER_SET_DISABLED 1 + /* Set transcoder output buffer size. + Arg: size_t* + Has effect only if the stream is unbuffered + */ +#define MU_IOCTL_FILTER_SET_OUTBUF_SIZE 2 + /* TLS transport streams */ /* Get cipher info. Arg: mu_property_t * diff --git a/include/mailutils/sys/filter.h b/include/mailutils/sys/filter.h index 1ee0b1942..23b5affcd 100644 --- a/include/mailutils/sys/filter.h +++ b/include/mailutils/sys/filter.h @@ -36,16 +36,14 @@ struct _mu_filter_buffer size_t pos; }; -#define _MU_FILTER_DISABLED 0x01 -#define _MU_FILTER_EOF 0x02 - struct _mu_filter_stream { struct _mu_stream stream; mu_stream_t transport; int mode; - int fltflag; - + unsigned flag_disabled:1; + unsigned flag_eof:1; + size_t outbuf_size; struct _mu_filter_buffer inbuf, outbuf; mu_filter_xcode_t xcode; void *xdata; diff --git a/libmailutils/base/assoc.c b/libmailutils/base/assoc.c index 4e31b499a..b0e353d32 100644 --- a/libmailutils/base/assoc.c +++ b/libmailutils/base/assoc.c @@ -47,6 +47,7 @@ struct _mu_assoc_elem { char *name; struct _mu_assoc_elem *next, *prev; + int mark:1; char *data; }; @@ -793,4 +794,34 @@ mu_assoc_sort_r (mu_assoc_t assoc, mu_assoc_comparator_t cmp, void *data) return 0; } + +int +mu_assoc_mark (mu_assoc_t asc, int (*cond) (char const *, void *, void *), + void *data) +{ + struct _mu_assoc_elem *elt; + + if (!asc) + return EINVAL; + for (elt = asc->head; elt; elt = elt->next) + elt->mark = !!cond (elt->name, elt->data, data); + return 0; +} + +int +mu_assoc_sweep (mu_assoc_t asc) +{ + unsigned i; + + if (!asc) + return EINVAL; + + for (i = hash_size[asc->hash_num]; i > 0; i--) + { + if (asc->tab[i-1] && asc->tab[i-1]->mark) + assoc_remove (asc, i-1); + } + return 0; +} + diff --git a/libmailutils/filter/Makefile.am b/libmailutils/filter/Makefile.am index b735bf772..5daedacae 100644 --- a/libmailutils/filter/Makefile.am +++ b/libmailutils/filter/Makefile.am @@ -25,6 +25,7 @@ libfilter_la_SOURCES =\ crlfflt.c\ decode.c\ dot.c\ + dq.c\ filter.c\ fltchain.c\ fromflt.c\ @@ -34,6 +35,7 @@ libfilter_la_SOURCES =\ inline-comment.c\ linecon.c\ linelenflt.c\ + percent.c\ qpflt.c\ xml.c diff --git a/libmailutils/filter/dq.c b/libmailutils/filter/dq.c new file mode 100644 index 000000000..d3bb18523 --- /dev/null +++ b/libmailutils/filter/dq.c @@ -0,0 +1,143 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2011-2012, 2014-2017 Free Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library. If not, see + <http://www.gnu.org/licenses/>. */ + +/* Doble-quote escaping filter. + In encode mode, escapes each occurrence of double-quote and backslash + by prefixing it with backslash. + In decode mode, removes backslash prefixes. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <mailutils/errno.h> +#include <mailutils/filter.h> +#include <mailutils/wordsplit.h> +#include <mailutils/cctype.h> + +/* Move min(isize,osize) bytes from iptr to optr, replacing each + escapable control character with its escape sequence. */ +static enum mu_filter_result +_dq_encoder (void *xd MU_ARG_UNUSED, + enum mu_filter_command cmd, + struct mu_filter_io *iobuf) +{ + size_t i, j; + const unsigned char *iptr; + size_t isize; + char *optr; + size_t osize; + + switch (cmd) + { + case mu_filter_init: + case mu_filter_done: + return mu_filter_ok; + default: + break; + } + + iptr = (const unsigned char *) iobuf->input; + isize = iobuf->isize; + optr = iobuf->output; + osize = iobuf->osize; + + for (i = j = 0; i < isize && j < osize; i++) + { + unsigned char c = *iptr++; + + if (strchr ("\\\"", c)) + { + if (j + 1 == osize) + { + if (i == 0) + { + iobuf->osize = 2; + return mu_filter_moreoutput; + } + break; + } + else + { + optr[j++] = '\\'; + optr[j++] = c; + } + } + else + optr[j++] = c; + } + iobuf->isize = i; + iobuf->osize = j; + return mu_filter_ok; +} + +/* Move min(isize,osize) bytes from iptr to optr, replacing each escape + sequence with its ASCII code. */ +static enum mu_filter_result +_dq_decoder (void *xd MU_ARG_UNUSED, + enum mu_filter_command cmd, + struct mu_filter_io *iobuf) +{ + size_t i, j; + const unsigned char *iptr; + size_t isize; + char *optr; + size_t osize; + + switch (cmd) + { + case mu_filter_init: + case mu_filter_done: + return mu_filter_ok; + default: + break; + } + + iptr = (const unsigned char *) iobuf->input; + isize = iobuf->isize; + optr = iobuf->output; + osize = iobuf->osize; + + for (i = j = 0; i < isize && j < osize; i++) + { + unsigned char c = *iptr++; + if (c == '\\') + { + if (i + 1 == isize) + break; + optr[j++] = *iptr++; + } + else + optr[j++] = c; + } + + iobuf->isize = i; + iobuf->osize = j; + return mu_filter_ok; +} + +static struct _mu_filter_record _dq_filter = { + "dq", + NULL, + _dq_encoder, + _dq_decoder, +}; + +mu_filter_record_t mu_dq_filter = &_dq_filter; + diff --git a/libmailutils/filter/filter.c b/libmailutils/filter/filter.c index 0717425fc..00bc50fa2 100644 --- a/libmailutils/filter/filter.c +++ b/libmailutils/filter/filter.c @@ -86,6 +86,8 @@ mu_filter_get_list (mu_list_t *plist) mu_list_append (filter_list, mu_c_escape_filter); mu_list_append (filter_list, mu_htmlent_filter); mu_list_append (filter_list, mu_xml_filter); + mu_list_append (filter_list, mu_percent_filter); + mu_list_append (filter_list, mu_dq_filter); /* FIXME: add the default encodings? */ } *plist = filter_list; diff --git a/libmailutils/filter/percent.c b/libmailutils/filter/percent.c new file mode 100644 index 000000000..62d248d88 --- /dev/null +++ b/libmailutils/filter/percent.c @@ -0,0 +1,186 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2011-2012, 2014-2017 Free Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library. If not, see + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <mailutils/errno.h> +#include <mailutils/filter.h> +#include <mailutils/cctype.h> + +static char xchar[] = "0123456789ABCDEF"; + +static enum mu_filter_result +percent_encoder (void *xd, + enum mu_filter_command cmd, + struct mu_filter_io *iobuf) +{ + size_t i, j; + const unsigned char *iptr; + size_t isize; + char *optr; + size_t osize; + char *escape_chars = xd; + + switch (cmd) + { + case mu_filter_init: + case mu_filter_done: + return mu_filter_ok; + default: + break; + } + + iptr = (const unsigned char *) iobuf->input; + isize = iobuf->isize; + optr = iobuf->output; + osize = iobuf->osize; + + for (i = j = 0; i < isize && j < osize; i++) + { + unsigned char c = iptr[i]; + + if (c == 0 || strchr (escape_chars, c)) + { + if (j + 3 >= osize) + { + if (i == 0) + { + iobuf->osize = 3; + return mu_filter_moreoutput; + } + break; + } + optr[j++] = '%'; + optr[j++] = xchar[((c >> 4) & 0xf)]; + optr[j++] = xchar[c & 0xf]; + } + else + optr[j++] = c; + } + iobuf->isize = i; + iobuf->osize = j; + return mu_filter_ok; +} + +static enum mu_filter_result +percent_decoder (void *xd MU_ARG_UNUSED, + enum mu_filter_command cmd, + struct mu_filter_io *iobuf) +{ + size_t i, j; + const unsigned char *iptr; + size_t isize; + char *optr; + size_t osize; + + switch (cmd) + { + case mu_filter_init: + case mu_filter_done: + return mu_filter_ok; + default: + break; + } + + iptr = (const unsigned char *) iobuf->input; + isize = iobuf->isize; + optr = iobuf->output; + osize = iobuf->osize; + + for (i = j = 0; i < isize && j < osize; j++) + { + unsigned char c = iptr[i++]; + if (c == '%') + { + char *phi, *plo; + + if (i + 2 >= isize) + break; + phi = strchr (xchar, mu_toupper (iptr[i])); + plo = strchr (xchar, mu_toupper (iptr[i+1])); + if (phi && plo) + { + optr[j] = ((phi - xchar) << 4) + (plo - xchar); + i += 2; + } + else + optr[j] = c; + } + else + optr[j] = c; + } + iobuf->isize = i; + iobuf->osize = j; + return mu_filter_ok; +} + +static int +percent_alloc (void **pret, int mode, int argc, const char **argv) +{ + if (mode == MU_FILTER_ENCODE) + { + char *s; + if (argc > 1) + { + int i; + size_t len = 0; + + for (i = 1; i < argc; i++) + len += strlen (argv[i]); + s = malloc (len + 1); + if (!s) + return ENOMEM; + *pret = s; + *s = 0; + for (i = 1; i < argc; i++) + strcat (s, argv[i]); + } + else + { + int i; + + s = malloc (UCHAR_MAX); + if (!s) + return ENOMEM; + *pret = s; + for (i = 1; i <= UCHAR_MAX; i++) + { + if (i == '%' || i == '"' || !mu_isgraph (i)) + *s++ = i; + } + *s = 0; + } + } + else + *pret = NULL; + return 0; +} + +static struct _mu_filter_record _percent_filter = { + "percent", + percent_alloc, + percent_encoder, + percent_decoder +}; + +mu_filter_record_t mu_percent_filter = &_percent_filter; + + diff --git a/libmailutils/mime/Makefile.am b/libmailutils/mime/Makefile.am index 98895cfd7..3edb9fdfa 100644 --- a/libmailutils/mime/Makefile.am +++ b/libmailutils/mime/Makefile.am @@ -20,6 +20,7 @@ noinst_LTLIBRARIES = libmime.la libmime_la_SOURCES = \ attachment.c\ mime.c\ - mimehdr.c + mimehdr.c\ + mimehdrset.c AM_CPPFLAGS = @MU_LIB_COMMON_INCLUDES@ -I/libmailutils diff --git a/libmailutils/mime/mime.c b/libmailutils/mime/mime.c index 9c3879769..e4463d307 100644 --- a/libmailutils/mime/mime.c +++ b/libmailutils/mime/mime.c @@ -38,6 +38,8 @@ #include <mailutils/header.h> #include <mailutils/errno.h> #include <mailutils/util.h> +#include <mailutils/assoc.h> +#include <mailutils/io.h> #include <mailutils/sys/mime.h> #include <mailutils/sys/message.h> @@ -445,6 +447,13 @@ _mimepart_body_lines (mu_body_t body, size_t *plines) /*------ Mime message/header functions for CREATING multipart message -----*/ static int +retain_charset (char const *name, void *value MU_ARG_UNUSED, + void *data MU_ARG_UNUSED) +{ + return strcmp (name, "charset") != 0; +} + +static int _mime_set_content_type (mu_mime_t mime) { const char *content_type; @@ -473,9 +482,36 @@ _mime_set_content_type (mu_mime_t mime) for (i = 0; i < mime->nmtp_parts; i++) { mu_header_t hdr; + char *val; + int rc; mu_message_get_header (mime->mtp_parts[i]->msg, &hdr); mu_header_remove (hdr, MU_HEADER_CONTENT_DISPOSITION, 1); + + rc = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, + &val); + if (rc == 0) + { + mu_content_type_t ct; + rc = mu_content_type_parse (val, NULL, &ct); + if (rc == 0) + { + char *type; + + mu_assoc_mark (ct->param, retain_charset, NULL); + mu_assoc_sweep (ct->param); + + rc = mu_asprintf (&type, "%s/%s", ct->type, ct->subtype); + if (rc == 0) + { + mu_mime_header_set (hdr, + MU_HEADER_CONTENT_TYPE, type, + ct->param); + free (type); + } + mu_content_type_destroy (&ct); + } + } } content_type = "multipart/alternative; boundary="; diff --git a/libmailutils/mime/mimehdrset.c b/libmailutils/mime/mimehdrset.c new file mode 100644 index 000000000..7c127caee --- /dev/null +++ b/libmailutils/mime/mimehdrset.c @@ -0,0 +1,314 @@ +/* Functions for formatting RFC-2231-compliant mail headers fields. + GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999-2001, 2004-2005, 2007, 2009-2012, 2014-2017 Free + Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library. If not, + see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <mailutils/mime.h> +#include <mailutils/cctype.h> +#include <mailutils/cstr.h> +#include <mailutils/header.h> +#include <mailutils/stream.h> +#include <mailutils/filter.h> +#include <mailutils/assoc.h> +#include <mailutils/errno.h> + +struct header_buffer +{ + mu_stream_t str; /* Output stream */ + size_t line_len; /* Length of current line */ + size_t line_max; /* Max. line length */ +}; + +static int +mime_store_parameter (char const *name, void *value, void *data) +{ + struct mu_mime_param *p = value; + struct header_buffer *hbuf = data; + size_t nlen; /* Length of parameter name + (eq. sign, eventual seqence no. and language info mark + included) */ + size_t vlen; /* Length of lang'charset' part */ + int langinfo; /* True if language info is available */ + int quote = 0; /* 2 if the value should be quoted, 0 otherwise */ + int segment = -1; /* Segment sequence number */ + mu_stream_t valstr; /* Value stream (properly encoded) */ + mu_off_t valsize; /* Number of octets left in valstr */ + char const *filter_name = NULL; /* Name of the filter for the value */ + int rc; + + rc = mu_static_memory_stream_create (&valstr, p->value, strlen (p->value)); + if (rc) + return rc; + + nlen = strlen (name); + if (p->lang || p->cset) + { + vlen = 2; + if (p->lang) + vlen += strlen (p->lang); + if (p->cset) + vlen += strlen (p->cset); + langinfo = 1; + filter_name = "percent"; + } + else + { + if (*mu_str_skip_class_comp (p->value, MU_CTYPE_TSPEC|MU_CTYPE_BLANK)) + { + /* Must be in quoted-string, to use within parameter values */ + quote = 2; + filter_name = "dq"; + } + else + quote = 0; + vlen = 0; + langinfo = 0; + } + + if (filter_name) + { + mu_stream_t tmp; + + rc = mu_filter_create (&tmp, valstr, filter_name, MU_FILTER_ENCODE, + MU_STREAM_READ | MU_STREAM_SEEK); + if (rc) + goto err; + mu_stream_unref (valstr); + valstr = tmp; + rc = mu_memory_stream_create (&tmp, MU_STREAM_RDWR); + if (rc == 0) + { + rc = mu_stream_copy (tmp, valstr, 0, &valsize); + mu_stream_destroy (&tmp); + } + } + else + rc = mu_stream_size (valstr, &valsize); + + if (rc) + goto err; + + nlen += langinfo; + + rc = mu_stream_seek (valstr, 0, MU_SEEK_SET, NULL); + + if (hbuf->line_max == 0) + { + /* No line wrapping requested. Store the value as it is */ + mu_stream_printf (hbuf->str, "%s", name); + if (langinfo) + mu_stream_write (hbuf->str, "*", 1, NULL); + mu_stream_write (hbuf->str, "=", 1, NULL); + if (vlen) + { + mu_stream_printf (hbuf->str, "%s'%s'", + p->lang ? p->lang : "", + p->cset ? p->cset : ""); + vlen = 0; + } + else if (quote) + mu_stream_write (hbuf->str, "\"", 1, NULL); + mu_stream_copy (hbuf->str, valstr, 0, NULL); + if (quote) + mu_stream_write (hbuf->str, "\"", 1, NULL); + if (mu_stream_err (hbuf->str)) + rc = mu_stream_last_error (hbuf->str); + } + else + { + /* Split the value into sequentially indexed segments, each one no + wider than the requested line width. + + Without special precautions, an encoded character occurring at + the end of a segment can be split between this and the following + segment to satisfy line width requirements. To avoid this, the + following approach is used: + + 1. The value stream is put to unbuffered mode. + 2. Before each write, the size of the transcoder output buffer + in valstr is set to the number of bytes left in the current + line. + + This way the transcoder will write as many bytes as possible + without breaking the encoded constructs while the unbuffered mode + will ensure that it will not be called again to fill up the stream + buffer. + + If the line width is insufficient, MU_ERR_BUFSPACE will be returned. + */ + char *iobuf; + + iobuf = malloc (hbuf->line_max + 1); + if (!iobuf) + { + rc = errno; + goto err; + } + + mu_stream_set_buffer (valstr, mu_buffer_none, 0); + + while (rc == 0 && valsize) + { + mu_off_t start, nr; /* Start and end positions in stream */ + size_t sz, n; + + mu_stream_write (hbuf->str, ";", 1, NULL); + mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &start); + + if (segment >= 0) + { + mu_stream_write (hbuf->str, "\n", 1, NULL); + hbuf->line_len = 0; + segment++; + } + else if (hbuf->line_len + valsize + quote + vlen + nlen + 1 > + hbuf->line_max) + { + mu_stream_write (hbuf->str, "\n", 1, NULL); + hbuf->line_len = 0; + if (hbuf->line_len + valsize + quote + vlen + nlen + 1 > + hbuf->line_max) + segment++; + } + + mu_stream_write (hbuf->str, " ", 1, NULL); + + if (segment >= 0) + mu_stream_printf (hbuf->str, "%s*%d", name, segment); + else + mu_stream_printf (hbuf->str, "%s", name); + if (langinfo) + mu_stream_write (hbuf->str, "*", 1, NULL); + mu_stream_write (hbuf->str, "=", 1, NULL); + mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &nr); + nlen = nr - start; + hbuf->line_len += nlen; + start = nr; + + /* Compute the number of octets to put into the current line. + If the requested line width is not enough to accomodate + the line, signal the error */ + if (hbuf->line_max <= (hbuf->line_len + quote + vlen)) + { + rc = MU_ERR_BUFSPACE; + break; + } + + sz = hbuf->line_max - (hbuf->line_len + quote + vlen); + mu_stream_ioctl (valstr, MU_IOCTL_FILTER, + MU_IOCTL_FILTER_SET_OUTBUF_SIZE, &sz); + + rc = mu_stream_read (valstr, iobuf, sz, &n); + if (rc || n == 0) + break; + + if (vlen) + { + mu_stream_printf (hbuf->str, "%s'%s'", + p->lang ? p->lang : "", + p->cset ? p->cset : ""); + vlen = 0; + } + else if (quote) + mu_stream_write (hbuf->str, "\"", 1, NULL); + + mu_stream_write (hbuf->str, iobuf, n, NULL); + + if (quote) + mu_stream_write (hbuf->str, "\"", 1, NULL); + mu_stream_seek (hbuf->str, 0, MU_SEEK_CUR, &nr); + nr -= start; + hbuf->line_len += nr; + valsize -= n; + + if (mu_stream_err (hbuf->str)) + rc = mu_stream_last_error (hbuf->str); + } + free (iobuf); + } + err: + mu_stream_destroy (&valstr); + + return rc; +} + +static int +mime_header_format (const char *value, mu_assoc_t params, + struct header_buffer *hbuf) +{ + size_t l = strlen (value); + + mu_stream_write (hbuf->str, value, l, NULL); + hbuf->line_len += l; + return mu_assoc_foreach (params, mime_store_parameter, hbuf); +} + +/* Store a header in accordance with RFC 2231, Section 3, + "Parameter Value Continuations" + + HDR - Message header object + NAME - Header name + VALUE - Header value part + PARAMS - Named parameters (assoc of struct mu_mime_param *) + LINE_WIDTH - Maximum line width. +*/ + +int +mu_mime_header_set_w (mu_header_t hdr, const char *name, + const char *value, mu_assoc_t params, size_t line_width) +{ + struct header_buffer hbuf; + int rc; + + rc = mu_memory_stream_create (&hbuf.str, MU_STREAM_RDWR); + if (rc) + return rc; + hbuf.line_len = strlen (name) + 2; + hbuf.line_max = line_width; + rc = mime_header_format (value, params, &hbuf); + if (rc == 0) + { + mu_off_t pos; + char *fmtval; + + mu_stream_seek (hbuf.str, 0, MU_SEEK_CUR, &pos); + fmtval = malloc (pos + 1); + mu_stream_seek (hbuf.str, 0, MU_SEEK_SET, NULL); + mu_stream_read (hbuf.str, fmtval, pos, NULL); + fmtval[pos] = 0; + rc = mu_header_set_value (hdr, name, fmtval, 1); + free (fmtval); + } + mu_stream_destroy (&hbuf.str); + return rc; |