i18n: Avoid memory leak when create_iconv() fails.
[pspp] / src / libpspp / i18n.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2009, 2010 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program 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
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <xalloc.h>
19 #include <assert.h>
20 #include <locale.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <libintl.h>
25 #include <iconv.h>
26 #include <errno.h>
27 #include <relocatable.h>
28 #include "assertion.h"
29 #include "hmapx.h"
30 #include "hash-functions.h"
31 #include "pool.h"
32
33 #include "i18n.h"
34
35 #include "version.h"
36
37 #include <localcharset.h>
38 #include "xstrndup.h"
39
40 #if HAVE_NL_LANGINFO
41 #include <langinfo.h>
42 #endif
43
44 struct converter
45  {
46     char *tocode;
47     char *fromcode;
48     iconv_t conv;
49   };
50
51 static char *default_encoding;
52 static struct hmapx map;
53
54 /* A wrapper around iconv_open */
55 static iconv_t
56 create_iconv (const char* tocode, const char* fromcode)
57 {
58   size_t hash;
59   struct hmapx_node *node;
60   struct converter *converter;
61   assert (fromcode);
62
63   hash = hash_string (tocode, hash_string (fromcode, 0));
64   HMAPX_FOR_EACH_WITH_HASH (converter, node, hash, &map)
65     if (!strcmp (tocode, converter->tocode)
66         && !strcmp (fromcode, converter->fromcode))
67       return converter->conv;
68
69   converter = xmalloc (sizeof *converter);
70   converter->tocode = xstrdup (tocode);
71   converter->fromcode = xstrdup (fromcode);
72   converter->conv = iconv_open (tocode, fromcode);
73   hmapx_insert (&map, converter, hash);
74
75   /* I don't think it's safe to translate this string or to use messaging
76      as the converters have not yet been set up */
77   if ( (iconv_t) -1 == converter->conv && 0 != strcmp (tocode, fromcode))
78     {
79       const int err = errno;
80       fprintf (stderr,
81                "Warning: "
82                "cannot create a converter for `%s' to `%s': %s\n",
83                fromcode, tocode, strerror (err));
84     }
85
86   return converter->conv;
87 }
88
89
90 /* Similar to recode_string_pool, but allocates the returned value on the heap
91    instead of in a pool.  It is the caller's responsibility to free the
92    returned value. */
93 char *
94 recode_string (const char *to, const char *from,
95                const char *text, int length)
96 {
97   return recode_string_pool (to, from, text, length, NULL);
98 }
99
100
101 /* Converts the string TEXT, which should be encoded in FROM-encoding, to a
102    dynamically allocated string in TO-encoding.  Any characters which cannot be
103    converted will be represented by '?'.
104
105    LENGTH should be the length of the string or -1, if null terminated.
106
107    The returned string will be allocated on POOL.
108
109    This function's behaviour differs from that of g_convert_with_fallback
110    provided by GLib.  The GLib function will fail (returns NULL) if any part of
111    the input string is not valid in the declared input encoding.  This function
112    however perseveres even in the presence of badly encoded input. */
113 char *
114 recode_string_pool (const char *to, const char *from,
115                     const char *text, int length, struct pool *pool)
116 {
117   char *outbuf = 0;
118   size_t outbufferlength;
119   size_t result;
120   char *op ;
121   size_t inbytes = 0;
122   size_t outbytes ;
123   iconv_t conv ;
124
125   /* FIXME: Need to ensure that this char is valid in the target encoding */
126   const char fallbackchar = '?';
127
128   if ( text == NULL )
129     return NULL;
130
131   if ( length == -1 )
132      length = strlen(text);
133
134   if (to == NULL)
135     to = default_encoding;
136
137   if (from == NULL)
138     from = default_encoding;
139
140   conv = create_iconv (to, from);
141
142   if ( (iconv_t) -1 == conv )
143     return xstrdup (text);
144
145   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
146     if ( outbufferlength > length)
147       break;
148
149   outbuf = pool_malloc (pool, outbufferlength);
150   op = outbuf;
151
152   outbytes = outbufferlength;
153   inbytes = length;
154
155
156   do {
157     const char *ip = text;
158     result = iconv (conv, (ICONV_CONST char **) &text, &inbytes,
159                    &op, &outbytes);
160
161     if ( -1 == result )
162       {
163         int the_error = errno;
164
165         switch (the_error)
166           {
167           case EILSEQ:
168           case EINVAL:
169             if ( outbytes > 0 )
170               {
171                 *op++ = fallbackchar;
172                 outbytes--;
173                 text++;
174                 inbytes--;
175                 break;
176               }
177             /* Fall through */
178           case E2BIG:
179             pool_free (pool, outbuf);
180             outbufferlength <<= 1;
181             outbuf = pool_malloc (pool, outbufferlength);
182             op = outbuf;
183             outbytes = outbufferlength;
184             inbytes = length;
185             text = ip;
186             break;
187           default:
188             /* should never happen */
189             fprintf (stderr, "Character conversion error: %s\n",
190                      strerror (the_error));
191             NOT_REACHED ();
192             break;
193           }
194       }
195   } while ( -1 == result );
196
197   if (outbytes == 0 )
198     {
199       char *const oldaddr = outbuf;
200       outbuf = pool_realloc (pool, outbuf, outbufferlength + 1);
201
202       op += (outbuf - oldaddr) ;
203     }
204
205   *op = '\0';
206
207   return outbuf;
208 }
209
210
211 void
212 i18n_init (void)
213 {
214 #if ENABLE_NLS
215   setlocale (LC_CTYPE, "");
216 #ifdef LC_MESSAGES
217   setlocale (LC_MESSAGES, "");
218 #endif
219 #if HAVE_LC_PAPER
220   setlocale (LC_PAPER, "");
221 #endif
222   bindtextdomain (PACKAGE, relocate(locale_dir));
223   textdomain (PACKAGE);
224 #endif /* ENABLE_NLS */
225
226   assert (default_encoding == NULL);
227   default_encoding = xstrdup (locale_charset ());
228
229   hmapx_init (&map);
230 }
231
232
233 const char *
234 get_default_encoding (void)
235 {
236   return default_encoding;
237 }
238
239 void
240 set_default_encoding (const char *enc)
241 {
242   free (default_encoding);
243   default_encoding = xstrdup (enc);
244 }
245
246
247 /* Attempts to set the encoding from a locale name
248    returns true if successfull.
249    This function does not (should not!) alter the current locale.
250 */
251 bool
252 set_encoding_from_locale (const char *loc)
253 {
254   bool ok = true;
255   char *c_encoding;
256   char *loc_encoding;
257   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
258
259   setlocale (LC_CTYPE, "C");
260   c_encoding = xstrdup (locale_charset ());
261
262   setlocale (LC_CTYPE, loc);
263   loc_encoding = xstrdup (locale_charset ());
264
265
266   if ( 0 == strcmp (loc_encoding, c_encoding))
267     {
268       ok = false;
269     }
270
271
272   setlocale (LC_CTYPE, tmp);
273
274   free (tmp);
275
276   if (ok)
277     {
278       free (default_encoding);
279       default_encoding = loc_encoding;
280     }
281   else
282     free (loc_encoding);
283
284   free (c_encoding);
285
286   return ok;
287 }
288
289 void
290 i18n_done (void)
291 {
292   struct hmapx_node *node;
293   struct converter *cvtr;
294
295   HMAPX_FOR_EACH (cvtr, node, &map)
296     {
297       free (cvtr->tocode);
298       free (cvtr->fromcode);
299       iconv_close (cvtr->conv);
300       free (cvtr);
301     }
302
303   hmapx_destroy (&map);
304
305   free (default_encoding);
306   default_encoding = NULL;
307 }
308
309
310
311 bool
312 valid_encoding (const char *enc)
313 {
314   iconv_t conv = iconv_open ("UTF8", enc);
315
316   if ( conv == (iconv_t) -1)
317     return false;
318
319   iconv_close (conv);
320
321   return true;
322 }
323
324
325 /* Return the system local's idea of the
326    decimal seperator character */
327 char
328 get_system_decimal (void)
329 {
330   char radix_char;
331
332   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
333   setlocale (LC_NUMERIC, "");
334
335 #if HAVE_NL_LANGINFO
336   radix_char = nl_langinfo (RADIXCHAR)[0];
337 #else
338   {
339     char buf[10];
340     snprintf (buf, sizeof buf, "%f", 2.5);
341     radix_char = buf[1];
342   }
343 #endif
344
345   /* We MUST leave LC_NUMERIC untouched, since it would
346      otherwise interfere with data_{in,out} */
347   setlocale (LC_NUMERIC, ol);
348   free (ol);
349   return radix_char;
350 }
351