1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2004-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 3 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General
15    Public License along with this library.  If not, see
16    <http://www.gnu.org/licenses/>. */
17 
18 /* Iconv filter: converts input between two charsets.
19 
20    This filter operates in decode mode only.  Initialization sequence
21    requires two arguments: the name of the input encoding and that
22    of the output encoding.  Optional third argument specifies what
23    to do when an illegal character sequence is encountered on input.
24    Possible values are:
25 
26      none       - return failure and stop further conversions;
27      copy-pass  - copy the offending character to the output verbatim;
28      copy-octal - represent it as a C octal sequence (\NNN).
29 
30    The default is "copy-octal", unless overridden by mu_default_fallback_mode
31    setting (which see).
32 */
33 
34 #ifdef HAVE_CONFIG_H
35 # include <config.h>
36 #endif
37 
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <mailutils/stream.h>
43 #include <mailutils/sys/stream.h>
44 #include <mailutils/filter.h>
45 #include <mailutils/errno.h>
46 #include <mailutils/cstr.h>
47 #include <mailutils/cctype.h>
48 #include <mailutils/util.h>
49 
50 #ifdef HAVE_ICONV_H
51 # include <iconv.h>
52 #endif
53 
54 #ifndef ICONV_CONST
55 # define ICONV_CONST
56 #endif
57 
58 #ifndef HAVE_ICONV
59 # undef iconv_open
60 # define iconv_open(tocode, fromcode) ((iconv_t) -1)
61 
62 # undef iconv
63 # define iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft) (errno = EILSEQ, (size_t) -1)
64 
65 # undef iconv_close
66 # define iconv_close(cd) 0
67 
68 #endif
69 
70 struct _icvt_filter
71 {
72   char *fromcode;
73   char *tocode;
74   enum mu_iconv_fallback_mode fallback_mode;
75   iconv_t cd;           /* Conversion descriptor */
76 };
77 
78 static void
format_octal(char * op,unsigned char n)79 format_octal (char *op, unsigned char n)
80 {
81   op += 4;
82   *--op = n % 8 + '0';
83   n >>= 3;
84   *--op = n % 8 + '0';
85   n >>= 3;
86   *--op = n % 8 + '0';
87   n >>= 3;
88   *--op = '\\';
89 }
90 
91 static enum mu_filter_result
_icvt_decoder(void * xd,enum mu_filter_command cmd,struct mu_filter_io * iobuf)92 _icvt_decoder (void *xd,
93 	       enum mu_filter_command cmd,
94 	       struct mu_filter_io *iobuf)
95 {
96   struct _icvt_filter *_icvt = xd;
97   char *ip, *op;
98   size_t ilen, olen;
99   int rc;
100 
101   switch (cmd)
102     {
103     case mu_filter_init:
104       if (mu_c_strcasecmp (_icvt->fromcode, _icvt->tocode) == 0)
105 	_icvt->cd = (iconv_t) -1;
106       else
107 	{
108 	  iconv_t cd = iconv_open (_icvt->tocode, _icvt->fromcode);
109 	  if (cd == (iconv_t) -1)
110 	    return mu_filter_failure;
111 	  _icvt->cd = cd;
112 	}
113       return mu_filter_ok;
114 
115     case mu_filter_done:
116       if (_icvt->cd != (iconv_t) -1)
117 	iconv_close (_icvt->cd);
118       free (_icvt->fromcode);
119       free (_icvt->tocode);
120       return mu_filter_ok;
121 
122     default:
123       break;
124     }
125 
126   if (_icvt->cd == (iconv_t) -1)
127     {
128       size_t len = iobuf->isize;
129       if (len > iobuf->osize)
130 	len = iobuf->osize;
131       memcpy (iobuf->output, iobuf->input, len);
132       iobuf->isize = len;
133       iobuf->osize = len;
134       return mu_filter_ok;
135     }
136 
137   ip = (char*) iobuf->input;
138   ilen = iobuf->isize;
139   op = iobuf->output;
140   olen = iobuf->osize;
141  again:
142   rc = iconv (_icvt->cd, &ip, &ilen, &op, &olen);
143   if (rc == -1)
144     {
145       switch (errno)
146 	{
147 	case E2BIG:
148 	  iobuf->osize += 16;
149 	  return mu_filter_moreoutput;
150 
151 	case EINVAL:
152 	  /* An incomplete multibyte sequence is encountered in the input */
153 	  if (ilen == iobuf->isize)
154 	    {
155 	      iobuf->isize++;
156 	      return mu_filter_moreinput;
157 	    }
158 	  break;
159 
160 	case EILSEQ:
161 	  switch (_icvt->fallback_mode)
162 	    {
163 	    case mu_fallback_none:
164 	      iobuf->errcode = EILSEQ;
165 	      return mu_filter_failure;
166 
167 	    case mu_fallback_copy_pass:
168 	      *op++ = *ip++;
169 	      ilen--;
170 	      olen--;
171 	      break;
172 
173 	    case mu_fallback_copy_octal:
174 	      if (mu_isprint (*ip))
175 		{
176 		  *op++ = *ip++;
177 		  ilen--;
178 		  olen--;
179 		}
180 	      else
181 		{
182 		  if (olen < 4)
183 		    {
184 		      iobuf->osize += 4;
185 		      return mu_filter_moreoutput;
186 		    }
187 		  format_octal (op, *(unsigned char*)ip);
188 		  op += 4;
189 		  olen -= 4;
190 		  ip++;
191 		  ilen--;
192 		}
193 	    }
194 	  if (ilen && olen)
195 	    goto again;
196 	  break;
197 
198 	default:
199 	  iobuf->errcode = errno;
200 	  return mu_filter_failure;
201 	}
202     }
203 
204   iobuf->isize = ip - iobuf->input;
205   iobuf->osize = op - iobuf->output;
206   return mu_filter_ok;
207 }
208 
209 static int
alloc_state(void ** pret,int mode MU_ARG_UNUSED,int argc,const char ** argv)210 alloc_state (void **pret, int mode MU_ARG_UNUSED, int argc, const char **argv)
211 {
212   struct _icvt_filter *flt;
213   const char *from;
214   const char *to;
215   enum mu_iconv_fallback_mode fallback_mode = mu_default_fallback_mode;
216 
217   if (argc < 3)
218     return EINVAL; /* FIXME: Provide some defaults? */
219   if (argc > 4)
220     return EINVAL;
221 
222   from = argv[1];
223   to = argv[2];
224 
225   if (argc == 4)
226     {
227       const char *str = argv[3];
228       if (strcmp (str, "none") == 0)
229 	fallback_mode = mu_fallback_none;
230       else if (strcmp (str, "copy-pass") == 0)
231 	fallback_mode = mu_fallback_copy_pass;
232       else if (strcmp (str, "copy-octal") == 0)
233 	fallback_mode = mu_fallback_copy_octal;
234       else
235 	return EINVAL;
236     }
237 
238   flt = calloc (1, sizeof (*flt));
239   if (!flt)
240     return ENOMEM;
241   flt->fromcode = strdup (from);
242   if (!flt->fromcode)
243     {
244       free (flt);
245       return ENOMEM;
246     }
247   flt->tocode = strdup (to);
248   if (!flt->tocode)
249     {
250       free (flt->fromcode);
251       free (flt);
252       return ENOMEM;
253     }
254   flt->fallback_mode = fallback_mode;
255   flt->cd = (iconv_t) -1;
256   *pret = flt;
257   return 0;
258 }
259 
260 static struct _mu_filter_record _iconv_filter = {
261   "ICONV",
262   alloc_state,
263   _icvt_decoder,
264   _icvt_decoder
265 };
266 
267 mu_filter_record_t mu_iconv_filter = &_iconv_filter;
268