Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gnulib-local / lib / iconv-ostream.oo.c
1 /* Output stream that converts the output to another encoding.
2    Copyright (C) 2006-2007, 2010, 2015 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include "iconv-ostream.h"
22
23 #if HAVE_ICONV
24
25 #include <errno.h>
26 #include <iconv.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "c-strcase.h"
31 #include "error.h"
32 #include "xalloc.h"
33 #include "gettext.h"
34
35 #define _(str) gettext (str)
36
37 #endif /* HAVE_ICONV */
38
39 struct iconv_ostream : struct ostream
40 {
41 fields:
42 #if HAVE_ICONV
43   /* The destination stream.  */
44   ostream_t destination;
45   /* The from and to encodings.  */
46   char *from_encoding;
47   char *to_encoding;
48   /* The converter.  */
49   iconv_t cd;
50   /* Last few bytes that could not yet be converted.  */
51   #define BUFSIZE 64
52   char buf[BUFSIZE];
53   size_t buflen;
54 #endif /* HAVE_ICONV */
55 };
56
57 #if HAVE_ICONV
58
59 /* Implementation of ostream_t methods.  */
60
61 static void
62 iconv_ostream::write_mem (iconv_ostream_t stream, const void *data, size_t len)
63 {
64   if (len > 0)
65     {
66       #define BUFFERSIZE 256
67       char inbuffer[BUFFERSIZE];
68       size_t inbufcount;
69
70       inbufcount = stream->buflen;
71       if (inbufcount > 0)
72         memcpy (inbuffer, stream->buf, inbufcount);
73       for (;;)
74         {
75           /* At this point, inbuffer[0..inbufcount-1] is filled.  */
76           {
77             /* Combine the previous rest with a chunk of new input.  */
78             size_t n =
79               (len <= BUFFERSIZE - inbufcount ? len : BUFFERSIZE - inbufcount);
80
81             if (n > 0)
82               {
83                 memcpy (inbuffer + inbufcount, data, n);
84                 data = (char *) data + n;
85                 inbufcount += n;
86                 len -= n;
87               }
88           }
89           {
90             /* Attempt to convert the combined input.  */
91             char outbuffer[8*BUFFERSIZE];
92
93             const char *inptr = inbuffer;
94             size_t insize = inbufcount;
95             char *outptr = outbuffer;
96             size_t outsize = sizeof (outbuffer);
97
98             size_t res = iconv (stream->cd,
99                                 (ICONV_CONST char **) &inptr, &insize,
100                                 &outptr, &outsize);
101             #if !defined _LIBICONV_VERSION \
102                 && !(defined __GLIBC__ && !defined __UCLIBC__)
103             /* Irix iconv() inserts a NUL byte if it cannot convert.
104                NetBSD iconv() inserts a question mark if it cannot convert.
105                Only GNU libiconv and GNU libc are known to prefer to fail rather
106                than doing a lossy conversion.  */
107             if (res > 0)
108               {
109                 errno = EILSEQ;
110                 res = -1;
111               }
112             #endif
113             if (res == (size_t)(-1) && errno != EINVAL)
114               error (EXIT_FAILURE, 0, _("%s: cannot convert from %s to %s"),
115                      "iconv_ostream",
116                      stream->from_encoding, stream->to_encoding);
117             /* Output the converted part.  */
118             if (sizeof (outbuffer) - outsize > 0)
119               ostream_write_mem (stream->destination,
120                                  outbuffer, sizeof (outbuffer) - outsize);
121             /* Put back the unconverted part.  */
122             if (insize > BUFSIZE)
123               error (EXIT_FAILURE, 0, _("%s: shift sequence too long"),
124                      "iconv_ostream");
125             if (len == 0)
126               {
127                 if (insize > 0)
128                   memcpy (stream->buf, inptr, insize);
129                 stream->buflen = insize;
130                 break;
131               }
132             if (insize > 0)
133               memmove (inbuffer, inptr, insize);
134             inbufcount = insize;
135           }
136         }
137       #undef BUFFERSIZE
138     }
139 }
140
141 static void
142 iconv_ostream::flush (iconv_ostream_t stream)
143 {
144   /* There's nothing we can do here, since stream->buf[] contains only a few
145      bytes that don't correspond to a character.  */
146 }
147
148 static void
149 iconv_ostream::free (iconv_ostream_t stream)
150 {
151   /* Silently ignore the few bytes in stream->buf[] that don't correspond to a
152      character.  */
153
154   /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
155   #if defined _LIBICONV_VERSION \
156       || !(((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) \
157             && !defined __UCLIBC__) \
158            || defined __sun)
159   {
160     char outbuffer[2048];
161     char *outptr = outbuffer;
162     size_t outsize = sizeof (outbuffer);
163     size_t res = iconv (stream->cd, NULL, NULL, &outptr, &outsize);
164     if (res == (size_t)(-1))
165       error (EXIT_FAILURE, 0, _("%s: cannot convert from %s to %s"),
166              "iconv_ostream", stream->from_encoding, stream->to_encoding);
167     /* Output the converted part.  */
168     if (sizeof (outbuffer) - outsize > 0)
169       ostream_write_mem (stream->destination,
170                          outbuffer, sizeof (outbuffer) - outsize);
171   }
172   #endif
173
174   iconv_close (stream->cd);
175   free (stream->from_encoding);
176   free (stream->to_encoding);
177   free (stream);
178 }
179
180 /* Constructor.  */
181
182 iconv_ostream_t
183 iconv_ostream_create (const char *from_encoding, const char *to_encoding,
184                       ostream_t destination)
185 {
186   iconv_ostream_t stream = XMALLOC (struct iconv_ostream_representation);
187
188   stream->base.vtable = &iconv_ostream_vtable;
189   stream->destination = destination;
190   stream->from_encoding = xstrdup (from_encoding);
191   stream->to_encoding = xstrdup (to_encoding);
192
193   /* Avoid glibc-2.1 bug with EUC-KR.  */
194   #if ((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) \
195        && !defined __UCLIBC__) \
196       && !defined _LIBICONV_VERSION
197   if (c_strcasecmp (from_encoding, "EUC-KR") == 0
198       || c_strcasecmp (to_encoding, "EUC-KR") == 0)
199     stream->cd = (iconv_t)(-1):
200   else
201   #endif
202     stream->cd = iconv_open (to_encoding, from_encoding);
203   if (stream->cd == (iconv_t)(-1))
204     {
205       if (iconv_open ("UTF-8", from_encoding) == (iconv_t)(-1))
206         error (EXIT_FAILURE, 0, _("%s does not support conversion from %s"),
207                "iconv", from_encoding);
208       else if (iconv_open (to_encoding, "UTF-8") == (iconv_t)(-1))
209         error (EXIT_FAILURE, 0, _("%s does not support conversion to %s"),
210                "iconv", to_encoding);
211       else
212         error (EXIT_FAILURE, 0,
213                _("%s does not support conversion from %s to %s"),
214                "iconv", from_encoding, to_encoding);
215     }
216
217   stream->buflen = 0;
218
219   return stream;
220 }
221
222 #else
223
224 static void
225 iconv_ostream::write_mem (iconv_ostream_t stream, const void *data, size_t len)
226 {
227   abort ();
228 }
229
230 static void
231 iconv_ostream::flush (iconv_ostream_t stream)
232 {
233   abort ();
234 }
235
236 static void
237 iconv_ostream::free (iconv_ostream_t stream)
238 {
239   abort ();
240 }
241
242 #endif /* HAVE_ICONV */