Blame view

mailbox/filter_iconv.c 10.4 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
2
   Copyright (C) 2004, 2005, 2007, 2010 Free Software Foundation, Inc.
3 4 5 6

   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
7
   version 3 of the License, or (at your option) any later version.
8 9 10 11 12 13

   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.

14 15 16 17
   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., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA */
18 19 20 21 22

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

23 24
#include <unistd.h>
#include <stdio.h>
25 26 27
#include <stdlib.h>
#include <errno.h>
#include <mailutils/stream.h>
28
#include <mailutils/sys/stream.h>
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
#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
  {
55 56 57 58 59 60
    state_closed,           /* Filter is closed */
    state_open,             /* Filter is open and running in conversion mode */
    state_copy_pass,        /* Filter is open and running in copy-pass mode */
    state_copy_octal,       /* Filter is open and running in copy-octal mode */
    state_iconv_error,      /* A fatal iconv error has occurred */
    state_transport_error   /* A fatal transport error has occurred */
61 62 63 64
  };

struct icvt_stream
{
65 66
  struct _mu_stream stream;
  mu_stream_t transport;/* I/O stream */
67
  int fallback_mode;
68 69 70 71
  iconv_t cd;           /* Conversion descriptor */
  char *buf;            /* Conversion buffer */
  size_t bufsize;       /* Size of buf */
  size_t bufpos;        /* Current position in buf */
72
  enum _icvt_state state;
73 74
  int ec;               /* Error code */
  char errbuf[128];     /* Error message buffer */
75 76 77
};

static int
78
_icvt_open (mu_stream_t stream)
79
{
80
  struct icvt_stream *s = (struct icvt_stream *)stream;
81 82 83 84 85 86 87
  if (s->cd == (iconv_t) -1)
    return EINVAL;
  s->state = state_open;
  return 0;
}

static int
88
_icvt_close (mu_stream_t stream)
89
{
90
  struct icvt_stream *s = (struct icvt_stream *)stream;
91 92
  if (s->state != state_closed)
    {
93
      mu_stream_close (s->transport);
94 95 96 97 98 99 100 101
      iconv_close (s->cd);
      s->cd = (iconv_t) -1;
      s->state = state_closed;
    }
  return 0;
}

static void
102
_icvt_done (mu_stream_t stream)
103
{
104
  struct icvt_stream *s = (struct icvt_stream *)stream;
105 106 107

  if (s->state != state_closed)
    _icvt_close (stream);
108
  mu_stream_destroy (&s->transport);
109 110 111
  free (s->buf);
}

112
static int _icvt_read (mu_stream_t stream, char *optr, size_t osize,
113
		       size_t *pnbytes);
114 115

static int
116 117
internal_icvt_read (mu_stream_t stream, char *optr, size_t osize,
		    size_t *pnbytes)
