Change copyright notice from GPLv2+ to GPLv3+.
[gnulib.git] / lib / striconveha.c
1 /* Character set conversion with error handling and autodetection.
2    Copyright (C) 2002, 2005, 2007 Free Software Foundation, Inc.
3    Written by Bruno Haible.
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 "striconveha.h"
22
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "malloca.h"
28 #include "c-strcase.h"
29
30 #define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
31
32
33 /* Autodetection list.  */
34
35 struct autodetect_alias
36 {
37   struct autodetect_alias *next;
38   const char *name;
39   const char * const *encodings_to_try;
40 };
41
42 static const char * const autodetect_utf8_try[] =
43 {
44   /* Try UTF-8 first. There are very few ISO-8859-1 inputs that would
45      be valid UTF-8, but many UTF-8 inputs are valid ISO-8859-1.  */
46   "UTF-8", "ISO-8859-1",
47   NULL
48 };
49 static const char * const autodetect_jp_try[] =
50 {
51   /* Try 7-bit encoding first. If the input contains bytes >= 0x80,
52      it will fail.
53      Try EUC-JP next. Short SHIFT_JIS inputs may come out wrong. This
54      is unavoidable. People will condemn SHIFT_JIS.
55      If we tried SHIFT_JIS first, then some short EUC-JP inputs would
56      come out wrong, and people would condemn EUC-JP and Unix, which
57      would not be good.
58      Finally try SHIFT_JIS.  */
59   "ISO-2022-JP-2", "EUC-JP", "SHIFT_JIS",
60   NULL
61 };
62 static const char * const autodetect_kr_try[] =
63 {
64   /* Try 7-bit encoding first. If the input contains bytes >= 0x80,
65      it will fail.
66      Finally try EUC-KR.  */
67   "ISO-2022-KR", "EUC-KR",
68   NULL
69 };
70
71 static struct autodetect_alias autodetect_predefined[] =
72 {
73   { &autodetect_predefined[1], "autodetect_utf8", autodetect_utf8_try },
74   { &autodetect_predefined[2], "autodetect_jp",   autodetect_jp_try },
75   { NULL,                      "autodetect_kr",   autodetect_kr_try }
76 };
77
78 static struct autodetect_alias *autodetect_list = &autodetect_predefined[0];
79 static struct autodetect_alias **autodetect_list_end =
80   &autodetect_predefined[SIZEOF(autodetect_predefined)-1].next;
81
82 int
83 uniconv_register_autodetect (const char *name,
84                              const char * const *try_in_order)
85 {
86   size_t namelen;
87   size_t listlen;
88   size_t memneed;
89   size_t i;
90   char *memory;
91   struct autodetect_alias *new_alias;
92   char *new_name;
93   const char **new_try_in_order;
94
95   /* The TRY_IN_ORDER list must not be empty.  */
96   if (try_in_order[0] == NULL)
97     {
98       errno = EINVAL;
99       return -1;
100     }
101
102   /* We must deep-copy NAME and TRY_IN_ORDER, because they may be allocated
103      with dynamic extent.  */
104   namelen = strlen (name) + 1;
105   memneed = sizeof (struct autodetect_alias) + namelen + sizeof (char *);
106   for (i = 0; try_in_order[i] != NULL; i++)
107     memneed += sizeof (char *) + strlen (try_in_order[i]) + 1;
108   listlen = i;
109
110   memory = (char *) malloc (memneed);
111   if (memory != NULL)
112     {
113       new_alias = (struct autodetect_alias *) memory;
114       memory += sizeof (struct autodetect_alias);
115
116       new_try_in_order = (const char **) memory;
117       memory += (listlen + 1) * sizeof (char *);
118
119       new_name = (char *) memory;
120       memcpy (new_name, name, namelen);
121       memory += namelen;
122
123       for (i = 0; i < listlen; i++)
124         {
125           size_t len = strlen (try_in_order[i]) + 1;
126           memcpy (memory, try_in_order[i], len);
127           new_try_in_order[i] = (const char *) memory;
128           memory += len;
129         }
130       new_try_in_order[i] = NULL;
131
132       /* Now insert the new alias.  */
133       new_alias->name = new_name;
134       new_alias->encodings_to_try = new_try_in_order;
135       new_alias->next = NULL;
136       /* FIXME: Not multithread-safe.  */
137       *autodetect_list_end = new_alias;
138       autodetect_list_end = &new_alias->next;
139       return 0;
140     }
141   else
142     {
143       errno = ENOMEM;
144       return -1;
145     }
146 }
147
148 /* Like mem_iconveha, except no handling of transliteration.  */
149 static int
150 mem_iconveha_notranslit (const char *src, size_t srclen,
151                          const char *from_codeset, const char *to_codeset,
152                          enum iconv_ilseq_handler handler,
153                          size_t *offsets,
154                          char **resultp, size_t *lengthp)
155 {
156   int retval = mem_iconveh (src, srclen, from_codeset, to_codeset, handler,
157                             offsets, resultp, lengthp);
158   if (retval >= 0 || errno != EINVAL)
159     return retval;
160   else
161     {
162       struct autodetect_alias *alias;
163
164       /* Unsupported from_codeset or to_codeset. Check whether the caller
165          requested autodetection.  */
166       for (alias = autodetect_list; alias != NULL; alias = alias->next)
167         if (strcmp (from_codeset, alias->name) == 0)
168           {
169             const char * const *encodings;
170
171             if (handler != iconveh_error)
172               {
173                 /* First try all encodings without any forgiving.  */
174                 encodings = alias->encodings_to_try;
175                 do
176                   {
177                     retval = mem_iconveha_notranslit (src, srclen,
178                                                       *encodings, to_codeset,
179                                                       iconveh_error, offsets,
180                                                       resultp, lengthp);
181                     if (!(retval < 0 && errno == EILSEQ))
182                       return retval;
183                     encodings++;
184                   }
185                 while (*encodings != NULL);
186               }
187
188             encodings = alias->encodings_to_try;
189             do
190               {
191                 retval = mem_iconveha_notranslit (src, srclen,
192                                                   *encodings, to_codeset,
193                                                   handler, offsets,
194                                                   resultp, lengthp);
195                 if (!(retval < 0 && errno == EILSEQ))
196                   return retval;
197                 encodings++;
198               }
199             while (*encodings != NULL);
200
201             /* Return the last call's result.  */
202             return -1;
203           }
204
205       /* It wasn't an autodetection name.  */
206       errno = EINVAL;
207       return -1;
208     }
209 }
210
211 int
212 mem_iconveha (const char *src, size_t srclen,
213               const char *from_codeset, const char *to_codeset,
214               bool transliterate,
215               enum iconv_ilseq_handler handler,
216               size_t *offsets,
217               char **resultp, size_t *lengthp)
218 {
219   if (srclen == 0)
220     {
221       /* Nothing to convert.  */
222       *lengthp = 0;
223       return 0;
224     }
225
226   /* When using GNU libc >= 2.2 or GNU libiconv >= 1.5,
227      we want to use transliteration.  */
228 #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2) || __GLIBC__ > 2 || _LIBICONV_VERSION >= 0x0105
229   if (transliterate)
230     {
231       int retval;
232       size_t len = strlen (to_codeset);
233       char *to_codeset_suffixed = (char *) malloca (len + 10 + 1);
234       memcpy (to_codeset_suffixed, to_codeset, len);
235       memcpy (to_codeset_suffixed + len, "//TRANSLIT", 10 + 1);
236
237       retval = mem_iconveha_notranslit (src, srclen,
238                                         from_codeset, to_codeset_suffixed,
239                                         handler, offsets, resultp, lengthp);
240
241       freea (to_codeset_suffixed);
242
243       return retval;
244     }
245   else
246 #endif
247     return mem_iconveha_notranslit (src, srclen,
248                                     from_codeset, to_codeset,
249                                     handler, offsets, resultp, lengthp);
250 }
251
252 /* Like str_iconveha, except no handling of transliteration.  */
253 static char *
254 str_iconveha_notranslit (const char *src,
255                          const char *from_codeset, const char *to_codeset,
256                          enum iconv_ilseq_handler handler)
257 {
258   char *result = str_iconveh (src, from_codeset, to_codeset, handler);
259
260   if (result != NULL || errno != EINVAL)
261     return result;
262   else
263     {
264       struct autodetect_alias *alias;
265
266       /* Unsupported from_codeset or to_codeset. Check whether the caller
267          requested autodetection.  */
268       for (alias = autodetect_list; alias != NULL; alias = alias->next)
269         if (strcmp (from_codeset, alias->name) == 0)
270           {
271             const char * const *encodings;
272
273             if (handler != iconveh_error)
274               {
275                 /* First try all encodings without any forgiving.  */
276                 encodings = alias->encodings_to_try;
277                 do
278                   {
279                     result = str_iconveha_notranslit (src,
280                                                       *encodings, to_codeset,
281                                                       iconveh_error);
282                     if (!(result == NULL && errno == EILSEQ))
283                       return result;
284                     encodings++;
285                   }
286                 while (*encodings != NULL);
287               }
288
289             encodings = alias->encodings_to_try;
290             do
291               {
292                 result = str_iconveha_notranslit (src,
293                                                   *encodings, to_codeset,
294                                                   handler);
295                 if (!(result == NULL && errno == EILSEQ))
296                   return result;
297                 encodings++;
298               }
299             while (*encodings != NULL);
300
301             /* Return the last call's result.  */
302             return NULL;
303           }
304
305       /* It wasn't an autodetection name.  */
306       errno = EINVAL;
307       return NULL;
308     }
309 }
310
311 char *
312 str_iconveha (const char *src,
313               const char *from_codeset, const char *to_codeset,
314               bool transliterate,
315               enum iconv_ilseq_handler handler)
316 {
317   if (*src == '\0' || c_strcasecmp (from_codeset, to_codeset) == 0)
318     {
319       char *result = strdup (src);
320
321       if (result == NULL)
322         errno = ENOMEM;
323       return result;
324     }
325
326   /* When using GNU libc >= 2.2 or GNU libiconv >= 1.5,
327      we want to use transliteration.  */
328 #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2) || __GLIBC__ > 2 || _LIBICONV_VERSION >= 0x0105
329   if (transliterate)
330     {
331       char *result;
332       size_t len = strlen (to_codeset);
333       char *to_codeset_suffixed = (char *) malloca (len + 10 + 1);
334       memcpy (to_codeset_suffixed, to_codeset, len);
335       memcpy (to_codeset_suffixed + len, "//TRANSLIT", 10 + 1);
336
337       result = str_iconveha_notranslit (src, from_codeset, to_codeset_suffixed,
338                                         handler);
339
340       freea (to_codeset_suffixed);
341
342       return result;
343     }
344   else
345 #endif
346     return str_iconveha_notranslit (src, from_codeset, to_codeset, handler);
347 }