diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2004-06-04 23:26:01 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2004-06-04 23:26:01 +0000 |
commit | 656532f4514a44a5207f6ab93e480aecba896627 (patch) | |
tree | 99ccaf4f69ecda9e1043ec8834b494e762f3d32a | |
parent | bdf393200a9cc7c04ae7b92be78a581681401e75 (diff) | |
download | mailutils-656532f4514a44a5207f6ab93e480aecba896627.tar.gz mailutils-656532f4514a44a5207f6ab93e480aecba896627.tar.bz2 |
Implementation of iconv filter streams.
-rw-r--r-- | mailbox/filter_iconv.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/mailbox/filter_iconv.c b/mailbox/filter_iconv.c new file mode 100644 index 000000000..6470de791 --- /dev/null +++ b/mailbox/filter_iconv.c @@ -0,0 +1,453 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2004 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <mailutils/stream.h> +#include <mailutils/filter.h> +#include <mailutils/errno.h> +#include <mailutils/nls.h> + +#ifdef HAVE_ICONV_H +# include <iconv.h> +#endif + +#ifndef ICONV_CONST +# define ICONV_CONST +#endif + +#ifndef HAVE_ICONV +# undef iconv_open +# define iconv_open(tocode, fromcode) ((iconv_t) -1) + +# undef iconv +# define iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft) (errno = EILSEQ, (size_t) -1) + +# undef iconv_close +# define iconv_close(cd) 0 + +#endif + +enum _icvt_state + { + state_closed, + state_open, + state_literal, + state_octal, + state_iconv_error, + state_transport_error + }; + +struct icvt_stream +{ + stream_t stream; /* I/O stream */ + iconv_t cd; /* Conversion descriptor */ + char *buf; /* Conversion buffer */ + size_t bufsize; /* Size of buf */ + size_t bufpos; /* Current position in buf */ + enum _icvt_state state; + int ec; /* Error code */ + char ICONV_CONST *errp; /* Offending input if state == state_iconv_error */ + char errbuf[128]; /* Error message buffer */ +}; + +static int +_icvt_open (stream_t stream) +{ + struct icvt_stream *s = stream_get_owner (stream); + if (s->cd == (iconv_t) -1) + return EINVAL; + s->state = state_open; + return 0; +} + +static int +_icvt_close (stream_t stream) +{ + struct icvt_stream *s = stream_get_owner (stream); + if (s->state != state_closed) + { + int flags; + stream_get_flags (stream, &flags); + if (!(flags & MU_STREAM_NO_CLOSE)) + stream_close (s->stream); + iconv_close (s->cd); + s->cd = (iconv_t) -1; + s->state = state_closed; + } + return 0; +} + +static void +_icvt_destroy (stream_t stream) +{ + struct icvt_stream *s = stream_get_owner (stream); + int flags; + + if (s->state != state_closed) + _icvt_close (stream); + stream_get_flags (stream, &flags); + if (!(flags & MU_STREAM_NO_CLOSE)) + stream_destroy (&s->stream, stream_get_owner (s->stream)); + free (s->buf); + s->buf = NULL; + if (s->cd != (iconv_t) -1) + iconv_close (s->cd); +} + +static int _icvt_read (stream_t stream, char *optr, size_t osize, + off_t offset, size_t *pnbytes); + +static int +internal_icvt_read (stream_t stream, char *optr, size_t osize, size_t *pnbytes) +{ + struct icvt_stream *s = stream_get_owner (stream); + size_t nbytes = 0; + size_t cvtbytes = 0; + int rc, status; + + if (s->bufpos == 0) + { + status = stream_sequential_read (s->stream, s->buf, s->bufsize, &nbytes); + if (status) + { + s->state = state_transport_error; + s->ec = rc; + return MU_ERR_FAILURE; + } + else if (nbytes == 0) + { + if (*pnbytes) + *pnbytes = 0; + return 0; + } + } + + do + { + char ICONV_CONST *ib = s->buf; + size_t inlen = s->bufpos + nbytes; + char *ob = optr + cvtbytes; + size_t olen = osize - cvtbytes; + + rc = iconv (s->cd, &ib, &inlen, &ob, &olen); + cvtbytes += ib - s->buf; + if (rc == -1) + { + if (errno == E2BIG) + { + if (cvtbytes) + { + memmove (s->buf, ib, inlen); + s->bufpos = inlen; + break; + } + else + { + s->ec = MU_ERR_BUFSPACE; + return MU_ERR_BUFSPACE; + } + } + else if (errno == EILSEQ) + { + int flags = 0; + stream_get_flags (stream, &flags); + if (flags & MU_STREAM_STRICT) + { + s->state = state_iconv_error; + s->ec = errno; + s->errp = ib; + if (cvtbytes) + break; + else + return MU_ERR_FAILURE; + } + else + { + s->state = state_octal; + memmove (s->buf, ib, inlen); + s->bufpos = inlen; + if (cvtbytes == 0) + return _icvt_read (stream, optr, osize, 0, pnbytes); + break; + } + } + else if (errno == EINVAL) + { + if (inlen == s->bufsize) + { + /* Try to reallocate temp buffer */ + char *p = realloc (s->buf, s->bufsize + 128); + if (!p) + { + /* Rearrange the buffer anyway */ + memmove (s->buf, ib, inlen); + s->bufpos = inlen; + return ENOMEM; + } + s->bufsize += 128; + } + memmove (s->buf, ib, inlen); + s->bufpos = inlen; + continue; + } + else + { + s->ec = errno; + s->state = state_iconv_error; + return MU_ERR_FAILURE; + } + } + } + while (cvtbytes < osize + && (status = stream_sequential_read (s->stream, + s->buf + s->bufpos, + s->bufsize - s->bufpos, + &nbytes)) == 0 + && nbytes); + + if (status) + { + s->state = state_transport_error; + s->ec = rc; + if (!cvtbytes) + return MU_ERR_FAILURE; + } + + if (*pnbytes) + *pnbytes = cvtbytes; + return 0; +} + +#define ISPRINT(c) ((c)>=' '&&(c)<127) + +static int +octal_icvt_read (struct icvt_stream *s, char *optr, size_t osize, size_t *pnbytes) +{ + size_t i, j; + int status; + + if (osize == 0) + return MU_ERR_BUFSPACE; + + if (s->bufpos < osize) + { + size_t rdcount = osize; + if (s->bufsize < rdcount) + { + /* Try to reallocate temp buffer */ + char *p = realloc (s->buf, rdcount); + if (p) + s->bufsize = rdcount; + else + rdcount = s->bufsize; + } + + status = stream_sequential_read (s->stream, s->buf, rdcount - s->bufpos, + &rdcount); + if (status) + { + s->state = state_transport_error; + s->ec = status; + if (s->bufpos == 0) + return MU_ERR_FAILURE; + } + else + s->bufpos += rdcount; + } + + for (i = j = 0; i < osize && j < s->bufpos; i++) + { + if (ISPRINT (*(unsigned char*)(s->buf+i))) + optr[j++] = s->buf[i]; + else if (j + 4 >= s->bufpos) + break; + else + { + sprintf (optr + j, "\\%03o", *(unsigned char*)(s->buf+i)); + j += 4; + } + } + s->bufpos -= i; + memmove (s->buf, s->buf + i, s->bufpos); + if (pnbytes) + *pnbytes = j; + return 0; +} + +static int +literal_icvt_read (struct icvt_stream *s, char *optr, size_t osize, size_t *pnbytes) +{ + int status; + size_t nbytes; + + if (s->bufpos) + { + size_t sz = s->bufpos < osize ? s->bufpos : osize; + memcpy (optr, s->buf, sz); + s->bufpos -= sz; + if (s->bufpos) + memmove (s->buf, s->buf + sz, s->bufpos); + if (*pnbytes) + *pnbytes = sz; + return 0; + } + + status = stream_sequential_read (s->stream, optr, osize, &nbytes); + if (status) + { + s->state = state_transport_error; + s->ec = status; + if (s->bufpos == 0) + return MU_ERR_FAILURE; + } + if (*pnbytes) + *pnbytes = nbytes; + return 0; +} + +static int +_icvt_read (stream_t stream, char *optr, size_t osize, + off_t offset ARG_UNUSED, size_t *pnbytes) +{ + struct icvt_stream *s = stream_get_owner (stream); + + switch (s->state) + { + case state_open: + return internal_icvt_read (stream, optr, osize, pnbytes); + + case state_closed: + return EINVAL; + + case state_literal: + return literal_icvt_read (s, optr, osize, pnbytes); + + case state_octal: + return octal_icvt_read (s, optr, osize, pnbytes); + + default: + break; + } + return MU_ERR_FAILURE; +} + +int +_icvt_strerror (stream_t stream, char **pstr) +{ + struct icvt_stream *s = stream_get_owner (stream); + switch (s->state) + { + case state_transport_error: + snprintf (s->errbuf, sizeof s->errbuf, + _("Transport error: %s"), mu_strerror (s->ec)); + *pstr = s->errbuf; + break; + + case state_iconv_error: + switch (s->ec) + { + case EILSEQ: + snprintf (s->errbuf, sizeof s->errbuf, + _("Illegal multibyte sequence near %s"), s->errp); + break; + + default: + snprintf (s->errbuf, sizeof s->errbuf, + _("Iconv error: %s"), mu_strerror (s->ec)); + } + *pstr = s->errbuf; + break; + + case state_closed: + *pstr = _("Stream is closed"); + break; + + default: + *pstr = (char*) mu_strerror (s->ec); + } + + return 0; +} + +int +_icvt_get_transport2 (stream_t stream, + mu_transport_t *pt, mu_transport_t *pt2) +{ + struct icvt_stream *s = stream_get_owner (stream); + if (pt) + *pt = s->stream; + if (*pt2) + *pt2 = NULL; + return 0; +} + +int +_icvt_wait (stream_t stream, int *pflags, struct timeval *tvp) +{ + struct icvt_stream *s = stream_get_owner (stream); + return stream_wait (s->stream, pflags, tvp); +} + +int +filter_iconv_create (stream_t *s, stream_t transport, + char *fromcode, char *tocode, int flags) +{ + struct icvt_stream *iptr; + iconv_t cd; + int rc; + + cd = iconv_open (tocode, fromcode); + if (cd == (iconv_t) -1) + return MU_ERR_FAILURE; + + iptr = malloc (sizeof *iptr); + if (!iptr) + return ENOMEM; + iptr->stream = transport; + iptr->cd = cd; + iptr->state = state_closed; + iptr->bufsize = 128; + iptr->buf = malloc (iptr->bufsize); + if (!iptr->buf) + { + free (iptr); + return ENOMEM; + } + iptr->bufpos = 0; + + rc = stream_create (s, flags, iptr); + if (rc) + { + free (iptr); + return rc; + } + stream_set_open (*s, _icvt_open, iptr); + stream_set_close (*s, _icvt_close, iptr); + stream_set_read (*s, _icvt_read, iptr); + stream_set_destroy (*s, _icvt_destroy, iptr); + stream_set_strerror (*s, _icvt_strerror, iptr); + stream_set_get_transport2 (*s, _icvt_get_transport2, iptr); + stream_set_wait (*s, _icvt_wait, iptr); + return 0; +} |