118
{
119
  struct icvt_stream *s = (struct icvt_stream *)stream;
120
  size_t nbytes = 0;
121 122 123
  int rc, status = 0;
  char *ob = optr;
  size_t olen = osize;
124 125 126

  if (s->bufpos == 0)
    {
127
      status = mu_stream_read (s->transport, s->buf, s->bufsize, &nbytes);
128 129 130 131 132 133 134 135
      if (status)
	{
	  s->state = state_transport_error;
	  s->ec = rc;
	  return MU_ERR_FAILURE;
	}
      else if (nbytes == 0)
	{
136
	  if (pnbytes)
137 138 139 140 141 142 143 144 145 146 147
	    *pnbytes = 0;
	  return 0;
	}
    }
  
  do
    {
      char ICONV_CONST *ib = s->buf;
      size_t inlen = s->bufpos + nbytes;
      
      rc = iconv (s->cd, &ib, &inlen, &ob, &olen);
148 149 150 151 152 153 154 155
      if (ib > s->buf)
	{
	  memmove (s->buf, ib, inlen);
	  s->bufpos = inlen;
	}
      else
	s->bufpos += nbytes;
      
156 157 158 159
      if (rc == -1)
	{
	  if (errno == E2BIG)
	    {
160 161
	      if (ob > optr)
		break;
162 163 164 165 166 167 168 169
	      else
		{
		  s->ec = MU_ERR_BUFSPACE;
		  return MU_ERR_BUFSPACE;
		}
	    }
	  else if (errno == EILSEQ)
	    {
170
	      switch (s->fallback_mode)
171
		{
172
		case mu_fallback_none:
173 174
		  s->state = state_iconv_error;
		  s->ec = errno;
175
		  if (ob == optr)
176
		    return MU_ERR_FAILURE;
177 178 179 180 181
		  break;

		case mu_fallback_copy_pass:
		  s->state = state_copy_pass;
		  if (ob == optr)
182
		    return _icvt_read (stream, optr, osize, pnbytes);
183 184 185 186 187
		  break;
		  
		case mu_fallback_copy_octal:
		  s->state = state_copy_octal;
		  if (ob == optr)
188
		    return _icvt_read (stream, optr, osize, pnbytes);
189 190 191 192 193 194 195 196 197 198
		  break;
		}
	    }
	  else if (errno == EINVAL)
	    {
	      if (inlen == s->bufsize)
		{
		  /* Try to reallocate temp buffer */
		  char *p = realloc (s->buf, s->bufsize + 128);
		  if (!p)
199
		    return ENOMEM;
200
		  s->buf = p;
201 202 203 204 205 206 207 208 209 210 211 212
		  s->bufsize += 128;
		}
	      continue;
	    }
	  else
	    {
	      s->ec = errno;
	      s->state = state_iconv_error;
	      return MU_ERR_FAILURE;
	    }
	}
    }
213
  while (olen > 0 
214 215 216 217
	 && (status = mu_stream_read (s->transport,
				      s->buf + s->bufpos,
				      s->bufsize - s->bufpos,
				      &nbytes)) == 0
218 219 220 221 222
	 && nbytes);

  if (status)
    {
      s->state = state_transport_error;
223 224
      s->ec = status;
      if (ob == optr)
225 226 227
	return MU_ERR_FAILURE;
    }
      
228
  if (pnbytes)
229
    *pnbytes = ob - optr;
230 231 232
  return 0;
}

233
#define ISPRINT(c) (((c)>=' '&&(c)<127)||(c)=='\n')
234 235

static int
236
copy_octal (struct icvt_stream *s, char *optr, size_t osize, size_t *pnbytes)
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
{
  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)
252 253 254 255
	    {
	      s->bufsize = rdcount;
	      s->buf = p;
	    }
256 257 258 259
	  else
	    rdcount = s->bufsize;
	}

260 261 262 263
      status = mu_stream_read (s->transport,
			       s->buf + s->bufpos,
			       rdcount - s->bufpos,
			       &rdcount);
264 265 266 267 268 269 270 271 272 273 274
      if (status)
	{
	  s->state = state_transport_error;
	  s->ec = status;
	  if (s->bufpos == 0)
	    return MU_ERR_FAILURE;
	}
      else
	s->bufpos += rdcount;
    }
  
275
  for (i = j = 0; j < osize && i < s->bufpos; i++)
276 277 278
    {
      if (ISPRINT (*(unsigned char*)(s->buf+i)))
	optr[j++] = s->buf[i];
279
      else if (j + 4 >= osize)
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
	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
295
copy_pass (struct icvt_stream *s, char *optr, size_t osize, size_t *pnbytes)
296 297 298 299 300 301 302 303 304 305 306
{
  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);
307
      if (pnbytes)
308 309 310 311
	*pnbytes = sz;
      return 0;
    }

312
  status = mu_stream_read (s->transport, optr, osize, &nbytes);
313 314 315 316 317 318 319
  if (status)
    {
      s->state = state_transport_error;
      s->ec = status;
      if (s->bufpos == 0)
	return MU_ERR_FAILURE;
    }
320
  if (pnbytes)
321 322 323 324 325
    *pnbytes = nbytes;
  return 0;
}

static int
326
_icvt_read (mu_stream_t stream, char *optr, size_t osize, size_t *pnbytes)
327
{
328
  struct icvt_stream *s = (struct icvt_stream *)stream;
329 330 331 332 333 334 335 336 337

  switch (s->state)
    {
    case state_open:
      return internal_icvt_read (stream, optr, osize, pnbytes);
      
    case state_closed:
      return EINVAL;
      
338 339
    case state_copy_pass:
      return copy_pass (s, optr, osize, pnbytes);
340
      
341 342
    case state_copy_octal:
      return copy_octal (s, optr, osize, pnbytes);
343 344 345 346 347 348 349
	
    default:
      break;
    }
  return MU_ERR_FAILURE;
}

350 351
const char *
_icvt_strerror (mu_stream_t stream, int rc)
352
{
353 354
  struct icvt_stream *s = (struct icvt_stream *)stream;

355 356 357 358 359 360 361 362 363 364 365 366
  switch (s->state)
    {
    case state_transport_error:
      snprintf (s->errbuf, sizeof s->errbuf,
		_("Transport error: %s"), mu_strerror (s->ec));
      break;

    case state_iconv_error:
      switch (s->ec)
	{
	case EILSEQ:
	  snprintf (s->errbuf, sizeof s->errbuf,
367
		    _("Illegal multibyte sequence near %*.*s"),
368
		    (int) s->bufpos, (int) s->bufpos, s->buf);
369 370 371 372 373 374 375 376 377
	  break;

	default:
	  snprintf (s->errbuf, sizeof s->errbuf,
		    _("Iconv error: %s"), mu_strerror (s->ec));
	}
      break;

    case state_closed:
378
      return _("Stream is closed"); 
379 380
      
    default:
381
      return mu_strerror (s->ec);
382 383
    }
  
384
  return s->errbuf;
385 386
}

387 388
static int
_icvt_ioctl (mu_stream_t stream, int code, void *ptr)
389
{
390
  struct icvt_stream *s = (struct icvt_stream *)stream;
391
  mu_transport_t *ptrans;
392 393 394 395 396 397 398
  
  switch (code)
    {
    case MU_IOCTL_GET_TRANSPORT:
      if (!ptr)
	return EINVAL;
      ptrans = ptr;
399 400
      ptrans[0] = (mu_transport_t) s->transport;
      ptrans[1] = NULL;
401 402
      break;

403 404 405
    case MU_IOCTL_SWAP_STREAM:
      return mu_stream_ioctl (s->transport, code, ptr);
      
406 407 408
    default:
      return EINVAL;
    }
409 410 411 412
  return 0;
}

int
413
_icvt_wait (mu_stream_t stream, int *pflags, struct timeval *tvp)
414
{
415 416 417 418
  struct icvt_stream *s = (struct icvt_stream *)stream;
  return mu_stream_wait (s->transport, pflags, tvp);
}

419
/* FIXME: Seeks in the *transport* stream. */
420
int
421
_icvt_seek (mu_stream_t stream, mu_off_t off, mu_off_t *presult)
422 423
{
  struct icvt_stream *s = (struct icvt_stream *)stream;
424
  return mu_stream_seek (s->transport, off, MU_SEEK_SET, presult);
425 426 427
}

int
428
mu_filter_iconv_create (mu_stream_t *s, mu_stream_t transport,
429 430
			const char *fromcode, const char *tocode, int flags,
			enum mu_iconv_fallback_mode fallback_mode)
431 432 433 434 435 436 437
{
  struct icvt_stream *iptr;
  iconv_t cd;
  
  cd = iconv_open (tocode, fromcode);
  if (cd == (iconv_t) -1)
    return MU_ERR_FAILURE;
438 439

  iptr = (struct icvt_stream *) _mu_stream_create (sizeof (*iptr), flags);
440
  if (!iptr)
441 442 443 444 445
    {
      iconv_close (cd);
      return ENOMEM;
    }
  
446 447
  if (!(flags & MU_STREAM_AUTOCLOSE))
    mu_stream_ref (transport);
448
  iptr->transport = transport;
449
  iptr->fallback_mode = fallback_mode;
450 451 452 453 454 455 456 457 458 459 460
  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;
  
461 462 463 464 465 466 467 468
  iptr->stream.open = _icvt_open;
  iptr->stream.close = _icvt_close;
  iptr->stream.read = _icvt_read;
  iptr->stream.done = _icvt_done;
  iptr->stream.error_string = _icvt_strerror;
  iptr->stream.ctl = _icvt_ioctl;
  iptr->stream.wait = _icvt_wait;
  iptr->stream.seek = _icvt_seek;
469
  iptr->stream.flags = MU_STREAM_READ | MU_STREAM_SEEK;
470
  *s = (mu_stream_t)iptr;
471 472
  return 0;
}